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