using System.Runtime.CompilerServices; using Cosmos.Kernel.System.Input; using XFESystem.Desktop; namespace XFESystem.Kernel; /// Routes ordered make/break reports to desktop shortcuts without sticky Win/Alt state. 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? 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); }