using System.Runtime.InteropServices; using CursorLang.Core.Interop; namespace CursorLang.Agent.Interop; /// /// The system context menu — the one the tray icon raises. /// /// /// A menu built this way is drawn by Windows, so the theme and the language chosen in /// the application no longer reach it. That is the accepted price of leaving WPF: a WPF /// ContextMenu costs the whole rendering stack in the background process. /// internal static class MenuNative { private const uint MF_STRING = 0x00000000; private const uint MF_SEPARATOR = 0x00000800; private const uint MF_GRAYED = 0x00000001; private const uint TPM_LEFTALIGN = 0x0000; private const uint TPM_RIGHTBUTTON = 0x0002; private const uint TPM_RETURNCMD = 0x0100; /// An item of the menu being built. /// What gives back when the item is chosen. /// The text, or null for a separator. /// A greyed item is shown but cannot be chosen. internal readonly record struct Item(int Id, string? Caption, bool IsEnabled = true) { internal static Item Separator => new(0, null); } /// /// Raises the menu at a screen point and returns the identifier of the chosen item, /// or zero when the user dismissed it. /// /// /// TPM_RETURNCMD means the answer comes back from the call itself instead of /// as a WM_COMMAND later, which keeps the whole menu in one place. The call /// does not return until the user is done with the menu — that is how a modal menu /// works, and the message loop keeps running inside it. /// /// The window is brought to the foreground first and poked with an empty message /// afterwards: without the first the menu never closes on a click elsewhere, and /// without the second it stays on screen after the choice is made. Both are /// long-standing quirks of a menu owned by a window the user cannot see. /// internal static int Track(IntPtr owner, PopupWindowNative.Point at, IReadOnlyList items) { IntPtr menu = CreatePopupMenu(); if (menu == IntPtr.Zero) { return 0; } try { foreach (Item item in items) { if (item.Caption is null) { AppendMenu(menu, MF_SEPARATOR, IntPtr.Zero, null); continue; } uint flags = MF_STRING | (item.IsEnabled ? 0 : MF_GRAYED); AppendMenu(menu, flags, new IntPtr(item.Id), item.Caption); } TrayIconNative.BringToForeground(owner); int chosen = TrackPopupMenuEx( menu, TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD, at.X, at.Y, owner, IntPtr.Zero); WindowNative.PostMessage(owner, WindowNative.WM_NULL, IntPtr.Zero, IntPtr.Zero); return chosen; } finally { DestroyMenu(menu); } } [DllImport("user32.dll")] private static extern IntPtr CreatePopupMenu(); [DllImport("user32.dll")] private static extern bool DestroyMenu(IntPtr hMenu); [DllImport("user32.dll", CharSet = CharSet.Unicode, EntryPoint = "AppendMenuW")] private static extern bool AppendMenu(IntPtr hMenu, uint uFlags, IntPtr uIDNewItem, string? lpNewItem); [DllImport("user32.dll")] private static extern int TrackPopupMenuEx(IntPtr hMenu, uint uFlags, int x, int y, IntPtr hwnd, IntPtr lptpm); }