using System.Threading; namespace XFESystem.Kernel; /// Bounded raw make/break reports; the original Cosmos decoder still runs. public static class NativeKeyboardCapture { private const int Capacity = 512; private static readonly int[] reports = new int[Capacity]; private static long published, consumed; private static int overflow, writerGate; // Touch before enabling keyboard delivery when the platform controls initialization. public static void Prepare() { _ = reports.Length; } // O(1), read-only, and safe to sample between software raster rows. public static bool HasPendingReports => Volatile.Read(ref overflow) != 0 || Volatile.Read(ref published) != Volatile.Read(ref consumed); // Called by the project-local IL hook at the start of HandleScanCode. // No allocation, blocking, logging or desktop mutation in interrupt context. public static void Observe(byte scanCode, bool released) { // Never spin in an interrupt or overwrite an unread slot. A nested/concurrent // writer is treated as loss, which the consumer handles by resetting modifiers. if (Interlocked.CompareExchange(ref writerGate, 1, 0) != 0) { Volatile.Write(ref overflow, 1); return; } try { long head = published; if (unchecked(head - Volatile.Read(ref consumed)) >= Capacity) { Volatile.Write(ref overflow, 1); return; } reports[(int)(head & (Capacity - 1))] = scanCode | (released ? 256 : 0); Volatile.Write(ref published, unchecked(head + 1)); } finally { Volatile.Write(ref writerGate, 0); } } public static bool TryRead(out byte scanCode, out bool released, out bool lostReports) { lostReports = false; scanCode = 0; released = false; if (Volatile.Read(ref overflow) != 0) { // Do not replay old modifier makes after a lost break. The gate also // excludes an in-flight writer whose report belongs to the lost batch. if (Interlocked.CompareExchange(ref writerGate, 1, 0) != 0) return false; try { Volatile.Write(ref consumed, published); Interlocked.Exchange(ref overflow, 0); } finally { Volatile.Write(ref writerGate, 0); } lostReports = true; released = true; return true; // Explicit loss sentinel; Translate resets without decoding a key. } long head = Volatile.Read(ref published); long tail = consumed; if (tail == head) return false; int report = reports[(int)(tail & (Capacity - 1))]; Volatile.Write(ref consumed, unchecked(tail + 1)); scanCode = (byte)report; released = (report & 256) != 0; return true; } }