Skip to content

Win32 Interop

Yasunobu edited this page Jun 26, 2026 · 1 revision

Win32 Interop

All Windows API usage is confined to KeepPressing/Interop/. Native bindings are generated by CsWin32 — list the wanted APIs in Interop/NativeMethods.txt and the source generator produces the P/Invoke signatures (the KeepPressing.csproj registers that file via AdditionalFiles, and AllowUnsafeBlocks is on because the generated INPUT union needs unsafe).

Win32InputSynthesizer — the IInputSynthesizer adapter

Implements the Core port with SendInput. Stateless and thread-safe.

  • Tap sends Down→Up in a single SendInput call, so no other input interleaves between them.
  • Keyboard uses scancodes, not just virtual keys: it maps VK→scancode via MapVirtualKey and sends KEYEVENTF_SCANCODE (adding KEYEVENTF_EXTENDEDKEY for extended keys), falling back to the VK only when a key has no scancode. Raw Input / DirectInput games often ignore VK-only synthetic input but accept scancodes, so this maximizes compatibility.
  • Fixed-position mouse presses add MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_VIRTUALDESK with coordinates normalized through VirtualScreen (see below). The move is attached to both Down and Up, so if the physical mouse drifts during a Hold, the Up still lands at the fixed point and you don't get an accidental drag. System metrics are re-read each time to follow monitor changes.
  • UIPI is silent: if SendInput reports fewer events than sent (blocked by an elevated window), it is logged via Debug.WriteLine rather than thrown — an exception would kill the loop. See Known Limitations #1.

HotkeyListener — global hotkeys

Implements global hotkeys with RegisterHotKey(hWnd = NULL). Key design points:

  • With a NULL window handle, WM_HOTKEY is posted to the calling thread's message queue, so the whole thing runs on one dedicated background thread — no window and no WNDPROC needed. The thread is named KeepPressing.Hotkeys and is a background thread.
  • MOD_NOREPEAT is OR-ed into every registration, so holding a hotkey down doesn't retrigger it.
  • Thread-affinity is respected: RegisterHotKey/UnregisterHotKey must be called on the thread that owns the message loop. Register/unregister requests are queued and pumped via a custom WM_APP+1 message; on WM_QUIT the loop unregisters everything before exiting.
  • Startup handshake: the loop force-creates its message queue with PeekMessage and signals a TaskCompletionSource before any PostThreadMessage is allowed, avoiding a lost-message race.
  • Registration returns false on conflict (another app owns the key), which the ViewModel surfaces to the user. Hotkey IDs: Toggle=1, CaptureConfirm=2, CaptureCancel=3, EmergencyStop=4.

Events fire on the listener thread, so subscribers must marshal to the UI thread — see Threading & ConfigureAwait.

CursorLocator — current cursor position

A thin GetCursorPos wrapper exposing Current as a ScreenPoint. During coordinate capture the view refreshes the live readout each render frame (CompositionTarget.Rendering) for smooth tracking rather than polling on a ~16 ms timer; the confirmed value is read at the instant F8 is pressed.

VirtualScreen — multi-monitor coordinates

Normalizes an absolute ScreenPoint into the 0–65535 range that MOUSEEVENTF_ABSOLUTE expects, across the entire virtual desktop (all monitors, whose origin can be negative). The bounding rectangle comes from GetSystemMetrics(SM_XVIRTUALSCREEN/…/SM_CYVIRTUALSCREEN).

ImeGuard

Prevents the IME from interfering while keys are being assigned/synthesized.

CsWin32: adding a new API

  1. Add the function/constant/type name to KeepPressing/Interop/NativeMethods.txt.
  2. Reference it via the generated Windows.Win32.PInvoke (or the generated type) — no manual [DllImport].
  3. Build; the generator emits the binding. Keep all such usage inside Interop/.

Related: Architecture · Hotkeys · Threading & ConfigureAwait

Clone this wiki locally