From 00ef35670cddcf6f73057285a1580a4f8f21909c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Moreau?= Date: Tue, 27 Oct 2026 11:30:57 -0400 Subject: [PATCH 1/2] Let UniGetUI keep running in the background without a tray icon The "Close UniGetUI to the system tray" checkbox controlled three things at once: whether closing the window quits, whether the tray icon shows, and whether notifications are sent. It is now a "When the main window is closed:" dropdown with three choices: keep running with a tray icon (the default), keep running without a tray icon, or quit. The new choice is stored in a separate HideSystemTrayIcon flag, so existing settings keep their meaning and an older version simply shows the icon again. Notifications still depend only on UniGetUI staying alive, not on the icon. The first time the window closes without an icon, a notice explains how to reopen and quit UniGetUI. macOS hides the quit choice, since closing the window never quits there. Closes #5361 Co-Authored-By: Claude Opus 5.5 --- src/Languages/lang_en.json | 11 +++- .../Infrastructure/SettingsSearchIndex.cs | 2 +- .../Infrastructure/TrayService.cs | 2 +- .../SettingsPages/NotificationsViewModel.cs | 8 +-- .../Views/Controls/Settings/ComboboxCard.cs | 15 +++--- .../Views/MainWindow.axaml.cs | 54 +++++++++++++++++++ .../Pages/SettingsPages/Interface_P.axaml | 6 +-- .../Pages/SettingsPages/Interface_P.axaml.cs | 42 +++++++++++++++ .../Pages/SettingsPages/Notifications.axaml | 6 ++- .../SettingsTest.cs | 53 ++++++++++++++++++ .../SettingsEngine_Extras.cs | 30 +++++++++++ .../SettingsEngine_Names.cs | 4 ++ src/UniGetUI.Tui/Views/Pages/SettingsPage.cs | 9 +++- 13 files changed, 222 insertions(+), 20 deletions(-) diff --git a/src/Languages/lang_en.json b/src/Languages/lang_en.json index 7f56d229ec..b2e34314c1 100644 --- a/src/Languages/lang_en.json +++ b/src/Languages/lang_en.json @@ -2004,5 +2004,14 @@ "UniGetUI could not replace {0}, so it is still running the previous version. Open the log to see what happened.": "UniGetUI could not replace {0}, so it is still running the previous version. Open the log to see what happened.", "UniGetUI cannot update itself from a read-only location.": "UniGetUI cannot update itself from a read-only location.", "UniGetUI is running from {0}, which cannot be modified, for example a mounted disk image. Move UniGetUI to the Applications folder, open it from there, and try again.": "UniGetUI is running from {0}, which cannot be modified, for example a mounted disk image. Move UniGetUI to the Applications folder, open it from there, and try again.", - "Copy link": "Copy link" + "Copy link": "Copy link", + "When the main window is closed:": "When the main window is closed:", + "Keep running in the background, with a system tray icon": "Keep running in the background, with a system tray icon", + "Keep running in the background, without a system tray icon": "Keep running in the background, without a system tray icon", + "UniGetUI keeps checking for updates. To reopen it, launch UniGetUI again. To exit, use More > Quit UniGetUI in the navigation menu.": "UniGetUI keeps checking for updates. To reopen it, launch UniGetUI again. To exit, use More > Quit UniGetUI in the navigation menu.", + "Notifications are not available on Linux, so you will not be told when updates are found.": "Notifications are not available on Linux, so you will not be told when updates are found.", + "UniGetUI stops checking for updates and sending notifications until you open it again.": "UniGetUI stops checking for updates and sending notifications until you open it again.", + "UniGetUI is still running": "UniGetUI is still running", + "UniGetUI keeps running in the background and checking for updates, without a system tray icon. To reopen it, launch UniGetUI again. To exit, use More > Quit UniGetUI in the navigation menu. You can change this in Settings > User interface preferences.": "UniGetUI keeps running in the background and checking for updates, without a system tray icon. To reopen it, launch UniGetUI again. To exit, use More > Quit UniGetUI in the navigation menu. You can change this in Settings > User interface preferences.", + "UniGetUI must keep running in the background when its window is closed for notifications to work": "UniGetUI must keep running in the background when its window is closed for notifications to work" } diff --git a/src/UniGetUI.Avalonia/Infrastructure/SettingsSearchIndex.cs b/src/UniGetUI.Avalonia/Infrastructure/SettingsSearchIndex.cs index 22922cc075..b73bc79bd3 100644 --- a/src/UniGetUI.Avalonia/Infrastructure/SettingsSearchIndex.cs +++ b/src/UniGetUI.Avalonia/Infrastructure/SettingsSearchIndex.cs @@ -56,7 +56,7 @@ private sealed record Entry( new("Navigation menu:", ["sidebar", "nav menu", "navigation", "layout", "docked", "overlay"], typeof(Interface_P), "NavMenuModeSelector"), new("Navigation menu order", ["sidebar", "nav menu", "navigation", "order", "reorder", "drag", "tabs", "pages", "shortcuts"], typeof(Interface_P), "NavigationOrderCard"), new("Automatically switch to software rendering when Windows has no hardware GPU", ["rendering", "gpu", "software rendering"], typeof(Interface_P), "InterfaceRenderingCard"), - new("Close UniGetUI to the system tray", ["system tray", "background", "minimize to tray"], typeof(Interface_P), "DisableSystemTrayCard"), + new("When the main window is closed:", ["system tray", "tray icon", "hide icon", "background", "minimize to tray", "close", "quit"], typeof(Interface_P), "WindowCloseBehaviorSelector"), new("Manage UniGetUI autostart behaviour", ["autostart", "run at login", "startup"], typeof(Interface_P), "EditAutostartSettings"), new("Show package icons on package lists", ["package icons"], typeof(Interface_P), "InterfacePackageListsCard"), new("Show illustrations on package lists", ["illustrations", "package illustrations"], typeof(Interface_P), "PackageIllustrationsCard"), diff --git a/src/UniGetUI.Avalonia/Infrastructure/TrayService.cs b/src/UniGetUI.Avalonia/Infrastructure/TrayService.cs index a42a4d1eaf..280e45cc3d 100644 --- a/src/UniGetUI.Avalonia/Infrastructure/TrayService.cs +++ b/src/UniGetUI.Avalonia/Infrastructure/TrayService.cs @@ -63,7 +63,7 @@ public void UpdateStatus() { try { - _trayIcon.IsVisible = !Settings.Get(Settings.K.DisableSystemTray); + _trayIcon.IsVisible = Settings.IsSystemTrayIconVisible(); string status; string tooltip; diff --git a/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/NotificationsViewModel.cs b/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/NotificationsViewModel.cs index 76dc13883d..06893fb800 100644 --- a/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/NotificationsViewModel.cs +++ b/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/NotificationsViewModel.cs @@ -7,11 +7,11 @@ namespace UniGetUI.Avalonia.ViewModels.Pages.SettingsPages; public partial class NotificationsViewModel : ViewModelBase { - [ObservableProperty] private bool _isSystemTrayEnabled; + [ObservableProperty] private bool _isBackgroundModeEnabled; [ObservableProperty] private bool _isNotificationsEnabled; - /// True when the system-tray-disabled warning should be shown. - public bool IsSystemTrayWarningVisible => !IsSystemTrayEnabled; + /// True when closing the window quits UniGetUI, which turns notifications off. + public bool IsSystemTrayWarningVisible => !IsBackgroundModeEnabled; // Per-type notifications are delivered only by the Windows/macOS bridges; Linux has no // delivery path, so the whole "Notification types" group is hidden there. @@ -20,7 +20,7 @@ public partial class NotificationsViewModel : ViewModelBase public NotificationsViewModel() { - _isSystemTrayEnabled = !CoreSettings.Get(CoreSettings.K.DisableSystemTray); + _isBackgroundModeEnabled = !CoreSettings.Get(CoreSettings.K.DisableSystemTray); _isNotificationsEnabled = !CoreSettings.Get(CoreSettings.K.DisableNotifications); } diff --git a/src/UniGetUI.Avalonia/Views/Controls/Settings/ComboboxCard.cs b/src/UniGetUI.Avalonia/Views/Controls/Settings/ComboboxCard.cs index 0dcc120c9d..f5871d21e1 100644 --- a/src/UniGetUI.Avalonia/Views/Controls/Settings/ComboboxCard.cs +++ b/src/UniGetUI.Avalonia/Views/Controls/Settings/ComboboxCard.cs @@ -60,11 +60,17 @@ public void AddItem(string name, string value, bool translate) _inverted_val_ref.Add(value, name); } - public void ShowAddedItems() + public void ShowAddedItems() => ShowAddedItems( + () => CoreSettings.GetValue(settings_name), + value => CoreSettings.SetValue(settings_name, value)); + + /// Shows the added items, reading and writing the selection through the given + /// accessors instead of a single string setting. + public void ShowAddedItems(Func readValue, Action writeValue) { try { - string savedItem = CoreSettings.GetValue(settings_name); + string savedItem = readValue(); _combobox.SelectedIndex = _elements.IndexOf(_inverted_val_ref[savedItem]); } catch @@ -76,10 +82,7 @@ public void ShowAddedItems() try { string selectedName = _combobox.SelectedItem?.ToString() ?? ""; - CoreSettings.SetValue( - settings_name, - _values_ref[selectedName] - ); + writeValue(_values_ref[selectedName]); ValueChanged?.Invoke(this, EventArgs.Empty); var cmd = ValueChangedCommand; if (cmd?.CanExecute(null) == true) diff --git a/src/UniGetUI.Avalonia/Views/MainWindow.axaml.cs b/src/UniGetUI.Avalonia/Views/MainWindow.axaml.cs index f8b36c3c9d..9d755bc76c 100644 --- a/src/UniGetUI.Avalonia/Views/MainWindow.axaml.cs +++ b/src/UniGetUI.Avalonia/Views/MainWindow.axaml.cs @@ -159,6 +159,7 @@ public partial class MainWindow : Window private bool _maxButtonPressed; private TrayService? _trayService; private bool _allowClose; + private bool _showingHiddenTrayIconNotice; private readonly ApplicationShutdownCoordinator _shutdownCoordinator = new(); // Saved outer size (DIPs) awaiting a native, exact restore in OnOpened on Windows. @@ -307,6 +308,19 @@ protected override void OnClosing(WindowClosingEventArgs e) if (!_allowClose && (OperatingSystem.IsMacOS() || !Settings.Get(Settings.K.DisableSystemTray))) { e.Cancel = true; + if (_showingHiddenTrayIconNotice) + return; + + // Without a tray icon nothing on screen shows that UniGetUI is still running, so say it once. + // macOS keeps the app in the Dock, which already makes that visible. + if (!OperatingSystem.IsMacOS() + && !Settings.IsSystemTrayIconVisible() + && !Settings.Get(Settings.K.ShownHiddenTrayIconNotice)) + { + _ = ShowHiddenTrayIconNoticeThenHideAsync(); + return; + } + ViewModel.ClearAllSearchQueries(); Hide(); return; @@ -316,6 +330,46 @@ protected override void OnClosing(WindowClosingEventArgs e) QuitApplication(); } + private async Task ShowHiddenTrayIconNoticeThenHideAsync() + { + _showingHiddenTrayIconNotice = true; + try + { + Settings.Set(Settings.K.ShownHiddenTrayIconNotice, true); + var dialog = new ImmersiveConfirmationDialog( + CoreTools.Translate("UniGetUI is still running"), + new TextBlock + { + Text = CoreTools.Translate("UniGetUI keeps running in the background and checking for updates, without a system tray icon. To reopen it, launch UniGetUI again. To exit, use More > Quit UniGetUI in the navigation menu. You can change this in Settings > User interface preferences."), + FontSize = 14, + LineHeight = 20, + TextWrapping = TextWrapping.Wrap, + }, + CoreTools.Translate("OK"), + CoreTools.Translate("Quit UniGetUI")); + await dialog.ShowDialog(this); + + if (dialog.Result is false) + { + QuitApplication(); + return; + } + + ViewModel.ClearAllSearchQueries(); + Hide(); + } + catch (Exception ex) + { + Logger.Error("Could not show the background notice:"); + Logger.Error(ex); + Hide(); + } + finally + { + _showingHiddenTrayIconNotice = false; + } + } + private void ReleaseWindowResources() { SaveGeometryNow(); diff --git a/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/Interface_P.axaml b/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/Interface_P.axaml index 6242a1a7b4..355fad1041 100644 --- a/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/Interface_P.axaml +++ b/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/Interface_P.axaml @@ -72,10 +72,8 @@ Margin="44,32,4,8" automation:AutomationProperties.HeadingLevel="2"/> - MainWindow.Instance?.RefreshNavigationMode(); + WindowCloseBehaviorSelector.AddItem( + CoreTools.Translate("Keep running in the background, with a system tray icon"), + nameof(CoreSettings.WindowCloseBehavior.KeepRunningWithTrayIcon)); + WindowCloseBehaviorSelector.AddItem( + CoreTools.Translate("Keep running in the background, without a system tray icon"), + nameof(CoreSettings.WindowCloseBehavior.KeepRunningWithoutTrayIcon)); + // macOS always keeps the app running when its window closes, so quitting is not a choice there. + if (!OperatingSystem.IsMacOS()) + WindowCloseBehaviorSelector.AddItem( + CoreTools.Translate("Quit UniGetUI"), + nameof(CoreSettings.WindowCloseBehavior.Quit)); + WindowCloseBehaviorSelector.Text = CoreTools.Translate("When the main window is closed:"); + WindowCloseBehaviorSelector.ShowAddedItems( + () => GetShownWindowCloseBehavior().ToString(), + value => CoreSettings.SetWindowCloseBehavior(Enum.Parse(value))); + WindowCloseBehaviorSelector.ValueChanged += (_, _) => UpdateWindowCloseBehaviorDescription(); + UpdateWindowCloseBehaviorDescription(); + NavigationOrderCard.Description = CoreTools.Translate( "Drag the pages in the navigation menu, or press {0} on a focused page, to reorder them. Keyboard shortcuts follow the same order.", OperatingSystem.IsMacOS() ? "Option+Up/Down" : "Alt+Up/Down"); NavigationOrderCard.Click += (_, _) => MainWindow.Instance?.ResetNavigationOrder(); } + + // A "Quit" saved on macOS (by an older version's tray checkbox) already behaves as a hidden icon there. + private static CoreSettings.WindowCloseBehavior GetShownWindowCloseBehavior() + { + var behavior = CoreSettings.GetWindowCloseBehavior(); + return OperatingSystem.IsMacOS() && behavior is CoreSettings.WindowCloseBehavior.Quit + ? CoreSettings.WindowCloseBehavior.KeepRunningWithoutTrayIcon + : behavior; + } + + private void UpdateWindowCloseBehaviorDescription() + { + WindowCloseBehaviorSelector.Description = GetShownWindowCloseBehavior() switch + { + CoreSettings.WindowCloseBehavior.KeepRunningWithoutTrayIcon when OperatingSystem.IsLinux() => + CoreTools.Translate("UniGetUI keeps checking for updates. To reopen it, launch UniGetUI again. To exit, use More > Quit UniGetUI in the navigation menu.") + + " " + CoreTools.Translate("Notifications are not available on Linux, so you will not be told when updates are found."), + CoreSettings.WindowCloseBehavior.KeepRunningWithoutTrayIcon => + CoreTools.Translate("UniGetUI keeps checking for updates. To reopen it, launch UniGetUI again. To exit, use More > Quit UniGetUI in the navigation menu."), + CoreSettings.WindowCloseBehavior.Quit => + CoreTools.Translate("UniGetUI stops checking for updates and sending notifications until you open it again."), + _ => null, + }; + } } diff --git a/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/Notifications.axaml b/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/Notifications.axaml index 968119aa91..2748cf5a73 100644 --- a/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/Notifications.axaml +++ b/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/Notifications.axaml @@ -19,8 +19,10 @@ Margin="44,32,4,8" automation:AutomationProperties.HeadingLevel="2"/> - - + + GetWindowCloseBehavior() is WindowCloseBehavior.KeepRunningWithTrayIcon; + + // Notifications depend on the process staying alive after the window closes, not on the + // tray icon, so hiding the icon alone leaves them enabled. public static bool AreNotificationsDisabled() => Get(K.DisableSystemTray) || Get(K.DisableNotifications); diff --git a/src/UniGetUI.Core.Settings/SettingsEngine_Names.cs b/src/UniGetUI.Core.Settings/SettingsEngine_Names.cs index 74916e6bd7..dc9685aae5 100644 --- a/src/UniGetUI.Core.Settings/SettingsEngine_Names.cs +++ b/src/UniGetUI.Core.Settings/SettingsEngine_Names.cs @@ -31,6 +31,8 @@ public enum K TransferredOldSettings, DisableApi, DisableSystemTray, + HideSystemTrayIcon, + ShownHiddenTrayIconNotice, MaintainSuccessfulInstalls, AutomaticallyUpdatePackages, DisableAUPOnMeteredConnections, @@ -175,6 +177,8 @@ public static string ResolveKey(K key) K.TransferredOldSettings => "TransferredOldSettings", K.DisableApi => "DisableApi", K.DisableSystemTray => "DisableSystemTray", + K.HideSystemTrayIcon => "HideSystemTrayIcon", + K.ShownHiddenTrayIconNotice => "ShownHiddenTrayIconNotice", K.MaintainSuccessfulInstalls => "MaintainSuccessfulInstalls", K.AutomaticallyUpdatePackages => "AutomaticallyUpdatePackages", K.DisableAUPOnMeteredConnections => "DisableAUPOnMeteredConnections", diff --git a/src/UniGetUI.Tui/Views/Pages/SettingsPage.cs b/src/UniGetUI.Tui/Views/Pages/SettingsPage.cs index 9666d6752f..0167acdc49 100644 --- a/src/UniGetUI.Tui/Views/Pages/SettingsPage.cs +++ b/src/UniGetUI.Tui/Views/Pages/SettingsPage.cs @@ -317,7 +317,14 @@ private void BuildInterface() Check("Use the system UI font" + _desktopOnly, Settings.K.UseSystemUIFont); Choice("Navigation menu" + _desktopOnly, Settings.K.NavMenuMode, [new(CoreTools.Translate("Automatic"), "auto"), new(CoreTools.Translate("Docked"), "docked"), new(CoreTools.Translate("Overlay"), "overlay")]); - Check("Close UniGetUI to the system tray" + _desktopOnly, Settings.K.DisableSystemTray, inverted: true); + _form.AddChoice(CoreTools.Translate("When the main window is closed:") + _desktopOnly, + [ + new(CoreTools.Translate("Keep running in the background, with a system tray icon"), nameof(Settings.WindowCloseBehavior.KeepRunningWithTrayIcon)), + new(CoreTools.Translate("Keep running in the background, without a system tray icon"), nameof(Settings.WindowCloseBehavior.KeepRunningWithoutTrayIcon)), + new(CoreTools.Translate("Quit UniGetUI"), nameof(Settings.WindowCloseBehavior.Quit)), + ], + () => Settings.GetWindowCloseBehavior().ToString(), + v => Settings.SetWindowCloseBehavior(Enum.Parse(v))); Check("Show package icons on package lists" + _desktopOnly, Settings.K.DisableIconsOnPackageLists, inverted: true); Check("Show illustrations on empty package lists" + _desktopOnly, Settings.K.DisablePackageIllustrations, inverted: true); Check("Use software rendering automatically on hosts without a GPU" + _desktopOnly, Settings.K.DisableAutoSoftwareRenderingOnGpuLessHosts, inverted: true); From da471ab214ff2fb61d6eef60e1a3dd30a008b4c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Moreau?= Date: Tue, 27 Oct 2026 12:00:30 -0400 Subject: [PATCH 2/2] Use two checkboxes for background mode and the tray icon Replace the "When the main window is closed:" dropdown with two separate settings, as the issue asked: - "Keep UniGetUI running in the background when its window is closed", stored in the existing DisableSystemTray flag. - "Show the UniGetUI icon in the system tray", stored in HideSystemTrayIcon. It is greyed out while closing the window quits, since the icon has always been hidden then. Each checkbox explains its unchecked state: how to reopen and quit UniGetUI without an icon (plus a note that Linux has no notifications), or that update checks and notifications stop when closing quits. Co-Authored-By: Claude Opus 5.5 --- src/Languages/lang_en.json | 7 +-- .../Infrastructure/SettingsSearchIndex.cs | 3 +- .../Views/Controls/Settings/ComboboxCard.cs | 15 ++--- .../Pages/SettingsPages/Interface_P.axaml | 14 ++++- .../Pages/SettingsPages/Interface_P.axaml.cs | 56 +++++++------------ .../SettingsTest.cs | 46 +++++---------- .../SettingsEngine_Extras.cs | 29 +--------- src/UniGetUI.Tui/Views/Pages/SettingsPage.cs | 13 ++--- 8 files changed, 65 insertions(+), 118 deletions(-) diff --git a/src/Languages/lang_en.json b/src/Languages/lang_en.json index b2e34314c1..4e9f35be1c 100644 --- a/src/Languages/lang_en.json +++ b/src/Languages/lang_en.json @@ -2005,10 +2005,9 @@ "UniGetUI cannot update itself from a read-only location.": "UniGetUI cannot update itself from a read-only location.", "UniGetUI is running from {0}, which cannot be modified, for example a mounted disk image. Move UniGetUI to the Applications folder, open it from there, and try again.": "UniGetUI is running from {0}, which cannot be modified, for example a mounted disk image. Move UniGetUI to the Applications folder, open it from there, and try again.", "Copy link": "Copy link", - "When the main window is closed:": "When the main window is closed:", - "Keep running in the background, with a system tray icon": "Keep running in the background, with a system tray icon", - "Keep running in the background, without a system tray icon": "Keep running in the background, without a system tray icon", - "UniGetUI keeps checking for updates. To reopen it, launch UniGetUI again. To exit, use More > Quit UniGetUI in the navigation menu.": "UniGetUI keeps checking for updates. To reopen it, launch UniGetUI again. To exit, use More > Quit UniGetUI in the navigation menu.", + "Keep UniGetUI running in the background when its window is closed": "Keep UniGetUI running in the background when its window is closed", + "Show the UniGetUI icon in the system tray": "Show the UniGetUI icon in the system tray", + "To reopen UniGetUI, launch it again. To exit, use More > Quit UniGetUI in the navigation menu.": "To reopen UniGetUI, launch it again. To exit, use More > Quit UniGetUI in the navigation menu.", "Notifications are not available on Linux, so you will not be told when updates are found.": "Notifications are not available on Linux, so you will not be told when updates are found.", "UniGetUI stops checking for updates and sending notifications until you open it again.": "UniGetUI stops checking for updates and sending notifications until you open it again.", "UniGetUI is still running": "UniGetUI is still running", diff --git a/src/UniGetUI.Avalonia/Infrastructure/SettingsSearchIndex.cs b/src/UniGetUI.Avalonia/Infrastructure/SettingsSearchIndex.cs index b73bc79bd3..d5d978fbd2 100644 --- a/src/UniGetUI.Avalonia/Infrastructure/SettingsSearchIndex.cs +++ b/src/UniGetUI.Avalonia/Infrastructure/SettingsSearchIndex.cs @@ -56,7 +56,8 @@ private sealed record Entry( new("Navigation menu:", ["sidebar", "nav menu", "navigation", "layout", "docked", "overlay"], typeof(Interface_P), "NavMenuModeSelector"), new("Navigation menu order", ["sidebar", "nav menu", "navigation", "order", "reorder", "drag", "tabs", "pages", "shortcuts"], typeof(Interface_P), "NavigationOrderCard"), new("Automatically switch to software rendering when Windows has no hardware GPU", ["rendering", "gpu", "software rendering"], typeof(Interface_P), "InterfaceRenderingCard"), - new("When the main window is closed:", ["system tray", "tray icon", "hide icon", "background", "minimize to tray", "close", "quit"], typeof(Interface_P), "WindowCloseBehaviorSelector"), + new("Keep UniGetUI running in the background when its window is closed", ["system tray", "background", "minimize to tray", "close", "quit"], typeof(Interface_P), "DisableSystemTrayCard"), + new("Show the UniGetUI icon in the system tray", ["system tray", "tray icon", "hide icon", "notification area"], typeof(Interface_P), "SystemTrayIconCard"), new("Manage UniGetUI autostart behaviour", ["autostart", "run at login", "startup"], typeof(Interface_P), "EditAutostartSettings"), new("Show package icons on package lists", ["package icons"], typeof(Interface_P), "InterfacePackageListsCard"), new("Show illustrations on package lists", ["illustrations", "package illustrations"], typeof(Interface_P), "PackageIllustrationsCard"), diff --git a/src/UniGetUI.Avalonia/Views/Controls/Settings/ComboboxCard.cs b/src/UniGetUI.Avalonia/Views/Controls/Settings/ComboboxCard.cs index f5871d21e1..0dcc120c9d 100644 --- a/src/UniGetUI.Avalonia/Views/Controls/Settings/ComboboxCard.cs +++ b/src/UniGetUI.Avalonia/Views/Controls/Settings/ComboboxCard.cs @@ -60,17 +60,11 @@ public void AddItem(string name, string value, bool translate) _inverted_val_ref.Add(value, name); } - public void ShowAddedItems() => ShowAddedItems( - () => CoreSettings.GetValue(settings_name), - value => CoreSettings.SetValue(settings_name, value)); - - /// Shows the added items, reading and writing the selection through the given - /// accessors instead of a single string setting. - public void ShowAddedItems(Func readValue, Action writeValue) + public void ShowAddedItems() { try { - string savedItem = readValue(); + string savedItem = CoreSettings.GetValue(settings_name); _combobox.SelectedIndex = _elements.IndexOf(_inverted_val_ref[savedItem]); } catch @@ -82,7 +76,10 @@ public void ShowAddedItems(Func readValue, Action writeValue) try { string selectedName = _combobox.SelectedItem?.ToString() ?? ""; - writeValue(_values_ref[selectedName]); + CoreSettings.SetValue( + settings_name, + _values_ref[selectedName] + ); ValueChanged?.Invoke(this, EventArgs.Empty); var cmd = ValueChangedCommand; if (cmd?.CanExecute(null) == true) diff --git a/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/Interface_P.axaml b/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/Interface_P.axaml index 355fad1041..5c9f82c584 100644 --- a/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/Interface_P.axaml +++ b/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/Interface_P.axaml @@ -72,10 +72,20 @@ Margin="44,32,4,8" automation:AutomationProperties.HeadingLevel="2"/> - + + MainWindow.Instance?.RefreshNavigationMode(); - WindowCloseBehaviorSelector.AddItem( - CoreTools.Translate("Keep running in the background, with a system tray icon"), - nameof(CoreSettings.WindowCloseBehavior.KeepRunningWithTrayIcon)); - WindowCloseBehaviorSelector.AddItem( - CoreTools.Translate("Keep running in the background, without a system tray icon"), - nameof(CoreSettings.WindowCloseBehavior.KeepRunningWithoutTrayIcon)); - // macOS always keeps the app running when its window closes, so quitting is not a choice there. - if (!OperatingSystem.IsMacOS()) - WindowCloseBehaviorSelector.AddItem( - CoreTools.Translate("Quit UniGetUI"), - nameof(CoreSettings.WindowCloseBehavior.Quit)); - WindowCloseBehaviorSelector.Text = CoreTools.Translate("When the main window is closed:"); - WindowCloseBehaviorSelector.ShowAddedItems( - () => GetShownWindowCloseBehavior().ToString(), - value => CoreSettings.SetWindowCloseBehavior(Enum.Parse(value))); - WindowCloseBehaviorSelector.ValueChanged += (_, _) => UpdateWindowCloseBehaviorDescription(); - UpdateWindowCloseBehaviorDescription(); + DisableSystemTrayCard.StateChanged += (_, _) => UpdateSystemTrayCards(); + SystemTrayIconCard.StateChanged += (_, _) => UpdateSystemTrayCards(); + UpdateSystemTrayCards(); NavigationOrderCard.Description = CoreTools.Translate( "Drag the pages in the navigation menu, or press {0} on a focused page, to reorder them. Keyboard shortcuts follow the same order.", @@ -85,27 +71,25 @@ public Interface_P() NavigationOrderCard.Click += (_, _) => MainWindow.Instance?.ResetNavigationOrder(); } - // A "Quit" saved on macOS (by an older version's tray checkbox) already behaves as a hidden icon there. - private static CoreSettings.WindowCloseBehavior GetShownWindowCloseBehavior() + // The icon only exists while UniGetUI runs in the background, so its checkbox follows the first one, + // and each card explains what its unchecked state means for the user. + private void UpdateSystemTrayCards() { - var behavior = CoreSettings.GetWindowCloseBehavior(); - return OperatingSystem.IsMacOS() && behavior is CoreSettings.WindowCloseBehavior.Quit - ? CoreSettings.WindowCloseBehavior.KeepRunningWithoutTrayIcon - : behavior; - } + bool keepsRunning = !CoreSettings.Get(CoreSettings.K.DisableSystemTray); + bool iconHidden = CoreSettings.Get(CoreSettings.K.HideSystemTrayIcon); - private void UpdateWindowCloseBehaviorDescription() - { - WindowCloseBehaviorSelector.Description = GetShownWindowCloseBehavior() switch + DisableSystemTrayCard.WarningText = keepsRunning + ? "" + : CoreTools.Translate("UniGetUI stops checking for updates and sending notifications until you open it again."); + + SystemTrayIconCard.IsEnabled = keepsRunning; + string iconHint = ""; + if (keepsRunning && iconHidden) { - CoreSettings.WindowCloseBehavior.KeepRunningWithoutTrayIcon when OperatingSystem.IsLinux() => - CoreTools.Translate("UniGetUI keeps checking for updates. To reopen it, launch UniGetUI again. To exit, use More > Quit UniGetUI in the navigation menu.") - + " " + CoreTools.Translate("Notifications are not available on Linux, so you will not be told when updates are found."), - CoreSettings.WindowCloseBehavior.KeepRunningWithoutTrayIcon => - CoreTools.Translate("UniGetUI keeps checking for updates. To reopen it, launch UniGetUI again. To exit, use More > Quit UniGetUI in the navigation menu."), - CoreSettings.WindowCloseBehavior.Quit => - CoreTools.Translate("UniGetUI stops checking for updates and sending notifications until you open it again."), - _ => null, - }; + iconHint = CoreTools.Translate("To reopen UniGetUI, launch it again. To exit, use More > Quit UniGetUI in the navigation menu."); + if (OperatingSystem.IsLinux()) + iconHint += " " + CoreTools.Translate("Notifications are not available on Linux, so you will not be told when updates are found."); + } + SystemTrayIconCard.WarningText = iconHint; } } diff --git a/src/UniGetUI.Core.Settings.Tests/SettingsTest.cs b/src/UniGetUI.Core.Settings.Tests/SettingsTest.cs index e0161b8b83..c6d99db894 100644 --- a/src/UniGetUI.Core.Settings.Tests/SettingsTest.cs +++ b/src/UniGetUI.Core.Settings.Tests/SettingsTest.cs @@ -93,56 +93,38 @@ public void TestExistingSettingsMigrateToNewDirectory() } [Theory] - [InlineData(false, false, Settings.WindowCloseBehavior.KeepRunningWithTrayIcon)] - [InlineData(false, true, Settings.WindowCloseBehavior.KeepRunningWithoutTrayIcon)] - [InlineData(true, false, Settings.WindowCloseBehavior.Quit)] - [InlineData(true, true, Settings.WindowCloseBehavior.Quit)] - public void WindowCloseBehaviorIsReadFromTheStoredFlags( + [InlineData(false, false, true)] + [InlineData(false, true, false)] + [InlineData(true, false, false)] + [InlineData(true, true, false)] + public void TrayIconShowsOnlyWhileRunningInTheBackgroundAndNotHidden( bool disableSystemTray, bool hideSystemTrayIcon, - Settings.WindowCloseBehavior expected) + bool expectedVisible) { Settings.Set(Settings.K.DisableSystemTray, disableSystemTray); Settings.Set(Settings.K.HideSystemTrayIcon, hideSystemTrayIcon); - Assert.Equal(expected, Settings.GetWindowCloseBehavior()); - Assert.Equal( - expected is Settings.WindowCloseBehavior.KeepRunningWithTrayIcon, - Settings.IsSystemTrayIconVisible() - ); - } - - [Theory] - [InlineData(Settings.WindowCloseBehavior.KeepRunningWithTrayIcon, false, false)] - [InlineData(Settings.WindowCloseBehavior.KeepRunningWithoutTrayIcon, false, true)] - [InlineData(Settings.WindowCloseBehavior.Quit, true, false)] - public void SetWindowCloseBehaviorWritesBothFlags( - Settings.WindowCloseBehavior behavior, - bool disableSystemTray, - bool hideSystemTrayIcon) - { - Settings.Set(Settings.K.DisableSystemTray, !disableSystemTray); - Settings.Set(Settings.K.HideSystemTrayIcon, !hideSystemTrayIcon); - - Settings.SetWindowCloseBehavior(behavior); + Assert.Equal(expectedVisible, Settings.IsSystemTrayIconVisible()); - Assert.Equal(disableSystemTray, Settings.Get(Settings.K.DisableSystemTray)); - Assert.Equal(hideSystemTrayIcon, Settings.Get(Settings.K.HideSystemTrayIcon)); - Assert.Equal(behavior, Settings.GetWindowCloseBehavior()); + Settings.Set(Settings.K.DisableSystemTray, false); + Settings.Set(Settings.K.HideSystemTrayIcon, false); } [Fact] public void HidingTheTrayIconKeepsNotificationsEnabled() { Settings.Set(Settings.K.DisableNotifications, false); + Settings.Set(Settings.K.DisableSystemTray, false); - Settings.SetWindowCloseBehavior(Settings.WindowCloseBehavior.KeepRunningWithoutTrayIcon); + Settings.Set(Settings.K.HideSystemTrayIcon, true); Assert.False(Settings.AreNotificationsDisabled()); - Settings.SetWindowCloseBehavior(Settings.WindowCloseBehavior.Quit); + Settings.Set(Settings.K.DisableSystemTray, true); Assert.True(Settings.AreNotificationsDisabled()); - Settings.SetWindowCloseBehavior(Settings.WindowCloseBehavior.KeepRunningWithTrayIcon); + Settings.Set(Settings.K.DisableSystemTray, false); + Settings.Set(Settings.K.HideSystemTrayIcon, false); } [Theory] diff --git a/src/UniGetUI.Core.Settings/SettingsEngine_Extras.cs b/src/UniGetUI.Core.Settings/SettingsEngine_Extras.cs index 5048546838..465dc4b826 100644 --- a/src/UniGetUI.Core.Settings/SettingsEngine_Extras.cs +++ b/src/UniGetUI.Core.Settings/SettingsEngine_Extras.cs @@ -12,33 +12,10 @@ public partial class Settings * */ - public enum WindowCloseBehavior - { - KeepRunningWithTrayIcon, - KeepRunningWithoutTrayIcon, - Quit, - } - - // Stored as two flags so that "Quit" keeps reading DisableSystemTray exactly as before, and a - // version that does not know HideSystemTrayIcon simply shows the icon again. - public static WindowCloseBehavior GetWindowCloseBehavior() - { - if (Get(K.DisableSystemTray)) - return WindowCloseBehavior.Quit; - - return Get(K.HideSystemTrayIcon) - ? WindowCloseBehavior.KeepRunningWithoutTrayIcon - : WindowCloseBehavior.KeepRunningWithTrayIcon; - } - - public static void SetWindowCloseBehavior(WindowCloseBehavior behavior) - { - Set(K.DisableSystemTray, behavior is WindowCloseBehavior.Quit); - Set(K.HideSystemTrayIcon, behavior is WindowCloseBehavior.KeepRunningWithoutTrayIcon); - } - + // DisableSystemTray means closing the window quits, which has always hidden the icon too. + // HideSystemTrayIcon only hides the icon while UniGetUI keeps running in the background. public static bool IsSystemTrayIconVisible() => - GetWindowCloseBehavior() is WindowCloseBehavior.KeepRunningWithTrayIcon; + !Get(K.DisableSystemTray) && !Get(K.HideSystemTrayIcon); // Notifications depend on the process staying alive after the window closes, not on the // tray icon, so hiding the icon alone leaves them enabled. diff --git a/src/UniGetUI.Tui/Views/Pages/SettingsPage.cs b/src/UniGetUI.Tui/Views/Pages/SettingsPage.cs index 0167acdc49..e766998dae 100644 --- a/src/UniGetUI.Tui/Views/Pages/SettingsPage.cs +++ b/src/UniGetUI.Tui/Views/Pages/SettingsPage.cs @@ -317,14 +317,11 @@ private void BuildInterface() Check("Use the system UI font" + _desktopOnly, Settings.K.UseSystemUIFont); Choice("Navigation menu" + _desktopOnly, Settings.K.NavMenuMode, [new(CoreTools.Translate("Automatic"), "auto"), new(CoreTools.Translate("Docked"), "docked"), new(CoreTools.Translate("Overlay"), "overlay")]); - _form.AddChoice(CoreTools.Translate("When the main window is closed:") + _desktopOnly, - [ - new(CoreTools.Translate("Keep running in the background, with a system tray icon"), nameof(Settings.WindowCloseBehavior.KeepRunningWithTrayIcon)), - new(CoreTools.Translate("Keep running in the background, without a system tray icon"), nameof(Settings.WindowCloseBehavior.KeepRunningWithoutTrayIcon)), - new(CoreTools.Translate("Quit UniGetUI"), nameof(Settings.WindowCloseBehavior.Quit)), - ], - () => Settings.GetWindowCloseBehavior().ToString(), - v => Settings.SetWindowCloseBehavior(Enum.Parse(v))); + Check("Keep UniGetUI running in the background when its window is closed" + _desktopOnly, Settings.K.DisableSystemTray, inverted: true); + _form.AddCheck(CoreTools.Translate("Show the UniGetUI icon in the system tray") + _desktopOnly, + () => !Settings.Get(Settings.K.HideSystemTrayIcon), + v => Settings.Set(Settings.K.HideSystemTrayIcon, !v), + enabled: () => !Settings.Get(Settings.K.DisableSystemTray)); Check("Show package icons on package lists" + _desktopOnly, Settings.K.DisableIconsOnPackageLists, inverted: true); Check("Show illustrations on empty package lists" + _desktopOnly, Settings.K.DisablePackageIllustrations, inverted: true); Check("Use software rendering automatically on hosts without a GPU" + _desktopOnly, Settings.K.DisableAutoSoftwareRenderingOnGpuLessHosts, inverted: true);