Files
omni-notify/App.xaml.cs
home-PC 09b68ada48 feat(tray): 支持静默启动和托盘单击切换
启动应用时不再自动打开主界面,方便配合开机自启在后台运行。

托盘左键单击现在会根据主界面显示状态进行打开或隐藏,保留托盘菜单和二次启动唤起主界面的行为。
2026-05-19 02:03:25 +08:00

162 lines
4.8 KiB
C#

using System.IO;
using System.Threading;
using System.Windows;
using Forms = System.Windows.Forms;
namespace OmniNotify;
public partial class App : System.Windows.Application
{
private const string SingleInstanceMutexName = @"Local\OmniNotify.SingleInstance";
private const string ActivateEventName = @"Local\OmniNotify.Activate";
private Forms.NotifyIcon? _notifyIcon;
private AppStore? _store;
private LocalHttpServer? _server;
private Mutex? _singleInstanceMutex;
private bool _ownsSingleInstanceMutex;
private EventWaitHandle? _activateEvent;
private RegisteredWaitHandle? _activateWait;
public AppState State { get; private set; } = new();
public NotificationRouter Router { get; private set; } = null!;
public PopupCoordinator PopupCoordinator { get; private set; } = null!;
public string ListenUrl => _server?.Url ?? "";
public string? ServerError => _server?.LastError;
protected override void OnStartup(StartupEventArgs e)
{
_singleInstanceMutex = new Mutex(true, SingleInstanceMutexName, out var createdNew);
_ownsSingleInstanceMutex = createdNew;
_activateEvent = new EventWaitHandle(false, EventResetMode.AutoReset, ActivateEventName);
if (!createdNew)
{
_activateEvent.Set();
Shutdown();
return;
}
RegisterActivationListener();
base.OnStartup(e);
_store = new AppStore();
State = _store.Load();
PopupCoordinator = new PopupCoordinator();
Router = new NotificationRouter(State, _store, PopupCoordinator);
Router.StateChanged += UpdateTrayMenu;
_server = new LocalHttpServer(Router);
_server.Start(State.Settings.LocalPort);
CreateTrayIcon();
}
public void SaveState()
{
_store?.Save(State);
UpdateTrayMenu();
}
public void ShowMainWindow()
{
if (MainWindow is null)
{
MainWindow = new MainWindow();
}
MainWindow.Show();
MainWindow.WindowState = WindowState.Normal;
MainWindow.Activate();
}
public void ToggleMainWindow()
{
if (MainWindow?.IsVisible == true)
{
MainWindow.Hide();
return;
}
ShowMainWindow();
}
private void RegisterActivationListener()
{
if (_activateEvent is null)
{
return;
}
_activateWait = ThreadPool.RegisterWaitForSingleObject(
_activateEvent,
(_, _) => Dispatcher.Invoke(ShowMainWindow),
null,
Timeout.InfiniteTimeSpan,
false);
}
private void CreateTrayIcon()
{
_notifyIcon = new Forms.NotifyIcon
{
Icon = System.Drawing.Icon.ExtractAssociatedIcon(Environment.ProcessPath ?? "") ?? System.Drawing.SystemIcons.Application,
Text = "Omni-Notify",
Visible = true
};
_notifyIcon.MouseClick += (_, args) =>
{
if (args.Button == Forms.MouseButtons.Left)
{
Dispatcher.Invoke(ToggleMainWindow);
}
};
UpdateTrayMenu();
}
private void UpdateTrayMenu()
{
if (_notifyIcon is null)
{
return;
}
var menu = new Forms.ContextMenuStrip();
menu.Items.Add("打开主控制面板", null, (_, _) => Dispatcher.Invoke(ShowMainWindow));
menu.Items.Add("全局设置", null, (_, _) => Dispatcher.Invoke(() =>
{
ShowMainWindow();
if (MainWindow is MainWindow window)
{
window.FocusSettingsTab();
}
}));
menu.Items.Add(State.Settings.DndEnabled ? "关闭免打扰" : "开启免打扰", null, (_, _) => Dispatcher.Invoke(() =>
{
State.Settings.DndEnabled = !State.Settings.DndEnabled;
SaveState();
}));
var resetBreaker = menu.Items.Add("解除熔断", null, (_, _) => Dispatcher.Invoke(() => Router.ResetCircuitBreaker()));
resetBreaker.Enabled = State.Settings.CircuitBreakerOpen;
resetBreaker.ForeColor = State.Settings.CircuitBreakerOpen ? System.Drawing.Color.Firebrick : System.Drawing.SystemColors.ControlText;
menu.Items.Add(new Forms.ToolStripSeparator());
menu.Items.Add("退出软件", null, (_, _) => Dispatcher.Invoke(Shutdown));
_notifyIcon.ContextMenuStrip = menu;
}
protected override void OnExit(ExitEventArgs e)
{
_activateWait?.Unregister(null);
_activateEvent?.Dispose();
if (_ownsSingleInstanceMutex)
{
_singleInstanceMutex?.ReleaseMutex();
}
_singleInstanceMutex?.Dispose();
_notifyIcon?.Dispose();
_server?.Dispose();
_store?.Save(State);
base.OnExit(e);
}
}