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;
// 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);
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();
case 0x60: rightAlt = !released; Modifier(session,"Alt","AltRight",released); return EndAltIfReleased();
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 DesktopKey.StartMenu;
return null;
case 0x3A: if (!released) capsLock = !capsLock; return null;
case 0x45: if (!released) numLock = !numLock; 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 (leftWin || rightWin)
{
windowsUsed = true;
return scanCode switch
{
0x12 => DesktopKey.OpenFiles, 0x20 => DesktopKey.ShowDesktop,
0x4B => DesktopKey.SnapLeft, 0x4D => DesktopKey.SnapRight,
0x48 => DesktopKey.Maximize, 0x50 => DesktopKey.Restore,
_ => null
};
}
if (alt)
{
if (scanCode == 0x0F) { switching = true; return shift ? DesktopKey.SwitchWindowBack : DesktopKey.SwitchWindow; }
return scanCode == 0x3E ? DesktopKey.CloseWindow : null;
}
if (control)
{
if (navigation) TraceNavigation(session,scanCode,false,"DesktopControl","");
return scanCode switch
{
0x1F => DesktopKey.Save, 0x26 => DesktopKey.FocusAddress,
0x01 => DesktopKey.StartMenu,
0x1E when session.ActiveWindow?.Application == DesktopApplication.Browser => DesktopKey.FocusAddress,
_ => null
};
}
return TranslateOrdinary(scanCode, shift, numLock, capsLock, out character);
}
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()
{
if (!leftAlt && !rightAlt && switching) { switching = false; return DesktopKey.EndWindowSwitch; }
return null;
}
private void Reset(DesktopSession session)
{
leftShift = rightShift = control = leftAlt = rightAlt = leftWin = rightWin = windowsUsed = false;
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;
// 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);
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();
case 0x60: rightAlt = !released; Modifier(session,"Alt","AltRight",released); return EndAltIfReleased();
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 DesktopKey.StartMenu;
return null;
case 0x3A: if (!released) capsLock = !capsLock; return null;
case 0x45: if (!released) numLock = !numLock; 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 (leftWin || rightWin)
{
windowsUsed = true;
return scanCode switch
{
0x12 => DesktopKey.OpenFiles, 0x20 => DesktopKey.ShowDesktop,
0x4B => DesktopKey.SnapLeft, 0x4D => DesktopKey.SnapRight,
0x48 => DesktopKey.Maximize, 0x50 => DesktopKey.Restore,
_ => null
};
}
if (alt)
{
if (scanCode == 0x0F) { switching = true; return shift ? DesktopKey.SwitchWindowBack : DesktopKey.SwitchWindow; }
return scanCode == 0x3E ? DesktopKey.CloseWindow : null;
}
if (control)
{
if (navigation) TraceNavigation(session,scanCode,false,"DesktopControl","");
return scanCode switch
{
0x1F => DesktopKey.Save, 0x26 => DesktopKey.FocusAddress,
0x01 => DesktopKey.StartMenu,
0x1E when session.ActiveWindow?.Application == DesktopApplication.Browser => DesktopKey.FocusAddress,
_ => null
};
}
return TranslateOrdinary(scanCode, shift, numLock, capsLock, out character);
}
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()
{
if (!leftAlt && !rightAlt && switching) { switching = false; return DesktopKey.EndWindowSwitch; }
return null;
}
private void Reset(DesktopSession session)
{
leftShift = rightShift = control = leftAlt = rightAlt = leftWin = rightWin = windowsUsed = false;
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);
}