diff --git a/src/WebhookServer.Gui/App.xaml.cs b/src/WebhookServer.Gui/App.xaml.cs index 9e28109..845e1e0 100644 --- a/src/WebhookServer.Gui/App.xaml.cs +++ b/src/WebhookServer.Gui/App.xaml.cs @@ -1,13 +1,5 @@ -using System.Configuration; -using System.Data; -using System.Windows; - namespace WebhookServer.Gui; -/// -/// Interaction logic for App.xaml -/// public partial class App : Application { } - diff --git a/src/WebhookServer.Gui/Converters/Converters.cs b/src/WebhookServer.Gui/Converters/Converters.cs index e09d260..e346830 100644 --- a/src/WebhookServer.Gui/Converters/Converters.cs +++ b/src/WebhookServer.Gui/Converters/Converters.cs @@ -1,6 +1,7 @@ using System.Globalization; using System.Windows.Data; -using System.Windows.Media; +using Brush = System.Windows.Media.Brush; +using Brushes = System.Windows.Media.Brushes; namespace WebhookServer.Gui.Converters; diff --git a/src/WebhookServer.Gui/GlobalUsings.cs b/src/WebhookServer.Gui/GlobalUsings.cs new file mode 100644 index 0000000..59cef05 --- /dev/null +++ b/src/WebhookServer.Gui/GlobalUsings.cs @@ -0,0 +1,15 @@ +// Enabling UseWindowsForms (for the system tray NotifyIcon) brings the WinForms +// namespace into scope, which conflicts with WPF for several common type names. +// Alias the most-used types to their WPF variants project-wide so existing code +// keeps compiling. Files that genuinely need a WinForms type import it explicitly +// (System.Windows.Forms.NotifyIcon etc. in Services/TrayIcon.cs). + +global using Application = System.Windows.Application; +global using MessageBox = System.Windows.MessageBox; +global using Clipboard = System.Windows.Clipboard; +global using TextBox = System.Windows.Controls.TextBox; +global using RadioButton = System.Windows.Controls.RadioButton; +global using MessageBoxButton = System.Windows.MessageBoxButton; +global using MessageBoxImage = System.Windows.MessageBoxImage; +global using MessageBoxResult = System.Windows.MessageBoxResult; +global using Binding = System.Windows.Data.Binding; diff --git a/src/WebhookServer.Gui/MainWindow.xaml.cs b/src/WebhookServer.Gui/MainWindow.xaml.cs index 3df0142..c825e1e 100644 --- a/src/WebhookServer.Gui/MainWindow.xaml.cs +++ b/src/WebhookServer.Gui/MainWindow.xaml.cs @@ -8,12 +8,37 @@ namespace WebhookServer.Gui; public partial class MainWindow : Window { + private readonly TrayIcon _tray; + private readonly MainViewModel _vm; + public MainWindow() { InitializeComponent(); - var vm = new MainViewModel(new AdminPipeClient()); - DataContext = vm; - Loaded += async (_, _) => await vm.RefreshCommand.ExecuteAsync(null); + _vm = new MainViewModel(new AdminPipeClient()); + DataContext = _vm; + + _tray = new TrayIcon( + resolveMainWindow: () => Application.Current.MainWindow, + restartServiceAsync: async () => await new AdminPipeClient().RestartListenerAsync()); + + Loaded += async (_, _) => await _vm.RefreshCommand.ExecuteAsync(null); + StateChanged += OnStateChanged; + Closed += (_, _) => _tray.Dispose(); + } + + private void OnStateChanged(object? sender, EventArgs e) + { + // Minimize-to-tray: hide the window when the user minimizes; restoring is + // via the tray icon's double-click or context menu. + if (WindowState == WindowState.Minimized) + { + Hide(); + ShowInTaskbar = false; + } + else + { + ShowInTaskbar = true; + } } private void OnLogTailChanged(object sender, TextChangedEventArgs e) diff --git a/src/WebhookServer.Gui/Services/TrayIcon.cs b/src/WebhookServer.Gui/Services/TrayIcon.cs new file mode 100644 index 0000000..7cedfa5 --- /dev/null +++ b/src/WebhookServer.Gui/Services/TrayIcon.cs @@ -0,0 +1,86 @@ +using System.Drawing; +using System.Runtime.Versioning; +using System.Windows; +using System.Windows.Forms; + +namespace WebhookServer.Gui.Services; + +/// +/// Minimal system tray icon using Windows Forms NotifyIcon. Owns a context menu +/// (Open / Restart service / Exit) and toggles the main window visibility on +/// double-click. Hide-to-tray on minimize is wired in MainWindow.xaml.cs. +/// +[SupportedOSPlatform("windows")] +public sealed class TrayIcon : IDisposable +{ + private readonly NotifyIcon _icon; + private readonly Func _resolveMainWindow; + private readonly Func _restartServiceAsync; + + public TrayIcon(Func resolveMainWindow, Func restartServiceAsync) + { + _resolveMainWindow = resolveMainWindow; + _restartServiceAsync = restartServiceAsync; + + _icon = new NotifyIcon + { + Icon = LoadEmbeddedIcon(), + Text = "Webhook Server", + Visible = true, + }; + _icon.DoubleClick += (_, _) => ShowMainWindow(); + _icon.ContextMenuStrip = BuildMenu(); + } + + private ContextMenuStrip BuildMenu() + { + var menu = new ContextMenuStrip(); + menu.Items.Add("&Open Webhook Server", null, (_, _) => ShowMainWindow()); + menu.Items.Add(new ToolStripSeparator()); + menu.Items.Add("&Restart service", null, async (_, _) => await _restartServiceAsync().ConfigureAwait(false)); + menu.Items.Add(new ToolStripSeparator()); + menu.Items.Add("E&xit", null, (_, _) => Application.Current.Shutdown()); + return menu; + } + + private void ShowMainWindow() + { + var w = _resolveMainWindow(); + if (w is null) return; + if (w.WindowState == WindowState.Minimized) w.WindowState = WindowState.Normal; + w.Show(); + w.Activate(); + w.Topmost = true; + w.Topmost = false; + } + + private static Icon LoadEmbeddedIcon() + { + // Pulled from the WPF Resource items in the csproj via the application + // pack URI. Falling back to SystemIcons keeps the tray usable if the + // resource is somehow missing. + try + { + var uri = new Uri("pack://application:,,,/webhook-server.ico", UriKind.Absolute); + using var stream = Application.GetResourceStream(uri).Stream; + return new Icon(stream); + } + catch + { + return SystemIcons.Application; + } + } + + public void ShowBalloon(string title, string message) + { + _icon.BalloonTipTitle = title; + _icon.BalloonTipText = message; + _icon.ShowBalloonTip(3000); + } + + public void Dispose() + { + _icon.Visible = false; + _icon.Dispose(); + } +} diff --git a/src/WebhookServer.Gui/WebhookServer.Gui.csproj b/src/WebhookServer.Gui/WebhookServer.Gui.csproj index 428feba..8308673 100644 --- a/src/WebhookServer.Gui/WebhookServer.Gui.csproj +++ b/src/WebhookServer.Gui/WebhookServer.Gui.csproj @@ -14,6 +14,7 @@ enable enable true + true ..\..\resources\webhook-server.ico Webhook Server