diff --git a/src/Files.App/App.xaml.cs b/src/Files.App/App.xaml.cs index 0005b87a43cb..612b3f652cf2 100644 --- a/src/Files.App/App.xaml.cs +++ b/src/Files.App/App.xaml.cs @@ -3,6 +3,7 @@ using CommunityToolkit.WinUI.Helpers; using CommunityToolkit.WinUI.Notifications; +using Files.App.Helpers; using Files.App.Services.DateTimeFormatter; using Files.App.Services.Settings; using Files.App.Storage.FtpStorage; @@ -21,6 +22,8 @@ using Microsoft.Extensions.Logging; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Media; +using Microsoft.UI.Xaml.Media.Animation; using Microsoft.Windows.AppLifecycle; using System.IO; using System.Text; @@ -40,7 +43,7 @@ public partial class App : Application private static bool ShowErrorNotification = false; public static string OutputPath { get; set; } public static CommandBarFlyout? LastOpenedFlyout { get; set; } - public static TaskCompletionSource? SplashScreenLoadingTCS { get; set; } + public static TaskCompletionSource? SplashScreenLoadingTCS { get; private set; } public static StorageHistoryWrapper HistoryWrapper { get; } = new(); public static AppModel AppModel { get; private set; } @@ -176,12 +179,7 @@ await Task.WhenAll( FileTagsHelper.UpdateTagsDb(); }); - // Check for required updates - var updateService = Ioc.Default.GetRequiredService(); - await updateService.CheckForUpdates(); - await updateService.DownloadMandatoryUpdates(); - await updateService.CheckAndUpdateFilesLauncherAsync(); - await updateService.CheckLatestReleaseNotesAsync(); + await CheckForRequiredUpdates(); static async Task OptionalTask(Task task, bool condition) { @@ -190,6 +188,15 @@ static async Task OptionalTask(Task task, bool condition) } } + private static async Task CheckForRequiredUpdates() + { + var updateService = Ioc.Default.GetRequiredService(); + await updateService.CheckForUpdates(); + await updateService.DownloadMandatoryUpdates(); + await updateService.CheckAndUpdateFilesLauncherAsync(); + await updateService.CheckLatestReleaseNotesAsync(); + } + /// /// Invoked when the application is launched normally by the end user. Other entry points /// will be used such as when the application is launched to open a specific file. @@ -300,6 +307,43 @@ private async void Window_Closed(object sender, WindowEventArgs args) return; } + if (Ioc.Default.GetRequiredService().GeneralSettingsService.LeaveAppRunning && + !Process.GetProcessesByName("Files").Any(x => x.Id != Process.GetCurrentProcess().Id)) + { + // Close open content dialogs + UIHelpers.CloseAllDialogs(); + + // Cache the window instead of closing it + MainWindow.Instance.AppWindow.Hide(); + args.Handled = true; + + // Save and close all tabs + SaveSessionTabs(); + MainPageViewModel.AppInstances.ForEach(tabItem => tabItem.Unload()); + MainPageViewModel.AppInstances.Clear(); + await Task.Delay(100); + + // Wait for all properties windows to close + await FilePropertiesHelpers.WaitClosingAll(); + + // Sleep current instance + Program.Pool = new(0, 1, "Files-Instance"); + Thread.Yield(); + if (Program.Pool.WaitOne()) + { + // Resume the instance + Program.Pool.Dispose(); + MainWindow.Instance.AppWindow.Show(); + MainWindow.Instance.Activate(); + + _ = CheckForRequiredUpdates(); + + MainWindow.Instance.EnsureWindowIsInitialized().Navigate(typeof(MainPage), null, new SuppressNavigationTransitionInfo()); + } + + return; + } + // Method can take a long time, make sure the window is hidden await Task.Yield(); diff --git a/src/Files.App/Data/Contexts/Multitasking/MultitaskingContext.cs b/src/Files.App/Data/Contexts/Multitasking/MultitaskingContext.cs index 3dd85e43e55c..3611cc0b8044 100644 --- a/src/Files.App/Data/Contexts/Multitasking/MultitaskingContext.cs +++ b/src/Files.App/Data/Contexts/Multitasking/MultitaskingContext.cs @@ -1,13 +1,10 @@ // Copyright (c) 2023 Files Community // Licensed under the MIT License. See the LICENSE. -using CommunityToolkit.Mvvm.ComponentModel; using Files.App.UserControls.MultitaskingControl; -using Files.App.ViewModels; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Input; using System.Collections.Specialized; -using System.ComponentModel; namespace Files.App.Data.Contexts { @@ -46,7 +43,8 @@ private void AppInstances_CollectionChanged(object? sender, NotifyCollectionChan } private void AppModel_PropertyChanged(object? sender, PropertyChangedEventArgs e) { - UpdateCurrentTabIndex(); + if (e.PropertyName is nameof(AppModel.TabStripSelectedIndex)) + UpdateCurrentTabIndex(); } private void BaseMultitaskingControl_OnLoaded(object? sender, IMultitaskingControl control) { diff --git a/src/Files.App/Data/Models/AppModel.cs b/src/Files.App/Data/Models/AppModel.cs index 07ca46926daa..5d6585cca232 100644 --- a/src/Files.App/Data/Models/AppModel.cs +++ b/src/Files.App/Data/Models/AppModel.cs @@ -33,19 +33,13 @@ public int TabStripSelectedIndex get => tabStripSelectedIndex; set { - if (value >= 0) - { - if (tabStripSelectedIndex != value) - { - SetProperty(ref tabStripSelectedIndex, value); - } + SetProperty(ref tabStripSelectedIndex, value); - if (value < MainPageViewModel.AppInstances.Count) - { - Frame rootFrame = (Frame)MainWindow.Instance.Content; - var mainView = (MainPage)rootFrame.Content; - mainView.ViewModel.SelectedTabItem = MainPageViewModel.AppInstances[value]; - } + if (value >= 0 && value < MainPageViewModel.AppInstances.Count) + { + Frame rootFrame = (Frame)MainWindow.Instance.Content; + var mainView = (MainPage)rootFrame.Content; + mainView.ViewModel.SelectedTabItem = MainPageViewModel.AppInstances[value]; } } } diff --git a/src/Files.App/Helpers/UI/UIHelpers.cs b/src/Files.App/Helpers/UI/UIHelpers.cs index 6a5fad871b5e..83dce59f37f0 100644 --- a/src/Files.App/Helpers/UI/UIHelpers.cs +++ b/src/Files.App/Helpers/UI/UIHelpers.cs @@ -2,20 +2,10 @@ // Licensed under the MIT License. See the LICENSE. using CommunityToolkit.WinUI.Notifications; -using Files.App.Extensions; -using Files.App.Utils.Shell; -using Files.Core.ViewModels.Dialogs; -using Files.Shared; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Media; using Microsoft.UI.Xaml.Media.Imaging; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics; using System.IO; -using System.Linq; -using System.Threading.Tasks; using Windows.UI.Notifications; namespace Files.App.Helpers @@ -130,7 +120,7 @@ private static ContentDialog SetContentDialogRoot(ContentDialog contentDialog) public static void CloseAllDialogs() { - var openedDialogs = VisualTreeHelper.GetOpenPopups(MainWindow.Instance); + var openedDialogs = VisualTreeHelper.GetOpenPopupsForXamlRoot(MainWindow.Instance.Content.XamlRoot); foreach (var item in openedDialogs) { diff --git a/src/Files.App/MainWindow.xaml.cs b/src/Files.App/MainWindow.xaml.cs index 3dc0aa7315b9..4109bddadbf4 100644 --- a/src/Files.App/MainWindow.xaml.cs +++ b/src/Files.App/MainWindow.xaml.cs @@ -4,11 +4,11 @@ using Files.App.UserControls.MultitaskingControl; using Microsoft.UI; using Microsoft.UI.Windowing; +using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Media.Animation; using Microsoft.UI.Xaml.Navigation; using System.IO; -using System.Runtime.InteropServices; using Windows.ApplicationModel; using Windows.ApplicationModel.Activation; using Windows.Storage; @@ -176,7 +176,7 @@ public async Task InitializeApplication(object activatedEventArgs) rootFrame.Navigate(typeof(MainPage), null, new SuppressNavigationTransitionInfo()); } - private Frame EnsureWindowIsInitialized() + public Frame EnsureWindowIsInitialized() { // NOTE: // Do not repeat app initialization when the Window already has content, diff --git a/src/Files.App/Program.cs b/src/Files.App/Program.cs index 31fbf6c315fa..81be191d6cc6 100644 --- a/src/Files.App/Program.cs +++ b/src/Files.App/Program.cs @@ -14,6 +14,20 @@ namespace Files.App { internal class Program { + public static Semaphore Pool; + + static Program() + { + Pool = new(0, 1, "Files-Instance", out var isNew); + if (!isNew) + { + // Resume cached instance + Pool.Release(); + Environment.Exit(0); + } + Pool.Dispose(); + } + // Note: // We can't declare Main to be async because in a WinUI app // This prevents Narrator from reading XAML elements diff --git a/src/Files.App/Services/Settings/GeneralSettingsService.cs b/src/Files.App/Services/Settings/GeneralSettingsService.cs index 9b1a8d05f9d7..641091bb1d82 100644 --- a/src/Files.App/Services/Settings/GeneralSettingsService.cs +++ b/src/Files.App/Services/Settings/GeneralSettingsService.cs @@ -208,6 +208,12 @@ public bool ShowOpenInNewPane set => Set(value); } + public bool LeaveAppRunning + { + get => Get(true); + set => Set(value); + } + public FileNameConflictResolveOptionType ConflictsResolveOption { get => (FileNameConflictResolveOptionType)Get((long)FileNameConflictResolveOptionType.GenerateNewName); @@ -254,6 +260,7 @@ protected override void RaiseOnSettingChangedEvent(object sender, SettingChanged case nameof(ShowOpenInNewTab): case nameof(ShowOpenInNewWindow): case nameof(ShowOpenInNewPane): + case nameof(LeaveAppRunning): case nameof(ConflictsResolveOption): case nameof(ShowHashesDictionary): Analytics.TrackEvent($"Set {e.SettingName} to {e.NewValue}"); diff --git a/src/Files.App/Strings/en-US/Resources.resw b/src/Files.App/Strings/en-US/Resources.resw index 34c02fa84c1b..6045b7e2f870 100644 --- a/src/Files.App/Strings/en-US/Resources.resw +++ b/src/Files.App/Strings/en-US/Resources.resw @@ -3447,4 +3447,7 @@ Due to platform limitations, drag and drop isn't available when running Files as administrator. + + Leave app running in the background when the window is closed + \ No newline at end of file diff --git a/src/Files.App/Utils/Storage/Helpers/FilePropertiesHelpers.cs b/src/Files.App/Utils/Storage/Helpers/FilePropertiesHelpers.cs index 5dfbf15d4eff..fa9915450952 100644 --- a/src/Files.App/Utils/Storage/Helpers/FilePropertiesHelpers.cs +++ b/src/Files.App/Utils/Storage/Helpers/FilePropertiesHelpers.cs @@ -31,6 +31,8 @@ public static class FilePropertiesHelpers public static nint GetWindowHandle(Window w) => WinRT.Interop.WindowNative.GetWindowHandle(w); + private static int WindowCount = 0; + private static TaskCompletionSource? PropertiesWindowsClosingTCS; private static BlockingCollection WindowCache = new(); /// @@ -140,8 +142,10 @@ public static void OpenPropertiesWindow(object item, IShellPage associatedInstan + Math.Max(0, Math.Min(displayArea.WorkArea.Height - appWindow.Size.Height, pointerPosition.Y - displayArea.WorkArea.Y)), }; - appWindow.Move(appWindowPos); + if (Interlocked.Increment(ref WindowCount) == 1) + PropertiesWindowsClosingTCS = new(); + appWindow.Move(appWindowPos); appWindow.Show(); } @@ -156,9 +160,19 @@ private static void PropertiesWindow_Closed(object sender, WindowEventArgs args) window.AppWindow.Hide(); window.Content = null; WindowCache.Add(window); + + if (Interlocked.Decrement(ref WindowCount) == 0) + { + PropertiesWindowsClosingTCS!.TrySetResult(); + PropertiesWindowsClosingTCS = null; + } } } + /// + /// Destroy all cached properties windows + /// + /// public static void DestroyCachedWindows() { while (WindowCache.TryTake(out var window)) @@ -167,5 +181,11 @@ public static void DestroyCachedWindows() window.Close(); } } + + /// + /// Returns task to wait for all properties windows to close + /// + /// Task to wait + public static Task WaitClosingAll() => PropertiesWindowsClosingTCS?.Task ?? Task.CompletedTask; } } diff --git a/src/Files.App/ViewModels/Settings/AdvancedViewModel.cs b/src/Files.App/ViewModels/Settings/AdvancedViewModel.cs index 2991c5469b6c..1949c1417b54 100644 --- a/src/Files.App/ViewModels/Settings/AdvancedViewModel.cs +++ b/src/Files.App/ViewModels/Settings/AdvancedViewModel.cs @@ -297,6 +297,20 @@ public bool CanOpenOnWindowsStartup set => SetProperty(ref canOpenOnWindowsStartup, value); } + public bool LeaveAppRunning + { + get => UserSettingsService.GeneralSettingsService.LeaveAppRunning; + set + { + if (value != UserSettingsService.GeneralSettingsService.LeaveAppRunning) + { + UserSettingsService.GeneralSettingsService.LeaveAppRunning = value; + + OnPropertyChanged(); + } + } + } + public async Task OpenFilesOnWindowsStartup() { var stateMode = await ReadState(); diff --git a/src/Files.App/Views/Properties/MainPropertiesPage.xaml.cs b/src/Files.App/Views/Properties/MainPropertiesPage.xaml.cs index f59fdc1b93aa..b8f2233ea386 100644 --- a/src/Files.App/Views/Properties/MainPropertiesPage.xaml.cs +++ b/src/Files.App/Views/Properties/MainPropertiesPage.xaml.cs @@ -1,11 +1,6 @@ // Copyright (c) 2023 Files Community // Licensed under the MIT License. See the LICENSE. -using CommunityToolkit.Mvvm.DependencyInjection; -using CommunityToolkit.WinUI; -using Files.App.Data.Parameters; -using Files.App.Helpers; -using Files.App.ViewModels; using Files.App.ViewModels.Properties; using Microsoft.UI; using Microsoft.UI.Windowing; @@ -13,8 +8,6 @@ using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Input; using Microsoft.UI.Xaml.Navigation; -using System; -using System.Threading.Tasks; using Windows.System; using Windows.UI; @@ -45,9 +38,6 @@ protected override void OnNavigatedTo(NavigationEventArgs e) AppWindow = parameter.AppWindow; Window = parameter.Window; - AppSettings = Ioc.Default.GetRequiredService(); - AppSettings.ThemeModeChanged += AppSettings_ThemeModeChanged; - base.OnNavigatedTo(e); MainPropertiesViewModel = new(Window, AppWindow, MainContentFrame, BaseProperties, parameter); @@ -55,6 +45,8 @@ protected override void OnNavigatedTo(NavigationEventArgs e) private void Page_Loaded(object sender, RoutedEventArgs e) { + AppSettings = Ioc.Default.GetRequiredService(); + AppSettings.ThemeModeChanged += AppSettings_ThemeModeChanged; Window.Closed += Window_Closed; UpdatePageLayout(); @@ -89,6 +81,9 @@ private void UpdatePageLayout() private async void AppSettings_ThemeModeChanged(object? sender, EventArgs e) { + if (Parent is null) + return; + await DispatcherQueue.EnqueueOrInvokeAsync(() => { ((Frame)Parent).RequestedTheme = ThemeHelper.RootTheme; diff --git a/src/Files.App/Views/Settings/AdvancedPage.xaml b/src/Files.App/Views/Settings/AdvancedPage.xaml index 72f67ea6d68e..bc1dda4d903c 100644 --- a/src/Files.App/Views/Settings/AdvancedPage.xaml +++ b/src/Files.App/Views/Settings/AdvancedPage.xaml @@ -98,6 +98,17 @@ + + + + + + + + bool ShowSendToMenu { get; set; } + /// + /// Gets or sets a value indicating whether or not to leave app running in the background. + /// + bool LeaveAppRunning { get; set; } + /// /// Gets or sets a value indicating the default option to resolve conflicts. ///