diff --git a/src/Languages/lang_en.json b/src/Languages/lang_en.json
index 8f57852cfd..35875c11b6 100644
--- a/src/Languages/lang_en.json
+++ b/src/Languages/lang_en.json
@@ -2012,5 +2012,13 @@
"{0} was not found.": "{0} was not found.",
"{0} was not found in {1}.": "{0} was not found in {1}.",
"The package link could not be opened": "The package link could not be opened",
- "A link to {0} was copied to the clipboard": "A link to {0} was copied to the clipboard"
+ "A link to {0} was copied to the clipboard": "A link to {0} was copied to the clipboard",
+ "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",
+ "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..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("Close UniGetUI to the system tray", ["system tray", "background", "minimize to tray"], typeof(Interface_P), "DisableSystemTrayCard"),
+ 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/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/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..5c9f82c584 100644
--- a/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/Interface_P.axaml
+++ b/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/Interface_P.axaml
@@ -74,10 +74,18 @@
+
+
MainWindow.Instance?.RefreshNavigationMode();
+ 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.",
OperatingSystem.IsMacOS() ? "Option+Up/Down" : "Alt+Up/Down");
NavigationOrderCard.Click += (_, _) => MainWindow.Instance?.ResetNavigationOrder();
}
+
+ // 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()
+ {
+ bool keepsRunning = !CoreSettings.Get(CoreSettings.K.DisableSystemTray);
+ bool iconHidden = CoreSettings.Get(CoreSettings.K.HideSystemTrayIcon);
+
+ 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)
+ {
+ 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.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"/>
-
-
+
+ !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.
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..e766998dae 100644
--- a/src/UniGetUI.Tui/Views/Pages/SettingsPage.cs
+++ b/src/UniGetUI.Tui/Views/Pages/SettingsPage.cs
@@ -317,7 +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")]);
- Check("Close UniGetUI to the system tray" + _desktopOnly, Settings.K.DisableSystemTray, inverted: true);
+ 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);