lightweight variant (#1)

Reviewed-on: #1
Co-authored-by: Aleksandr Neychev <alexnejchev73@gmail.com>
This commit was merged in pull request #1.
This commit is contained in:
2026-08-12 13:37:30 +00:00
committed by alex
parent a0d3098fe4
commit 55ac8e6556
147 changed files with 4055 additions and 2349 deletions
+104
View File
@@ -0,0 +1,104 @@
using CursorLang.Agent.Interop;
namespace CursorLang.Agent.Windows;
/// <summary>
/// A window with no framework behind it: a registered class, a handle and a window
/// procedure that lands in <see cref="OnMessage"/>.
/// </summary>
/// <remarks>
/// 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
/// <c>CreateWindowExW</c> 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.
/// </remarks>
internal abstract class NativeWindow : IDisposable
{
private static readonly Dictionary<IntPtr, NativeWindow> Live = [];
private static readonly HashSet<string> 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;
}
/// <summary>The window handle. Zero once the window is gone.</summary>
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);
}
/// <summary>
/// A message for this window. Returning <c>false</c> passes it to
/// <c>DefWindowProcW</c>, which is what the vast majority of messages want — and
/// what all of them want for a window whose content is set from the outside.
/// </summary>
protected virtual bool OnMessage(uint message, IntPtr wParam, IntPtr lParam, out IntPtr result)
{
result = IntPtr.Zero;
return false;
}
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);
}
}