diff --git a/Directory.Packages.props b/Directory.Packages.props
index b800ef26..710751ef 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -51,6 +51,7 @@
+
diff --git a/Wino.Core.Domain/Interfaces/ISystemTrayService.cs b/Wino.Core.Domain/Interfaces/ISystemTrayService.cs
new file mode 100644
index 00000000..7a67953c
--- /dev/null
+++ b/Wino.Core.Domain/Interfaces/ISystemTrayService.cs
@@ -0,0 +1,36 @@
+using System;
+
+namespace Wino.Core.Domain.Interfaces;
+
+public interface ISystemTrayService
+{
+ ///
+ /// Initializes the system tray icon.
+ ///
+ void Initialize();
+
+ ///
+ /// Shows the system tray icon.
+ ///
+ void Show();
+
+ ///
+ /// Hides the system tray icon.
+ ///
+ void Hide();
+
+ ///
+ /// Event fired when the tray icon is double-clicked.
+ ///
+ event EventHandler? TrayIconDoubleClicked;
+
+ ///
+ /// Gets whether the tray icon is currently minimized.
+ ///
+ bool IsMinimizedToTray { get; }
+
+ ///
+ /// Disposes of the system tray resources.
+ ///
+ void Dispose();
+}
\ No newline at end of file
diff --git a/Wino.Mail.WinUI/App.xaml.cs b/Wino.Mail.WinUI/App.xaml.cs
index 268e044b..a2d97c06 100644
--- a/Wino.Mail.WinUI/App.xaml.cs
+++ b/Wino.Mail.WinUI/App.xaml.cs
@@ -8,6 +8,7 @@ using Wino.Core.WinUI;
using Wino.Core.WinUI.Interfaces;
using Wino.Mail.Services;
using Wino.Mail.ViewModels;
+using Wino.Mail.WinUI.Services;
using Wino.Messaging.Server;
using Wino.Services;
namespace Wino.Mail.WinUI;
@@ -33,6 +34,7 @@ public partial class App : WinoApplication, IRecipient();
services.AddTransient();
services.AddSingleton();
+ services.AddSingleton();
}
private void RegisterViewModels(IServiceCollection services)
@@ -91,6 +93,14 @@ public partial class App : WinoApplication, IRecipient();
+ if (systemTrayService != null)
+ {
+ systemTrayService.Initialize();
+ systemTrayService.Show(); // Explicitly show the tray icon
+ }
+
if (MainWindow is not IWinoShellWindow shellWindow) throw new ArgumentException("MainWindow must implement IWinoShellWindow");
shellWindow.HandleAppActivation(args);
diff --git a/Wino.Mail.WinUI/Assets/Wino_Icon.ico b/Wino.Mail.WinUI/Assets/Wino_Icon.ico
new file mode 100644
index 00000000..be12c893
Binary files /dev/null and b/Wino.Mail.WinUI/Assets/Wino_Icon.ico differ
diff --git a/Wino.Mail.WinUI/Services/SystemTrayService.cs b/Wino.Mail.WinUI/Services/SystemTrayService.cs
new file mode 100644
index 00000000..ca82ac8d
--- /dev/null
+++ b/Wino.Mail.WinUI/Services/SystemTrayService.cs
@@ -0,0 +1,196 @@
+using System;
+using System.Windows.Input;
+using H.NotifyIcon;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Media.Imaging;
+using Wino.Core.Domain.Interfaces;
+using Microsoft.Extensions.DependencyInjection;
+using Wino.Core.WinUI;
+
+namespace Wino.Mail.WinUI.Services;
+
+public class SystemTrayService : ISystemTrayService
+{
+ private TaskbarIcon? _taskbarIcon;
+ private bool _isDisposed;
+ private bool _isMinimizedToTray;
+
+ public bool IsMinimizedToTray => _isMinimizedToTray;
+
+ public event EventHandler? TrayIconDoubleClicked;
+
+ public void Initialize()
+ {
+ if (_taskbarIcon != null) return;
+
+ try
+ {
+ System.Diagnostics.Debug.WriteLine("Starting system tray initialization...");
+
+ // Create TaskbarIcon first
+ _taskbarIcon = new TaskbarIcon();
+
+ // Set basic properties first
+ _taskbarIcon.ToolTipText = "Wino Mail";
+
+ // Configure the taskbar icon with icon loading
+ var iconUri = new Uri("ms-appx:///Assets/Wino_Icon.ico");
+ var bitmapImage = new BitmapImage(iconUri);
+ _taskbarIcon.IconSource = bitmapImage;
+ System.Diagnostics.Debug.WriteLine("Icon source set");
+
+ // Create context menu
+ var contextMenu = new MenuFlyout();
+
+ // Show Window menu item
+ var showMenuItem = new MenuFlyoutItem
+ {
+ Text = "Show Wino Mail",
+ Icon = new SymbolIcon(Symbol.Home)
+ };
+ showMenuItem.Click += ShowMenuItem_Click;
+ contextMenu.Items.Add(showMenuItem);
+ System.Diagnostics.Debug.WriteLine("Show menu item added");
+
+ // Separator
+ contextMenu.Items.Add(new MenuFlyoutSeparator());
+
+ // Exit menu item
+ var exitMenuItem = new MenuFlyoutItem
+ {
+ Text = "Exit",
+ Icon = new SymbolIcon(Symbol.Cancel)
+ };
+ exitMenuItem.Click += ExitMenuItem_Click;
+ contextMenu.Items.Add(exitMenuItem);
+ System.Diagnostics.Debug.WriteLine("Exit menu item added");
+
+ // Set context menu
+ _taskbarIcon.ContextFlyout = contextMenu;
+
+ // Handle double-click using the proper event
+ _taskbarIcon.LeftClickCommand = new RelayCommand(OnTrayIconLeftClick);
+
+ // Set visibility and create explicitly
+ _taskbarIcon.Visibility = Visibility.Visible;
+
+ // Try ForceCreate to ensure the icon is properly created in the system tray
+ _taskbarIcon.ForceCreate();
+ System.Diagnostics.Debug.WriteLine("System tray icon created and visible");
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"Failed to initialize system tray: {ex.Message}");
+ System.Diagnostics.Debug.WriteLine($"Stack trace: {ex.StackTrace}");
+ }
+ }
+
+ private void ShowMenuItem_Click(object sender, RoutedEventArgs e)
+ {
+ System.Diagnostics.Debug.WriteLine("Show menu item clicked");
+ TrayIconDoubleClicked?.Invoke(this, EventArgs.Empty);
+ }
+
+ private void ExitMenuItem_Click(object sender, RoutedEventArgs e)
+ {
+ System.Diagnostics.Debug.WriteLine("Exit menu item clicked");
+ ExitApplication();
+ }
+
+ private void OnTrayIconLeftClick()
+ {
+ System.Diagnostics.Debug.WriteLine("Tray icon left clicked");
+ TrayIconDoubleClicked?.Invoke(this, EventArgs.Empty);
+ }
+
+ public void Show()
+ {
+ if (_taskbarIcon != null)
+ {
+ try
+ {
+ _taskbarIcon.Visibility = Visibility.Visible;
+ _taskbarIcon.ForceCreate(); // Ensure the icon is properly created and visible
+ _isMinimizedToTray = true;
+ System.Diagnostics.Debug.WriteLine("System tray icon set to visible and force created");
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"Failed to show system tray icon: {ex.Message}");
+ }
+ }
+ else
+ {
+ System.Diagnostics.Debug.WriteLine("TaskbarIcon is null when trying to show");
+ }
+ }
+
+ public void Hide()
+ {
+ if (_taskbarIcon != null)
+ {
+ _taskbarIcon.Visibility = Visibility.Collapsed;
+ _isMinimizedToTray = false;
+ }
+ }
+
+ private void ExitApplication()
+ {
+ System.Diagnostics.Debug.WriteLine("Attempting to exit application...");
+
+ try
+ {
+ // Clean up the tray icon first
+ Dispose();
+
+ // Get the main window and close it properly
+ if (WinoApplication.MainWindow is ShellWindow shellWindow)
+ {
+ // Force close the window without minimizing to tray
+ shellWindow.ForceClose();
+ }
+ else
+ {
+ // Fallback to application exit
+ Application.Current.Exit();
+ }
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"Error during application exit: {ex.Message}");
+ // Force exit if normal exit fails
+ Environment.Exit(0);
+ }
+ }
+
+ public void Dispose()
+ {
+ if (_isDisposed) return;
+
+ _taskbarIcon?.Dispose();
+ _taskbarIcon = null;
+ _isDisposed = true;
+ }
+}
+
+// Simple RelayCommand implementation for the tray icon
+public class RelayCommand : ICommand
+{
+ private readonly Action _execute;
+ private readonly Func? _canExecute;
+
+ public RelayCommand(Action execute, Func? canExecute = null)
+ {
+ _execute = execute ?? throw new ArgumentNullException(nameof(execute));
+ _canExecute = canExecute;
+ }
+
+ public event EventHandler? CanExecuteChanged;
+
+ public bool CanExecute(object? parameter) => _canExecute?.Invoke() ?? true;
+
+ public void Execute(object? parameter) => _execute();
+
+ public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
+}
diff --git a/Wino.Mail.WinUI/ShellWindow.xaml.cs b/Wino.Mail.WinUI/ShellWindow.xaml.cs
index c03fa2e7..efb8c768 100644
--- a/Wino.Mail.WinUI/ShellWindow.xaml.cs
+++ b/Wino.Mail.WinUI/ShellWindow.xaml.cs
@@ -19,6 +19,7 @@ public sealed partial class ShellWindow : WindowEx, IWinoShellWindow, IRecipient
{
public IStatePersistanceService StatePersistanceService { get; } = WinoApplication.Current.Services.GetService() ?? throw new Exception("StatePersistanceService not registered in DI container.");
public IPreferencesService PreferencesService { get; } = WinoApplication.Current.Services.GetService() ?? throw new Exception("PreferencesService not registered in DI container.");
+ private readonly ISystemTrayService _systemTrayService;
public ShellWindow()
{
@@ -30,6 +31,17 @@ public sealed partial class ShellWindow : WindowEx, IWinoShellWindow, IRecipient
MinWidth = 420;
MinHeight = 420;
ConfigureTitleBar();
+
+ // Initialize system tray service
+ _systemTrayService = WinoApplication.Current.Services.GetService() ?? throw new Exception("SystemTrayService not registered in DI container.");
+ _systemTrayService.Initialize();
+ _systemTrayService.TrayIconDoubleClicked += OnTrayIconDoubleClicked;
+
+ // Handle window closing event to minimize to tray instead of closing
+ Closed += OnWindowClosed;
+
+ // Use the AppWindow.Closing event to handle the close request
+ AppWindow.Closing += OnAppWindowClosing;
}
private void ConfigureTitleBar()
@@ -119,4 +131,56 @@ public sealed partial class ShellWindow : WindowEx, IWinoShellWindow, IRecipient
}
});
}
+
+ private void OnAppWindowClosing(object sender, Microsoft.UI.Windowing.AppWindowClosingEventArgs e)
+ {
+ // Cancel the close and minimize to tray instead
+ e.Cancel = true;
+ MinimizeToTray();
+ }
+
+ private void OnWindowClosed(object sender, WindowEventArgs e)
+ {
+ // Clean up tray icon when window is actually closed
+ _systemTrayService?.Dispose();
+ }
+
+ private void MinimizeToTray()
+ {
+ // Hide the window and show tray icon
+ this.Hide();
+ _systemTrayService.Show();
+ }
+
+ private void OnTrayIconDoubleClicked(object? sender, EventArgs e)
+ {
+ // Restore the window from tray
+ RestoreFromTray();
+ }
+
+ private void RestoreFromTray()
+ {
+ if (_systemTrayService.IsMinimizedToTray)
+ {
+ // Show the window and hide tray icon
+ this.Show();
+ this.Activate();
+ _systemTrayService.Hide();
+ }
+ }
+
+ public void ForceClose()
+ {
+ // Unsubscribe from the closing event to avoid infinite loop
+ AppWindow.Closing -= OnAppWindowClosing;
+
+ // Clean up system tray
+ _systemTrayService?.Dispose();
+
+ // Close the window
+ this.Close();
+
+ // Exit the application
+ Application.Current.Exit();
+ }
}
diff --git a/Wino.Mail.WinUI/Wino.Mail.WinUI.csproj b/Wino.Mail.WinUI/Wino.Mail.WinUI.csproj
index 1b1f2a4f..666cec74 100644
--- a/Wino.Mail.WinUI/Wino.Mail.WinUI.csproj
+++ b/Wino.Mail.WinUI/Wino.Mail.WinUI.csproj
@@ -72,6 +72,7 @@
+
@@ -112,6 +113,7 @@
+