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;
    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);
}