using CursorLang.Agent.Interop; namespace CursorLang.Agent.Windows; /// /// A window with no framework behind it: a registered class, a handle and a window /// procedure that lands in . /// /// /// Windows knows one procedure per class, so the procedure here is shared and static, /// and finds the instance by handle. The very first message of a window arrives while /// CreateWindowExW is still running and there is nothing to find yet — that is /// what the field holding the instance under construction is for. /// /// Everything is deliberately without locks: the agent has one message loop, every /// window belongs to it, and a window procedure can only ever be called on the thread /// that created the window. /// internal abstract class NativeWindow : IDisposable { private static readonly Dictionary Live = []; private static readonly HashSet RegisteredClasses = new(StringComparer.Ordinal); // The shared procedure is a static field for the same reason a hook procedure is: // Windows holds the only reference to it and the collector does not see that private static readonly WindowNative.WindowProc SharedProc = StaticWindowProc; [ThreadStatic] private static NativeWindow? _creating; protected NativeWindow(string className, string title, int style, int exStyle) { if (RegisteredClasses.Add(className)) { WindowNative.RegisterClass(className, SharedProc); } _creating = this; try { Handle = WindowNative.CreateWindow(className, title, style, exStyle); } finally { _creating = null; } Live[Handle] = this; } /// The window handle. Zero once the window is gone. internal IntPtr Handle { get; private set; } public virtual void Dispose() { if (Handle == IntPtr.Zero) { return; } IntPtr handle = Handle; Handle = IntPtr.Zero; Live.Remove(handle); WindowNative.DestroyWindow(handle); } /// /// A message for this window. Returning false passes it to /// DefWindowProcW, which is what the vast majority of messages want. /// protected abstract bool OnMessage(uint message, IntPtr wParam, IntPtr lParam, out IntPtr result); private static IntPtr StaticWindowProc(IntPtr hWnd, uint message, IntPtr wParam, IntPtr lParam) { if (!Live.TryGetValue(hWnd, out NativeWindow? window)) { if (_creating is null) { return WindowNative.DefWindowProc(hWnd, message, wParam, lParam); } // The window is being created right now: bind the handle to the instance // so that the rest of its creation messages find their way home window = _creating; window.Handle = hWnd; Live[hWnd] = window; } if (message == WindowNative.WM_DESTROY) { Live.Remove(hWnd); } return window.OnMessage(message, wParam, lParam, out IntPtr result) ? result : WindowNative.DefWindowProc(hWnd, message, wParam, lParam); } }