This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
using CursorLang.Agent.Interop;
|
||||
using CursorLang.Core.Interop;
|
||||
using CursorLang.Core.Services;
|
||||
|
||||
namespace CursorLang.Agent.Windows;
|
||||
|
||||
/// <summary>
|
||||
/// The icon in the notification area: the way to the settings window and the only way
|
||||
/// to quit the application.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The interop half is the same as it always was — the icon has never known anything
|
||||
/// about the framework. What changed is the menu: a WPF <c>ContextMenu</c> obeyed the
|
||||
/// theme and the language chosen in the settings, and a <c>TrackPopupMenuEx</c> menu is
|
||||
/// drawn by Windows in the system look. The language still reaches it, because the
|
||||
/// captions are ours; the theme does not, and that is the price of taking the rendering
|
||||
/// stack out of the background process.
|
||||
///
|
||||
/// The captions are read each time the menu is raised rather than once: the language is
|
||||
/// changed in the settings without a restart, and the menu is built on every click
|
||||
/// anyway — a menu costs nothing to build and is asked for rarely.
|
||||
/// </remarks>
|
||||
internal sealed class NativeTrayIcon : IDisposable
|
||||
{
|
||||
/// <summary>Windows shows it under the pointer. The name of the app says enough.</summary>
|
||||
private const string Tooltip = "CursorLang";
|
||||
|
||||
/// <summary>Distinguishes the icon among those of the same window; we have one.</summary>
|
||||
private const int IconId = 1;
|
||||
|
||||
private const int CommandSettings = 1;
|
||||
private const int CommandExit = 2;
|
||||
|
||||
private readonly AgentWindow _window;
|
||||
private readonly ILocalizationService _localization;
|
||||
|
||||
private IntPtr _icon;
|
||||
private bool _isInstalled;
|
||||
|
||||
internal NativeTrayIcon(AgentWindow window, ILocalizationService localization)
|
||||
{
|
||||
_window = window;
|
||||
_localization = localization;
|
||||
|
||||
_window.AddFilter(OnMessage);
|
||||
}
|
||||
|
||||
internal event EventHandler? OpenRequested;
|
||||
|
||||
internal event EventHandler? ExitRequested;
|
||||
|
||||
/// <summary>Whether the shell accepted the icon. Reported by the diagnostics.</summary>
|
||||
internal bool IsInstalled => _isInstalled;
|
||||
|
||||
internal bool Install()
|
||||
{
|
||||
if (_isInstalled)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
_icon = TrayIconNative.LoadApplicationIcon();
|
||||
_isInstalled = TrayIconNative.Add(_window.Handle, IconId, _icon, Tooltip);
|
||||
|
||||
return _isInstalled;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isInstalled)
|
||||
{
|
||||
TrayIconNative.Remove(_window.Handle, IconId);
|
||||
_isInstalled = false;
|
||||
}
|
||||
|
||||
TrayIconNative.ReleaseIcon(_icon);
|
||||
_icon = IntPtr.Zero;
|
||||
}
|
||||
|
||||
private bool OnMessage(uint message, IntPtr wParam, IntPtr lParam)
|
||||
{
|
||||
// Explorer has restarted and taken every icon down with it
|
||||
if (message == (uint)TrayIconNative.TaskbarCreatedMessage && _isInstalled)
|
||||
{
|
||||
TrayIconNative.Add(_window.Handle, IconId, _icon, Tooltip);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (message != TrayIconNative.CallbackMessage)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (TrayIconNative.NotificationOf(lParam))
|
||||
{
|
||||
case TrayIconNative.SelectNotification:
|
||||
case TrayIconNative.KeySelectNotification:
|
||||
OpenRequested?.Invoke(this, EventArgs.Empty);
|
||||
return true;
|
||||
|
||||
case TrayIconNative.ContextMenuNotification:
|
||||
ShowMenu(TrayIconNative.PointOf(wParam));
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Raises the menu of the icon where the pointer is.</summary>
|
||||
internal void ShowMenu(PopupWindowNative.Point at)
|
||||
{
|
||||
MenuNative.Item[] items =
|
||||
[
|
||||
new(CommandSettings, _localization["TrayMenuSettings"]),
|
||||
MenuNative.Item.Separator,
|
||||
new(CommandExit, _localization["TrayMenuExit"]),
|
||||
];
|
||||
|
||||
switch (MenuNative.Track(_window.Handle, at, items))
|
||||
{
|
||||
case CommandSettings:
|
||||
OpenRequested?.Invoke(this, EventArgs.Empty);
|
||||
break;
|
||||
|
||||
case CommandExit:
|
||||
ExitRequested?.Invoke(this, EventArgs.Empty);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user