using XFESystem.Desktop;
using XFESystem.NativeKernel;
namespace XFESystem.NativeRuntime.Application;
public readonly record struct NativeInputContext(bool BrowserFocused, DesktopApplication? ActiveApplication);
public interface IPs2ReportBackend
{
/// <summary>Only successfully initialized devices: bit 0 keyboard, bit 1 standard mouse.</summary>
uint Capabilities { get; }
bool TryRead(out NativeInputReport report);
}
internal sealed class NativePs2ReportBackend : IPs2ReportBackend
{
public uint Capabilities => NativePlatform.InputCapabilities();
public bool TryRead(out NativeInputReport report) => NativePlatform.ReadInput(out report);
}
/// <summary>Maps actual ordered kernel reports to the existing desktop/browser APIs. No hardware-driver dependency.</summary>
public sealed class Ps2InputSource : INativeInputSource
{
private readonly IPs2ReportBackend backend;
private readonly Func<NativeInputContext> context;
private readonly int nativeWidth, nativeHeight, scalePercent;
private readonly Queue<NativeInputEvent> events = new();
private readonly HashSet<uint> held = [];
private readonly Dictionary<uint, BrowserKeyboardInput> browserHeld = [];
private int nativeX, nativeY;
private uint modifiers;
private bool windowsUsed, switching;
public uint Capabilities => backend.Capabilities & 7;
public bool IsAvailable => Capabilities != 0;
public int PointerX => (int)Math.Min((long)nativeX * 100 / scalePercent, (long)nativeWidth * 100 / scalePercent - 1);
public int PointerY => (int)Math.Min((long)nativeY * 100 / scalePercent, (long)nativeHeight * 100 / scalePercent - 1);
public long ReportsRead { get; private set; }
public long LostReports { get; private set; }
public Ps2InputSource(IPs2ReportBackend backend, int nativeWidth, int nativeHeight, int scalePercent, Func<NativeInputContext> context)
{
ArgumentNullException.ThrowIfNull(backend); ArgumentNullException.ThrowIfNull(context);
if (nativeWidth <= 0 || nativeHeight <= 0 || scalePercent is < 100 or > 400 ||
nativeWidth * 100L / scalePercent < 1 || nativeHeight * 100L / scalePercent < 1) throw new ArgumentOutOfRangeException(nameof(nativeWidth));
this.backend = backend; this.nativeWidth = nativeWidth; this.nativeHeight = nativeHeight;
this.scalePercent = scalePercent; this.context = context; nativeX = nativeWidth / 2; nativeY = nativeHeight / 2;
}
public bool TryRead(out NativeInputEvent result)
{
if (events.TryDequeue(out result)) return true;
if (!IsAvailable) { result = default; return false; }
for (int consumed = 0; consumed < 256 && backend.TryRead(out var report); consumed++)
{
ReportsRead++; Translate(report, context());
if (events.TryDequeue(out result)) return true;
}
result = default; return false;
}
private void Translate(NativeInputReport report, NativeInputContext current)
{
if (report.Kind == Ps2Decoder.Pointer)
{
nativeX = (int)Math.Clamp((long)nativeX + report.DeltaX, 0, nativeWidth - 1);
nativeY = (int)Math.Clamp((long)nativeY + report.DeltaY, 0, nativeHeight - 1);
Emit(new(NativeInputKind.Pointer, PointerX, PointerY, (report.Buttons & 1) != 0, report.Wheel)); return;
}
if (report.Kind == Ps2Decoder.Lost) { Recover(report, current); return; }
if (report.Kind is not (Ps2Decoder.KeyDown or Ps2Decoder.KeyUp)) throw new InvalidDataException("Unknown native input report kind.");
uint key = report.KeyCode;
if (key > 0x17F && key != Ps2Decoder.PauseKey) throw new InvalidDataException("Native set-1 key code is outside the supported ABI.");
bool released = report.Kind == Ps2Decoder.KeyUp;
bool wasHeld = held.Contains(key), repeat = !released && wasHeld;
if (key != Ps2Decoder.PauseKey) { if (released) held.Remove(key); else held.Add(key); }
bool previousSuper = (modifiers & Ps2Decoder.Super) != 0;
modifiers = report.Modifiers;
bool shift = (modifiers & Ps2Decoder.Shift) != 0, control = (modifiers & Ps2Decoder.Control) != 0;
bool alt = (modifiers & Ps2Decoder.Alt) != 0, super = (modifiers & Ps2Decoder.Super) != 0;
if (!released && super && key is not (0x15B or 0x15C)) windowsUsed = true;
if (key is 0x15B or 0x15C)
{
if (!released && !previousSuper) windowsUsed = shift || control || alt;
if (released && wasHeld && !super && !windowsUsed) EmitKey(DesktopKey.StartMenu);
browserHeld.Remove(key); return;
}
if (ModifierName(key) is { } modifier)
{
if (current.BrowserFocused && !super && !switching)
EmitBrowser(key, new(released ? "keyup" : "keydown", modifier, PhysicalCode(key), Shift: shift, Control: control, Alt: alt, Repeat: repeat));
if (released && key is 0x38 or 0x138 && !alt && switching)
{ switching = false; EmitKey(DesktopKey.EndWindowSwitch); }
return;
}
if (key == 0x3F)
{ if (!released) EmitKey(DesktopKey.Run); return; }
bool desktopChord = super || (alt && key is 0x0F or 0x3E) || (control && (key == 0x26 || key == 0x1F && current.ActiveApplication == DesktopApplication.Editor));
if (current.BrowserFocused && !desktopChord)
{
string keyName = KeyName(key, report.Unicode, modifiers);
if (keyName.Length != 0)
{
string text = !released && !control && !alt && !super && report.Unicode is >= 32 and <= 0x7E ? ((char)report.Unicode).ToString() : "";
EmitBrowser(key, new(released ? "keyup" : "keydown", keyName, PhysicalCode(key), text, shift, control, alt, repeat));
}
return;
}
if (released) { browserHeld.Remove(key); return; }
if (super)
{
DesktopKey? action = key switch
{
0x12 => DesktopKey.OpenFiles, 0x20 => DesktopKey.ShowDesktop,
0x14B => DesktopKey.SnapLeft, 0x14D => DesktopKey.SnapRight, 0x148 => DesktopKey.Maximize, 0x150 => DesktopKey.Restore,
0x4B when (modifiers & Ps2Decoder.NumLock) == 0 => DesktopKey.SnapLeft,
0x4D when (modifiers & Ps2Decoder.NumLock) == 0 => DesktopKey.SnapRight,
0x48 when (modifiers & Ps2Decoder.NumLock) == 0 => DesktopKey.Maximize,
0x50 when (modifiers & Ps2Decoder.NumLock) == 0 => DesktopKey.Restore,
_ => null
};
if (action is { } value) EmitKey(value); return;
}
if (alt)
{
if (key == 0x0F) { switching = true; EmitKey(shift ? DesktopKey.SwitchWindowBack : DesktopKey.SwitchWindow); }
else if (key == 0x3E) EmitKey(DesktopKey.CloseWindow);
return;
}
if (control)
{
DesktopKey? action = key switch { 0x2E when current.ActiveApplication == DesktopApplication.Terminal => DesktopKey.Cancel,
0x1F => DesktopKey.Save, 0x26 => DesktopKey.FocusAddress, 0x01 => DesktopKey.StartMenu,
0x1E when current.ActiveApplication == DesktopApplication.Browser => DesktopKey.FocusAddress, _ => null };
if (action is { } value) EmitKey(value); return;
}
if (report.Unicode is >= 32 and <= 0x7E) { Emit(new(NativeInputKind.Key, Key: DesktopKey.Character, Character: (char)report.Unicode)); return; }
DesktopKey? ordinary = key switch
{
0x1C or 0x11C => DesktopKey.Enter, 0x0E => DesktopKey.Backspace, 0x53 or 0x153 => DesktopKey.Delete,
0x4B or 0x14B => DesktopKey.Left, 0x4D or 0x14D => DesktopKey.Right, 0x48 or 0x148 => DesktopKey.Up,
0x50 or 0x150 => DesktopKey.Down, 0x47 or 0x147 => DesktopKey.Home, 0x4F or 0x14F => DesktopKey.End,
0x52 or 0x152 => DesktopKey.Insert, 0x0F => DesktopKey.Tab, 0x01 => DesktopKey.Escape, _ => null
};
if (ordinary is { } mapped) EmitKey(mapped);
}
private void Recover(NativeInputReport report, NativeInputContext current)
{
LostReports++;
if (current.BrowserFocused)
foreach (var entry in browserHeld.Values)
Emit(new(NativeInputKind.BrowserKey, BrowserKey: new("keyup", entry.Key, entry.Code)));
browserHeld.Clear(); held.Clear(); modifiers = report.Modifiers; windowsUsed = true; switching = false;
Emit(new(NativeInputKind.ReportsLost, PointerX, PointerY, (report.Buttons & 1) != 0));
}
private void EmitKey(DesktopKey key) => Emit(new(NativeInputKind.Key, Key: key));
private void EmitBrowser(uint rawKey, BrowserKeyboardInput input)
{
if (input.Type == "keydown" && rawKey != Ps2Decoder.PauseKey) browserHeld[rawKey] = input;
else browserHeld.Remove(rawKey);
Emit(new(NativeInputKind.BrowserKey, BrowserKey: input));
}
private void Emit(NativeInputEvent input)
{
if (events.Count >= 512) throw new InvalidOperationException("Application input recovery exceeded its bounded event budget.");
events.Enqueue(input);
}
private static string? ModifierName(uint key) => key switch
{ 0x2A or 0x36 => "Shift", 0x1D or 0x11D => "Control", 0x38 or 0x138 => "Alt", 0x3A => "CapsLock", 0x45 => "NumLock", _ => null };
private static string KeyName(uint key, uint unicode, uint modifiers)
{
if (unicode is >= 32 and <= 0x7E) return ((char)unicode).ToString();
char printable = Ps2Decoder.Character(key, modifiers);
if (printable >= ' ') return printable.ToString();
return key switch
{
0x1C or 0x11C => "Enter", 0x0E => "Backspace", 0x0F => "Tab", 0x01 => "Escape",
0x47 or 0x147 => "Home", 0x4F or 0x14F => "End", 0x4B or 0x14B => "ArrowLeft", 0x4D or 0x14D => "ArrowRight",
0x48 or 0x148 => "ArrowUp", 0x50 or 0x150 => "ArrowDown", 0x49 or 0x149 => "PageUp", 0x51 or 0x151 => "PageDown",
0x52 or 0x152 => "Insert", 0x53 or 0x153 => "Delete", 0x137 => "PrintScreen", Ps2Decoder.PauseKey => "Pause",
>= 0x3B and <= 0x44 => "F" + (key - 0x3A), 0x57 => "F11", 0x58 => "F12", _ => ""
};
}
private static string PhysicalCode(uint key)
{
char unshifted = Ps2Decoder.Character(key, 0);
if (unshifted is >= 'a' and <= 'z') return "Key" + char.ToUpperInvariant(unshifted);
if (key is >= 0x02 and <= 0x0B) return "Digit" + unshifted;
return key switch
{
0x2A => "ShiftLeft", 0x36 => "ShiftRight", 0x1D => "ControlLeft", 0x11D => "ControlRight", 0x38 => "AltLeft", 0x138 => "AltRight",
0x3A => "CapsLock", 0x45 => "NumLock", 0x1C => "Enter", 0x11C => "NumpadEnter", 0x0E => "Backspace", 0x0F => "Tab", 0x01 => "Escape",
0x0C => "Minus", 0x0D => "Equal", 0x1A => "BracketLeft", 0x1B => "BracketRight", 0x27 => "Semicolon", 0x28 => "Quote",
0x29 => "Backquote", 0x2B => "Backslash", 0x33 => "Comma", 0x34 => "Period", 0x35 => "Slash", 0x39 => "Space",
0x37 => "NumpadMultiply", 0x135 => "NumpadDivide", 0x4A => "NumpadSubtract", 0x4E => "NumpadAdd",
0x47 => "Numpad7", 0x48 => "Numpad8", 0x49 => "Numpad9", 0x4B => "Numpad4", 0x4C => "Numpad5", 0x4D => "Numpad6",
0x4F => "Numpad1", 0x50 => "Numpad2", 0x51 => "Numpad3", 0x52 => "Numpad0", 0x53 => "NumpadDecimal",
_ => KeyName(key, 0, 0)
};
}
}
using XFESystem.Desktop;
using XFESystem.NativeKernel;
namespace XFESystem.NativeRuntime.Application;
public readonly record struct NativeInputContext(bool BrowserFocused, DesktopApplication? ActiveApplication);
public interface IPs2ReportBackend
{
/// <summary>Only successfully initialized devices: bit 0 keyboard, bit 1 standard mouse.</summary>
uint Capabilities { get; }
bool TryRead(out NativeInputReport report);
}
internal sealed class NativePs2ReportBackend : IPs2ReportBackend
{
public uint Capabilities => NativePlatform.InputCapabilities();
public bool TryRead(out NativeInputReport report) => NativePlatform.ReadInput(out report);
}
/// <summary>Maps actual ordered kernel reports to the existing desktop/browser APIs. No hardware-driver dependency.</summary>
public sealed class Ps2InputSource : INativeInputSource
{
private readonly IPs2ReportBackend backend;
private readonly Func<NativeInputContext> context;
private readonly int nativeWidth, nativeHeight, scalePercent;
private readonly Queue<NativeInputEvent> events = new();
private readonly HashSet<uint> held = [];
private readonly Dictionary<uint, BrowserKeyboardInput> browserHeld = [];
private int nativeX, nativeY;
private uint modifiers;
private bool windowsUsed, switching;
public uint Capabilities => backend.Capabilities & 7;
public bool IsAvailable => Capabilities != 0;
public int PointerX => (int)Math.Min((long)nativeX * 100 / scalePercent, (long)nativeWidth * 100 / scalePercent - 1);
public int PointerY => (int)Math.Min((long)nativeY * 100 / scalePercent, (long)nativeHeight * 100 / scalePercent - 1);
public long ReportsRead { get; private set; }
public long LostReports { get; private set; }
public Ps2InputSource(IPs2ReportBackend backend, int nativeWidth, int nativeHeight, int scalePercent, Func<NativeInputContext> context)
{
ArgumentNullException.ThrowIfNull(backend); ArgumentNullException.ThrowIfNull(context);
if (nativeWidth <= 0 || nativeHeight <= 0 || scalePercent is < 100 or > 400 ||
nativeWidth * 100L / scalePercent < 1 || nativeHeight * 100L / scalePercent < 1) throw new ArgumentOutOfRangeException(nameof(nativeWidth));
this.backend = backend; this.nativeWidth = nativeWidth; this.nativeHeight = nativeHeight;
this.scalePercent = scalePercent; this.context = context; nativeX = nativeWidth / 2; nativeY = nativeHeight / 2;
}
public bool TryRead(out NativeInputEvent result)
{
if (events.TryDequeue(out result)) return true;
if (!IsAvailable) { result = default; return false; }
for (int consumed = 0; consumed < 256 && backend.TryRead(out var report); consumed++)
{
ReportsRead++; Translate(report, context());
if (events.TryDequeue(out result)) return true;
}
result = default; return false;
}
private void Translate(NativeInputReport report, NativeInputContext current)
{
if (report.Kind == Ps2Decoder.Pointer)
{
nativeX = (int)Math.Clamp((long)nativeX + report.DeltaX, 0, nativeWidth - 1);
nativeY = (int)Math.Clamp((long)nativeY + report.DeltaY, 0, nativeHeight - 1);
Emit(new(NativeInputKind.Pointer, PointerX, PointerY, (report.Buttons & 1) != 0, report.Wheel)); return;
}
if (report.Kind == Ps2Decoder.Lost) { Recover(report, current); return; }
if (report.Kind is not (Ps2Decoder.KeyDown or Ps2Decoder.KeyUp)) throw new InvalidDataException("Unknown native input report kind.");
uint key = report.KeyCode;
if (key > 0x17F && key != Ps2Decoder.PauseKey) throw new InvalidDataException("Native set-1 key code is outside the supported ABI.");
bool released = report.Kind == Ps2Decoder.KeyUp;
bool wasHeld = held.Contains(key), repeat = !released && wasHeld;
if (key != Ps2Decoder.PauseKey) { if (released) held.Remove(key); else held.Add(key); }
bool previousSuper = (modifiers & Ps2Decoder.Super) != 0;
modifiers = report.Modifiers;
bool shift = (modifiers & Ps2Decoder.Shift) != 0, control = (modifiers & Ps2Decoder.Control) != 0;
bool alt = (modifiers & Ps2Decoder.Alt) != 0, super = (modifiers & Ps2Decoder.Super) != 0;
if (!released && super && key is not (0x15B or 0x15C)) windowsUsed = true;
if (key is 0x15B or 0x15C)
{
if (!released && !previousSuper) windowsUsed = shift || control || alt;
if (released && wasHeld && !super && !windowsUsed) EmitKey(DesktopKey.StartMenu);
browserHeld.Remove(key); return;
}
if (ModifierName(key) is { } modifier)
{
if (current.BrowserFocused && !super && !switching)
EmitBrowser(key, new(released ? "keyup" : "keydown", modifier, PhysicalCode(key), Shift: shift, Control: control, Alt: alt, Repeat: repeat));
if (released && key is 0x38 or 0x138 && !alt && switching)
{ switching = false; EmitKey(DesktopKey.EndWindowSwitch); }
return;
}
if (key == 0x3F)
{ if (!released) EmitKey(DesktopKey.Run); return; }
bool desktopChord = super || (alt && key is 0x0F or 0x3E) || (control && (key == 0x26 || key == 0x1F && current.ActiveApplication == DesktopApplication.Editor));
if (current.BrowserFocused && !desktopChord)
{
string keyName = KeyName(key, report.Unicode, modifiers);
if (keyName.Length != 0)
{
string text = !released && !control && !alt && !super && report.Unicode is >= 32 and <= 0x7E ? ((char)report.Unicode).ToString() : "";
EmitBrowser(key, new(released ? "keyup" : "keydown", keyName, PhysicalCode(key), text, shift, control, alt, repeat));
}
return;
}
if (released) { browserHeld.Remove(key); return; }
if (super)
{
DesktopKey? action = key switch
{
0x12 => DesktopKey.OpenFiles, 0x20 => DesktopKey.ShowDesktop,
0x14B => DesktopKey.SnapLeft, 0x14D => DesktopKey.SnapRight, 0x148 => DesktopKey.Maximize, 0x150 => DesktopKey.Restore,
0x4B when (modifiers & Ps2Decoder.NumLock) == 0 => DesktopKey.SnapLeft,
0x4D when (modifiers & Ps2Decoder.NumLock) == 0 => DesktopKey.SnapRight,
0x48 when (modifiers & Ps2Decoder.NumLock) == 0 => DesktopKey.Maximize,
0x50 when (modifiers & Ps2Decoder.NumLock) == 0 => DesktopKey.Restore,
_ => null
};
if (action is { } value) EmitKey(value); return;
}
if (alt)
{
if (key == 0x0F) { switching = true; EmitKey(shift ? DesktopKey.SwitchWindowBack : DesktopKey.SwitchWindow); }
else if (key == 0x3E) EmitKey(DesktopKey.CloseWindow);
return;
}
if (control)
{
DesktopKey? action = key switch { 0x2E when current.ActiveApplication == DesktopApplication.Terminal => DesktopKey.Cancel,
0x1F => DesktopKey.Save, 0x26 => DesktopKey.FocusAddress, 0x01 => DesktopKey.StartMenu,
0x1E when current.ActiveApplication == DesktopApplication.Browser => DesktopKey.FocusAddress, _ => null };
if (action is { } value) EmitKey(value); return;
}
if (report.Unicode is >= 32 and <= 0x7E) { Emit(new(NativeInputKind.Key, Key: DesktopKey.Character, Character: (char)report.Unicode)); return; }
DesktopKey? ordinary = key switch
{
0x1C or 0x11C => DesktopKey.Enter, 0x0E => DesktopKey.Backspace, 0x53 or 0x153 => DesktopKey.Delete,
0x4B or 0x14B => DesktopKey.Left, 0x4D or 0x14D => DesktopKey.Right, 0x48 or 0x148 => DesktopKey.Up,
0x50 or 0x150 => DesktopKey.Down, 0x47 or 0x147 => DesktopKey.Home, 0x4F or 0x14F => DesktopKey.End,
0x52 or 0x152 => DesktopKey.Insert, 0x0F => DesktopKey.Tab, 0x01 => DesktopKey.Escape, _ => null
};
if (ordinary is { } mapped) EmitKey(mapped);
}
private void Recover(NativeInputReport report, NativeInputContext current)
{
LostReports++;
if (current.BrowserFocused)
foreach (var entry in browserHeld.Values)
Emit(new(NativeInputKind.BrowserKey, BrowserKey: new("keyup", entry.Key, entry.Code)));
browserHeld.Clear(); held.Clear(); modifiers = report.Modifiers; windowsUsed = true; switching = false;
Emit(new(NativeInputKind.ReportsLost, PointerX, PointerY, (report.Buttons & 1) != 0));
}
private void EmitKey(DesktopKey key) => Emit(new(NativeInputKind.Key, Key: key));
private void EmitBrowser(uint rawKey, BrowserKeyboardInput input)
{
if (input.Type == "keydown" && rawKey != Ps2Decoder.PauseKey) browserHeld[rawKey] = input;
else browserHeld.Remove(rawKey);
Emit(new(NativeInputKind.BrowserKey, BrowserKey: input));
}
private void Emit(NativeInputEvent input)
{
if (events.Count >= 512) throw new InvalidOperationException("Application input recovery exceeded its bounded event budget.");
events.Enqueue(input);
}
private static string? ModifierName(uint key) => key switch
{ 0x2A or 0x36 => "Shift", 0x1D or 0x11D => "Control", 0x38 or 0x138 => "Alt", 0x3A => "CapsLock", 0x45 => "NumLock", _ => null };
private static string KeyName(uint key, uint unicode, uint modifiers)
{
if (unicode is >= 32 and <= 0x7E) return ((char)unicode).ToString();
char printable = Ps2Decoder.Character(key, modifiers);
if (printable >= ' ') return printable.ToString();
return key switch
{
0x1C or 0x11C => "Enter", 0x0E => "Backspace", 0x0F => "Tab", 0x01 => "Escape",
0x47 or 0x147 => "Home", 0x4F or 0x14F => "End", 0x4B or 0x14B => "ArrowLeft", 0x4D or 0x14D => "ArrowRight",
0x48 or 0x148 => "ArrowUp", 0x50 or 0x150 => "ArrowDown", 0x49 or 0x149 => "PageUp", 0x51 or 0x151 => "PageDown",
0x52 or 0x152 => "Insert", 0x53 or 0x153 => "Delete", 0x137 => "PrintScreen", Ps2Decoder.PauseKey => "Pause",
>= 0x3B and <= 0x44 => "F" + (key - 0x3A), 0x57 => "F11", 0x58 => "F12", _ => ""
};
}
private static string PhysicalCode(uint key)
{
char unshifted = Ps2Decoder.Character(key, 0);
if (unshifted is >= 'a' and <= 'z') return "Key" + char.ToUpperInvariant(unshifted);
if (key is >= 0x02 and <= 0x0B) return "Digit" + unshifted;
return key switch
{
0x2A => "ShiftLeft", 0x36 => "ShiftRight", 0x1D => "ControlLeft", 0x11D => "ControlRight", 0x38 => "AltLeft", 0x138 => "AltRight",
0x3A => "CapsLock", 0x45 => "NumLock", 0x1C => "Enter", 0x11C => "NumpadEnter", 0x0E => "Backspace", 0x0F => "Tab", 0x01 => "Escape",
0x0C => "Minus", 0x0D => "Equal", 0x1A => "BracketLeft", 0x1B => "BracketRight", 0x27 => "Semicolon", 0x28 => "Quote",
0x29 => "Backquote", 0x2B => "Backslash", 0x33 => "Comma", 0x34 => "Period", 0x35 => "Slash", 0x39 => "Space",
0x37 => "NumpadMultiply", 0x135 => "NumpadDivide", 0x4A => "NumpadSubtract", 0x4E => "NumpadAdd",
0x47 => "Numpad7", 0x48 => "Numpad8", 0x49 => "Numpad9", 0x4B => "Numpad4", 0x4C => "Numpad5", 0x4D => "Numpad6",
0x4F => "Numpad1", 0x50 => "Numpad2", 0x51 => "Numpad3", 0x52 => "Numpad0", 0x53 => "NumpadDecimal",
_ => KeyName(key, 0, 0)
};
}
}