using CursorLang.Agent.Interop;
using CursorLang.Core.Interop;
using CursorLang.Core.Services;
namespace CursorLang.Agent.Windows;
///
/// The icon in the notification area: the way to the settings window and the only way
/// to quit the application.
///
///
/// 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 ContextMenu obeyed the
/// theme and the language chosen in the settings, and a TrackPopupMenuEx 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.
///
internal sealed class NativeTrayIcon : IDisposable
{
/// Windows shows it under the pointer. The name of the app says enough.
private const string Tooltip = "CursorLang";
/// Distinguishes the icon among those of the same window; we have one.
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;
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;
}
}
/// Raises the menu of the icon where the pointer is.
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;
}
}
}