using System.Collections.Concurrent; using CursorLang.Agent.Interop; namespace CursorLang.Agent.Windows; /// /// The window the agent lives around: never shown, but it owns the tray icon and it /// is the way back onto the message loop from a callback. /// /// /// A window with no WS_VISIBLE shows nowhere, yet is a window in every other /// way. A message-only window would do as well were it not for the news of Explorer /// restarting: that one is broadcast, and broadcasts pass such windows by. /// internal sealed class AgentWindow : NativeWindow { /// /// A message hook. Returning true means the message has been dealt with. /// internal delegate bool MessageFilter(uint message, IntPtr wParam, IntPtr lParam); private const string ClassName = "CursorLang.Agent.Window"; /// Drain the queue of posted work. WM_APP is free for the application. private const uint WM_INVOKE = WindowNative.WM_APP + 100; private readonly List _filters = []; private readonly ConcurrentQueue _posted = new(); internal AgentWindow() : base(ClassName, "CursorLang agent", WindowNative.WS_POPUP, WindowNative.WS_EX_TOOLWINDOW) { } internal void AddFilter(MessageFilter filter) => _filters.Add(filter); /// /// Runs the action on the message loop, after the current message is done with. /// /// /// This is what the agent has instead of Dispatcher.BeginInvoke. The caller /// that matters is the keyboard hook: Windows removes a hook whose procedure takes /// too long, so the procedure only records what happened and the answer — showing /// the popup, switching the layout — waits for the message after this one. /// internal void Post(Action action) { _posted.Enqueue(action); WindowNative.PostMessage(Handle, WM_INVOKE, IntPtr.Zero, IntPtr.Zero); } /// Asks the message loop to finish. internal void Quit() => WindowNative.PostQuitMessage(0); protected override bool OnMessage(uint message, IntPtr wParam, IntPtr lParam, out IntPtr result) { result = IntPtr.Zero; if (message == WM_INVOKE) { while (_posted.TryDequeue(out Action? action)) { action(); } return true; } if (message is WindowNative.WM_CLOSE or WindowNative.WM_ENDSESSION) { Quit(); return true; } foreach (MessageFilter filter in _filters) { if (filter(message, wParam, lParam)) { return true; } } return false; } }