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
+66
View File
@@ -0,0 +1,66 @@
using System.Runtime.InteropServices;
namespace CursorLang.Core.Threading;
/// <summary>
/// The Win32 message loop — what the application has instead of a dispatcher.
/// </summary>
/// <remarks>
/// It lives in Core rather than in the agent because the things that need a pumping
/// thread do: <see cref="MessageTimer"/> is used by the layout polling and by saving
/// the settings, and both are Core's. The settings window has a loop of its own, run
/// by WPF, and everything here works inside it just the same.
/// </remarks>
public static class MessageLoop
{
/// <summary>
/// Pumps messages until <c>WM_QUIT</c> and returns its exit code.
/// </summary>
/// <remarks>
/// A -1 from GetMessage means the window handle has already gone; going round
/// again would spin forever, so the loop gives up instead.
/// </remarks>
public static int Run()
{
while (true)
{
int result = GetMessage(out Message message, IntPtr.Zero, 0, 0);
if (result is 0 or -1)
{
return result == 0 ? (int)message.wParam : 1;
}
TranslateMessage(ref message);
DispatchMessage(ref message);
}
}
/// <summary>Asks the loop on this thread to finish.</summary>
public static void Quit(int exitCode = 0) => PostQuitMessage(exitCode);
[DllImport("user32.dll", CharSet = CharSet.Unicode, EntryPoint = "GetMessageW")]
private static extern int GetMessage(out Message lpMsg, IntPtr hWnd, uint filterMin, uint filterMax);
[DllImport("user32.dll")]
private static extern bool TranslateMessage(ref Message lpMsg);
[DllImport("user32.dll", CharSet = CharSet.Unicode, EntryPoint = "DispatchMessageW")]
private static extern IntPtr DispatchMessage(ref Message lpMsg);
[DllImport("user32.dll")]
private static extern void PostQuitMessage(int exitCode);
[StructLayout(LayoutKind.Sequential)]
private struct Message
{
public IntPtr hwnd;
public uint message;
public IntPtr wParam;
public IntPtr lParam;
public uint time;
public int x;
public int y;
public uint lPrivate;
}
}
+70
View File
@@ -0,0 +1,70 @@
using System.Runtime.InteropServices;
namespace CursorLang.Core.Threading;
/// <summary>
/// A timer that ticks on the message loop — the agent's stand-in for
/// <c>DispatcherTimer</c>.
/// </summary>
/// <remarks>
/// <c>SetTimer</c> with a null window binds the timer to the thread rather than to a
/// window, and <c>DispatchMessage</c> calls the callback straight from the loop. The
/// upshot is the same as with a dispatcher timer: the tick arrives on the thread that
/// owns the hook and the popup, so nothing needs marshalling and nothing races.
///
/// The callback lives in a field for the reason a hook procedure does: the only
/// reference to it is held by Win32, and a collected delegate takes the process down
/// with it at the first tick.
/// </remarks>
internal sealed class MessageTimer : IDisposable
{
/// <summary>Windows will not go below this, and pretending otherwise misleads.</summary>
private const uint MinimumIntervalMilliseconds = 10;
private readonly TimerProc _callback;
private nuint _id;
internal MessageTimer() => _callback = OnTimer;
internal event EventHandler? Tick;
internal TimeSpan Interval { get; set; } = TimeSpan.FromMilliseconds(100);
internal bool IsRunning => _id != 0;
/// <summary>Starts the timer, or restarts it from zero when it is already running.</summary>
internal void Start()
{
Stop();
var milliseconds = (uint)Math.Clamp(
Math.Round(Interval.TotalMilliseconds), MinimumIntervalMilliseconds, int.MaxValue);
_id = SetTimer(IntPtr.Zero, 0, milliseconds, _callback);
}
internal void Stop()
{
if (_id == 0)
{
return;
}
KillTimer(IntPtr.Zero, _id);
_id = 0;
}
public void Dispose() => Stop();
private void OnTimer(IntPtr window, uint message, nuint id, uint time) =>
Tick?.Invoke(this, EventArgs.Empty);
private delegate void TimerProc(IntPtr hWnd, uint message, nuint idEvent, uint time);
[DllImport("user32.dll", SetLastError = true)]
private static extern nuint SetTimer(IntPtr hWnd, nuint nIDEvent, uint uElapse, TimerProc lpTimerFunc);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool KillTimer(IntPtr hWnd, nuint uIDEvent);
}