XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFESystem

【Rust】XFE系统,基于Rust和汇编

公开
关注 0 Fork 0 Star 0
UTF-8
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);
}