using System.Threading;
namespace XFESystem.Kernel;
/// <summary>Bounded raw make/break reports; the original Cosmos decoder still runs.</summary>
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;
}
}
using System.Threading;
namespace XFESystem.Kernel;
/// <summary>Bounded raw make/break reports; the original Cosmos decoder still runs.</summary>
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;
}
}