using System.Runtime.CompilerServices;
using Cosmos.Kernel.System.Input;
using XFESystem.Desktop;
namespace XFESystem.Kernel;
/// <summary>Routes ordered make/break reports to desktop shortcuts without sticky Win/Alt state.</summary>
internal sealed class NativeDesktopInput
{
private bool leftShift, rightShift, control, leftAlt, rightAlt, leftWin, rightWin;
private bool windowsUsed, capsLock, numLock, switching;
private ulong systemKeysLow, systemKeysHigh;
// UI-thread diagnostics only. No entered text, allocations or logging are
// added to the IRQ capture path.
public Action<string>? Trace { get; set; }
public Action? NavigationDispatched { get; set; }
public DesktopKey? Translate(byte scanCode, bool released, bool lostReports, DesktopSession session, out char character)
{
character = '\0';
if (lostReports) Reset(session);
if (lostReports) return null;
TraceShortcut(session, scanCode, released, "Raw", null);
bool navigation = IsNavigationScan(scanCode);
if (navigation) TraceNavigation(session, scanCode, released, "Raw", "");
if (!released && (leftWin || rightWin) && scanCode is not (0x5B or 0x5C)) windowsUsed = true;
switch (scanCode)
{
case 0x2A: leftShift = !released; Modifier(session,"Shift","ShiftLeft",released); return null;
case 0x36: rightShift = !released; Modifier(session,"Shift","ShiftRight",released); return null;
case 0x1D: control = !released; Modifier(session,"Control","ControlLeft",released); TraceNavigation(session,scanCode,released,"Modifier","Control"); return null;
case 0x38: leftAlt = !released; Modifier(session,"Alt","AltLeft",released); return EndAltIfReleased(session, scanCode, released);
case 0x60: rightAlt = !released; Modifier(session,"Alt","AltRight",released); return EndAltIfReleased(session, scanCode, released);
case 0x5B:
case 0x5C:
bool wasHeld = leftWin || rightWin;
bool thisWasHeld = scanCode == 0x5B ? leftWin : rightWin;
if (released && !thisWasHeld) return null;
if (scanCode == 0x5B) leftWin = !released; else rightWin = !released;
if (!wasHeld && !released) windowsUsed = leftShift || rightShift || control || leftAlt || rightAlt;
if (released && !leftWin && !rightWin && !windowsUsed) return Shortcut(session, scanCode, released, DesktopKey.StartMenu);
return null;
case 0x3A: if (!released) capsLock = !capsLock; return null;
case 0x45: if (!released) numLock = !numLock; return null;
}
// Desktop chords are handled before invoking any page/layout code. In
// particular a focused web editor must never consume Tab/Win commands.
// Remember the consumed make so its break cannot arrive as an unmatched
// browser keyup if the modifier was released first in the USB report.
if (released && TakeSystemKey(scanCode)) return null;
if (!released && TranslateShortcut(scanCode) is { } shortcut)
{
MarkSystemKey(scanCode);
return Shortcut(session, scanCode, false, shortcut);
}
if (leftWin || rightWin)
{
if (!released) MarkSystemKey(scanCode);
return null;
}
if (session.HasBrowserContentFocus && !leftWin && !rightWin &&
!((leftAlt || rightAlt) && scanCode is 0x0F or 0x3E) && !(control && scanCode == 0x26) &&
!(control && scanCode == 0x1F && session.ActiveWindow?.Application == DesktopApplication.Editor))
{
var ordinary = TranslateOrdinary(scanCode, leftShift || rightShift, numLock, capsLock, out var text);
if (navigation) TraceNavigation(session,scanCode,released,"Decoded",ordinary?.ToString() ?? "Unmapped");
string name = ordinary switch
{
DesktopKey.Character => text.ToString(), DesktopKey.Enter => "Enter", DesktopKey.Backspace => "Backspace",
DesktopKey.Delete => "Delete", DesktopKey.Left => "ArrowLeft", DesktopKey.Right => "ArrowRight",
DesktopKey.Up => "ArrowUp", DesktopKey.Down => "ArrowDown", DesktopKey.Home => "Home", DesktopKey.End => "End",
DesktopKey.Tab => "Tab", DesktopKey.Escape => "Escape", _ => ""
};
if (name.Length != 0)
{
TranslateOrdinary(scanCode,false,numLock,false,out var physical);
string code = char.IsAsciiLetter(physical) ? "Key" + char.ToUpperInvariant(physical) : char.IsAsciiDigit(physical) ? "Digit" + physical : scanCode switch
{ 0x0C=>"Minus",0x0D=>"Equal",0x1A=>"BracketLeft",0x1B=>"BracketRight",0x27=>"Semicolon",0x28=>"Quote",0x29=>"Backquote",0x2B=>"Backslash",0x33=>"Comma",0x34=>"Period",0x35=>"Slash",0x39=>"Space",_=>name };
session.BrowserKey(new(released ? "keyup" : "keydown", name, code,
!released && !control && !leftAlt && !rightAlt && text >= ' ' ? text.ToString() : "",
leftShift || rightShift, control, leftAlt || rightAlt));
if (navigation) { TraceNavigation(session,scanCode,released,"Dispatched",ordinary==DesktopKey.Character?"Character":name); NavigationDispatched?.Invoke(); }
return null;
}
}
if (released) { if (navigation) TraceNavigation(session,scanCode,true,"DesktopRelease",""); return null; }
bool shift = leftShift || rightShift, alt = leftAlt || rightAlt;
if (alt || control) return null;
return TranslateOrdinary(scanCode, shift, numLock, capsLock, out character);
DesktopKey? TranslateShortcut(byte code)
{
if (leftWin || rightWin)
return code switch
{
0x12 => DesktopKey.OpenFiles, 0x20 => DesktopKey.ShowDesktop,
0x4B => DesktopKey.SnapLeft, 0x4D => DesktopKey.SnapRight,
0x48 => DesktopKey.Maximize, 0x50 => DesktopKey.Restore, _ => null
};
if (leftAlt || rightAlt)
{
if (code == 0x0F) { switching = true; return leftShift || rightShift ? DesktopKey.SwitchWindowBack : DesktopKey.SwitchWindow; }
return code == 0x3E ? DesktopKey.CloseWindow : null;
}
if (control)
return code switch
{
0x1F when session.ActiveWindow?.Application == DesktopApplication.Editor || !session.HasBrowserContentFocus => DesktopKey.Save,
0x26 => DesktopKey.FocusAddress, 0x01 => DesktopKey.StartMenu,
0x1E when session.ActiveWindow?.Application == DesktopApplication.Browser && !session.HasBrowserContentFocus => DesktopKey.FocusAddress,
_ => null
};
return null;
}
}
private static bool IsNavigationScan(byte scan) => (scan & 0x7F) is >= 0x47 and <= 0x53 or 0x67;
private void TraceNavigation(DesktopSession session,byte scan,bool released,string stage,string key)
{
var trace = Trace; if (trace is null) return;
trace("XFE_NATIVE_NAV|"+stage+"|Scan|"+scan+"|Key|"+key+"|Released|"+(released?1:0)+
"|Control|"+(control?1:0)+"|Shift|"+(leftShift||rightShift?1:0)+"|Alt|"+(leftAlt||rightAlt?1:0)+
"|NumLock|"+(numLock?1:0)+"|BrowserFocus|"+(session.HasBrowserContentFocus?1:0)+"\n");
}
private void Modifier(DesktopSession session,string key,string code,bool released)
{
if (session.HasBrowserContentFocus && !leftWin && !rightWin && !switching)
session.BrowserKey(new(released?"keyup":"keydown",key,code,Shift:leftShift||rightShift,Control:control,Alt:leftAlt||rightAlt));
}
// Raw desktop chords are independent of native layout initialization. Keeping
// the adapter separate also allows host tests to exercise the actual router
// without attempting to load a guest-only Cosmos runtime into Windows.
[MethodImpl(MethodImplOptions.NoInlining)]
private static DesktopKey? TranslateOrdinary(byte scanCode, bool shift, bool numLock, bool capsLock, out char character)
{
character = '\0';
var layout = KeyboardManager.GetLayout();
var key = layout is null ? null : Convert(layout, scanCode, false, shift, false, numLock, capsLock);
if (key is null) return null;
character = key.KeyChar;
return key.Key switch
{
Key.Enter => DesktopKey.Enter, Key.Backspace => DesktopKey.Backspace,
Key.Delete => DesktopKey.Delete, Key.LeftArrow => DesktopKey.Left,
Key.RightArrow => DesktopKey.Right, Key.UpArrow => DesktopKey.Up,
Key.DownArrow => DesktopKey.Down, Key.Home => DesktopKey.Home,
Key.End => DesktopKey.End, Key.Tab => DesktopKey.Tab,
Key.Escape => DesktopKey.Escape, Key.F5 => DesktopKey.Run,
_ => character >= ' ' ? DesktopKey.Character : null
};
}
private DesktopKey? EndAltIfReleased(DesktopSession session, byte scanCode, bool released)
{
if (!leftAlt && !rightAlt && switching) { switching = false; return Shortcut(session, scanCode, released, DesktopKey.EndWindowSwitch); }
return null;
}
private void MarkSystemKey(byte scanCode)
{
if (scanCode < 64) systemKeysLow |= 1UL << scanCode;
else if (scanCode < 128) systemKeysHigh |= 1UL << (scanCode - 64);
}
private bool TakeSystemKey(byte scanCode)
{
if (scanCode >= 128) return false;
ulong bit = 1UL << (scanCode & 63);
ref ulong keys = ref (scanCode < 64 ? ref systemKeysLow : ref systemKeysHigh);
bool consumed = (keys & bit) != 0; keys &= ~bit; return consumed;
}
private DesktopKey Shortcut(DesktopSession session, byte scanCode, bool released, DesktopKey key)
{
TraceShortcut(session, scanCode, released, "Desktop", key);
return key;
}
private void TraceShortcut(DesktopSession session, byte scanCode, bool released, string stage, DesktopKey? key)
{
if (Trace is not { } trace || (key is null && scanCode is not (0x38 or 0x60 or 0x5B or 0x5C or 0x0F or 0x3E))) return;
trace("XFE_NATIVE_SHORTCUT|" + stage + "|Scan|" + scanCode + "|Released|" + (released ? 1 : 0) +
"|Key|" + (key?.ToString() ?? "") + "|Alt|" + (leftAlt || rightAlt ? 1 : 0) +
"|Win|" + (leftWin || rightWin ? 1 : 0) + "|BrowserFocus|" + (session.HasBrowserContentFocus ? 1 : 0) + "\n");
}
private void Reset(DesktopSession session)
{
leftShift = rightShift = control = leftAlt = rightAlt = leftWin = rightWin = windowsUsed = false;
systemKeysLow = systemKeysHigh = 0;
if (switching) session.Key(DesktopKey.EndWindowSwitch);
switching = false;
}
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "ConvertScanCode")]
private static extern KeyEvent? Convert(KeyboardLayout layout, byte scan, bool control, bool shift, bool alt, bool numLock, bool capsLock);
}
using System.Runtime.CompilerServices;
using Cosmos.Kernel.System.Input;
using XFESystem.Desktop;
namespace XFESystem.Kernel;
/// <summary>Routes ordered make/break reports to desktop shortcuts without sticky Win/Alt state.</summary>
internal sealed class NativeDesktopInput
{
private bool leftShift, rightShift, control, leftAlt, rightAlt, leftWin, rightWin;
private bool windowsUsed, capsLock, numLock, switching;
private ulong systemKeysLow, systemKeysHigh;
// UI-thread diagnostics only. No entered text, allocations or logging are
// added to the IRQ capture path.
public Action<string>? Trace { get; set; }
public Action? NavigationDispatched { get; set; }
public DesktopKey? Translate(byte scanCode, bool released, bool lostReports, DesktopSession session, out char character)
{
character = '\0';
if (lostReports) Reset(session);
if (lostReports) return null;
TraceShortcut(session, scanCode, released, "Raw", null);
bool navigation = IsNavigationScan(scanCode);
if (navigation) TraceNavigation(session, scanCode, released, "Raw", "");
if (!released && (leftWin || rightWin) && scanCode is not (0x5B or 0x5C)) windowsUsed = true;
switch (scanCode)
{
case 0x2A: leftShift = !released; Modifier(session,"Shift","ShiftLeft",released); return null;
case 0x36: rightShift = !released; Modifier(session,"Shift","ShiftRight",released); return null;
case 0x1D: control = !released; Modifier(session,"Control","ControlLeft",released); TraceNavigation(session,scanCode,released,"Modifier","Control"); return null;
case 0x38: leftAlt = !released; Modifier(session,"Alt","AltLeft",released); return EndAltIfReleased(session, scanCode, released);
case 0x60: rightAlt = !released; Modifier(session,"Alt","AltRight",released); return EndAltIfReleased(session, scanCode, released);
case 0x5B:
case 0x5C:
bool wasHeld = leftWin || rightWin;
bool thisWasHeld = scanCode == 0x5B ? leftWin : rightWin;
if (released && !thisWasHeld) return null;
if (scanCode == 0x5B) leftWin = !released; else rightWin = !released;
if (!wasHeld && !released) windowsUsed = leftShift || rightShift || control || leftAlt || rightAlt;
if (released && !leftWin && !rightWin && !windowsUsed) return Shortcut(session, scanCode, released, DesktopKey.StartMenu);
return null;
case 0x3A: if (!released) capsLock = !capsLock; return null;
case 0x45: if (!released) numLock = !numLock; return null;
}
// Desktop chords are handled before invoking any page/layout code. In
// particular a focused web editor must never consume Tab/Win commands.
// Remember the consumed make so its break cannot arrive as an unmatched
// browser keyup if the modifier was released first in the USB report.
if (released && TakeSystemKey(scanCode)) return null;
if (!released && TranslateShortcut(scanCode) is { } shortcut)
{
MarkSystemKey(scanCode);
return Shortcut(session, scanCode, false, shortcut);
}
if (leftWin || rightWin)
{
if (!released) MarkSystemKey(scanCode);
return null;
}
if (session.HasBrowserContentFocus && !leftWin && !rightWin &&
!((leftAlt || rightAlt) && scanCode is 0x0F or 0x3E) && !(control && scanCode == 0x26) &&
!(control && scanCode == 0x1F && session.ActiveWindow?.Application == DesktopApplication.Editor))
{
var ordinary = TranslateOrdinary(scanCode, leftShift || rightShift, numLock, capsLock, out var text);
if (navigation) TraceNavigation(session,scanCode,released,"Decoded",ordinary?.ToString() ?? "Unmapped");
string name = ordinary switch
{
DesktopKey.Character => text.ToString(), DesktopKey.Enter => "Enter", DesktopKey.Backspace => "Backspace",
DesktopKey.Delete => "Delete", DesktopKey.Left => "ArrowLeft", DesktopKey.Right => "ArrowRight",
DesktopKey.Up => "ArrowUp", DesktopKey.Down => "ArrowDown", DesktopKey.Home => "Home", DesktopKey.End => "End",
DesktopKey.Tab => "Tab", DesktopKey.Escape => "Escape", _ => ""
};
if (name.Length != 0)
{
TranslateOrdinary(scanCode,false,numLock,false,out var physical);
string code = char.IsAsciiLetter(physical) ? "Key" + char.ToUpperInvariant(physical) : char.IsAsciiDigit(physical) ? "Digit" + physical : scanCode switch
{ 0x0C=>"Minus",0x0D=>"Equal",0x1A=>"BracketLeft",0x1B=>"BracketRight",0x27=>"Semicolon",0x28=>"Quote",0x29=>"Backquote",0x2B=>"Backslash",0x33=>"Comma",0x34=>"Period",0x35=>"Slash",0x39=>"Space",_=>name };
session.BrowserKey(new(released ? "keyup" : "keydown", name, code,
!released && !control && !leftAlt && !rightAlt && text >= ' ' ? text.ToString() : "",
leftShift || rightShift, control, leftAlt || rightAlt));
if (navigation) { TraceNavigation(session,scanCode,released,"Dispatched",ordinary==DesktopKey.Character?"Character":name); NavigationDispatched?.Invoke(); }
return null;
}
}
if (released) { if (navigation) TraceNavigation(session,scanCode,true,"DesktopRelease",""); return null; }
bool shift = leftShift || rightShift, alt = leftAlt || rightAlt;
if (alt || control) return null;
return TranslateOrdinary(scanCode, shift, numLock, capsLock, out character);
DesktopKey? TranslateShortcut(byte code)
{
if (leftWin || rightWin)
return code switch
{
0x12 => DesktopKey.OpenFiles, 0x20 => DesktopKey.ShowDesktop,
0x4B => DesktopKey.SnapLeft, 0x4D => DesktopKey.SnapRight,
0x48 => DesktopKey.Maximize, 0x50 => DesktopKey.Restore, _ => null
};
if (leftAlt || rightAlt)
{
if (code == 0x0F) { switching = true; return leftShift || rightShift ? DesktopKey.SwitchWindowBack : DesktopKey.SwitchWindow; }
return code == 0x3E ? DesktopKey.CloseWindow : null;
}
if (control)
return code switch
{
0x1F when session.ActiveWindow?.Application == DesktopApplication.Editor || !session.HasBrowserContentFocus => DesktopKey.Save,
0x26 => DesktopKey.FocusAddress, 0x01 => DesktopKey.StartMenu,
0x1E when session.ActiveWindow?.Application == DesktopApplication.Browser && !session.HasBrowserContentFocus => DesktopKey.FocusAddress,
_ => null
};
return null;
}
}
private static bool IsNavigationScan(byte scan) => (scan & 0x7F) is >= 0x47 and <= 0x53 or 0x67;
private void TraceNavigation(DesktopSession session,byte scan,bool released,string stage,string key)
{
var trace = Trace; if (trace is null) return;
trace("XFE_NATIVE_NAV|"+stage+"|Scan|"+scan+"|Key|"+key+"|Released|"+(released?1:0)+
"|Control|"+(control?1:0)+"|Shift|"+(leftShift||rightShift?1:0)+"|Alt|"+(leftAlt||rightAlt?1:0)+
"|NumLock|"+(numLock?1:0)+"|BrowserFocus|"+(session.HasBrowserContentFocus?1:0)+"\n");
}
private void Modifier(DesktopSession session,string key,string code,bool released)
{
if (session.HasBrowserContentFocus && !leftWin && !rightWin && !switching)
session.BrowserKey(new(released?"keyup":"keydown",key,code,Shift:leftShift||rightShift,Control:control,Alt:leftAlt||rightAlt));
}
// Raw desktop chords are independent of native layout initialization. Keeping
// the adapter separate also allows host tests to exercise the actual router
// without attempting to load a guest-only Cosmos runtime into Windows.
[MethodImpl(MethodImplOptions.NoInlining)]
private static DesktopKey? TranslateOrdinary(byte scanCode, bool shift, bool numLock, bool capsLock, out char character)
{
character = '\0';
var layout = KeyboardManager.GetLayout();
var key = layout is null ? null : Convert(layout, scanCode, false, shift, false, numLock, capsLock);
if (key is null) return null;
character = key.KeyChar;
return key.Key switch
{
Key.Enter => DesktopKey.Enter, Key.Backspace => DesktopKey.Backspace,
Key.Delete => DesktopKey.Delete, Key.LeftArrow => DesktopKey.Left,
Key.RightArrow => DesktopKey.Right, Key.UpArrow => DesktopKey.Up,
Key.DownArrow => DesktopKey.Down, Key.Home => DesktopKey.Home,
Key.End => DesktopKey.End, Key.Tab => DesktopKey.Tab,
Key.Escape => DesktopKey.Escape, Key.F5 => DesktopKey.Run,
_ => character >= ' ' ? DesktopKey.Character : null
};
}
private DesktopKey? EndAltIfReleased(DesktopSession session, byte scanCode, bool released)
{
if (!leftAlt && !rightAlt && switching) { switching = false; return Shortcut(session, scanCode, released, DesktopKey.EndWindowSwitch); }
return null;
}
private void MarkSystemKey(byte scanCode)
{
if (scanCode < 64) systemKeysLow |= 1UL << scanCode;
else if (scanCode < 128) systemKeysHigh |= 1UL << (scanCode - 64);
}
private bool TakeSystemKey(byte scanCode)
{
if (scanCode >= 128) return false;
ulong bit = 1UL << (scanCode & 63);
ref ulong keys = ref (scanCode < 64 ? ref systemKeysLow : ref systemKeysHigh);
bool consumed = (keys & bit) != 0; keys &= ~bit; return consumed;
}
private DesktopKey Shortcut(DesktopSession session, byte scanCode, bool released, DesktopKey key)
{
TraceShortcut(session, scanCode, released, "Desktop", key);
return key;
}
private void TraceShortcut(DesktopSession session, byte scanCode, bool released, string stage, DesktopKey? key)
{
if (Trace is not { } trace || (key is null && scanCode is not (0x38 or 0x60 or 0x5B or 0x5C or 0x0F or 0x3E))) return;
trace("XFE_NATIVE_SHORTCUT|" + stage + "|Scan|" + scanCode + "|Released|" + (released ? 1 : 0) +
"|Key|" + (key?.ToString() ?? "") + "|Alt|" + (leftAlt || rightAlt ? 1 : 0) +
"|Win|" + (leftWin || rightWin ? 1 : 0) + "|BrowserFocus|" + (session.HasBrowserContentFocus ? 1 : 0) + "\n");
}
private void Reset(DesktopSession session)
{
leftShift = rightShift = control = leftAlt = rightAlt = leftWin = rightWin = windowsUsed = false;
systemKeysLow = systemKeysHigh = 0;
if (switching) session.Key(DesktopKey.EndWindowSwitch);
switching = false;
}
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "ConvertScanCode")]
private static extern KeyEvent? Convert(KeyboardLayout layout, byte scan, bool control, bool shift, bool alt, bool numLock, bool capsLock);
}