【问题标题】:How to Create Global Keyboard Hook with C# in Linux如何在 Linux 中使用 C# 创建全局键盘挂钩
【发布时间】:2021-11-23 14:04:27
【问题描述】:

我有一个无线 USB 遥控器 that I purchased on amazon,我想用它来触发我的程序中的操作。

在 Linux 中连接时,遥控器显示为单独的键盘和鼠标。因此,我正在 C# 中寻找一种方法来拦截来自遥控器的键盘事件并在我的应用程序中使用它们。

我考虑过的一些选项...

选项 1 - 读取 /dev/input/by-id 中的文件

在这个文件夹中,有一个名为“usb-SG.Ltd_SG_Control_Mic-if03-mouse”的文件,当我跟踪它时,它确实会产生一些信息。

这并不理想,原因有两个:

  1. 需要提升权限才能访问数据
  2. 它不允许我的程序独占访问输入数据

选项 2 - 使用 HIDSharp

https://www.zer7.com/software/hidsharp

这是一个看起来像这样可以完成我正在寻找的库,但是文档非常稀疏。

【问题讨论】:

    标签: c# linux keyboard .net-5


    【解决方案1】:

    在这里回答我自己的问题,因为我必须为此做大量研究,我相信它会在以后对其他人有所帮助。我选择了选项 1,因为它似乎是最容易实现的。

    警告 - 这篇文章中会有很多代码

    总结

    出于我的意图和目的,我希望有一些代码可以在用户按下系统任意位置的键时发布事件。在开发这个的过程中,我发现我也可以挂钩鼠标事件。

    需要注意的是,这里的代码(如 linux 操作系统)并没有真正区分键盘按钮按下和鼠标按钮按下。对于 linux,它们都只是按钮。

    了解如果您愿意,您实际上可以扩展此代码以使用其他项目,例如游戏手柄和特殊输入外围设备。

    其他陷阱 - 如问题所述,此代码不会阻止设备输入到其他程序。如果您想覆盖电源按钮或音量按钮等默认功能,这可能会出现问题。

    设置权限

    为了运行此代码,运行此程序的用户必须在输入用户组中,否则将引发异常。运行此代码将当前用户添加到该组。

     sudo gpasswd -a $USER input
    

    EventType.cs

    由于文件夹 /dev/input 本质上是一组用于 linux 操作系统的输入/输出设备的事件总线,因此您可能想要使用多种事件类型。这是我能够组合在一起的枚举,以使破译事件类型更容易一些。

    public enum EventType
    {
        /// <summary>
        /// Used as markers to separate events. Events may be separated in time or in space, such as with the multitouch protocol.
        /// </summary>
        EV_SYN,
    
        /// <summary>
        /// Used to describe state changes of keyboards, buttons, or other key-like devices.
        /// </summary>
        EV_KEY,
    
        /// <summary>
        /// Used to describe relative axis value changes, e.g. moving the mouse 5 units to the left.
        /// </summary>
        EV_REL,
    
        /// <summary>
        /// Used to describe absolute axis value changes, e.g. describing the coordinates of a touch on a touchscreen.
        /// </summary>
        EV_ABS,
    
        /// <summary>
        /// Used to describe miscellaneous input data that do not fit into other types.
        /// </summary>
        EV_MSC,
    
        /// <summary>
        /// Used to describe binary state input switches.
        /// </summary>
        EV_SW,
    
        /// <summary>
        /// Used to turn LEDs on devices on and off.
        /// </summary>
        EV_LED,
    
        /// <summary>
        /// Used to output sound to devices.
        /// </summary>
        EV_SND,
    
        /// <summary>
        /// Used for autorepeating devices.
        /// </summary>
        EV_REP,
    
        /// <summary>
        /// Used to send force feedback commands to an input device.
        /// </summary>
        EV_FF,
    
        /// <summary>
        /// A special type for power button and switch input.
        /// </summary>
        EV_PWR,
    
        /// <summary>
        /// Used to receive force feedback device status.
        /// </summary>
        EV_FF_STATUS,
    }
    

    KeyState.cs

    与许多其他事件处理系统一样,每次用户按键时都会发生多个事件。一次是按下键时,一次是按下键时,另一次是用户决定按住键时。

    public enum KeyState
    {
        KeyUp,
        KeyDown,
        KeyHold
    }
    

    EventCode.cs

    每个不同的按钮都与一个事件代码相关联。无论是键盘上的按钮还是鼠标上的按钮,您都可以在这里找到它。他是一个帮助枚举类,可以更轻松地破译这些代码。

    /// <summary>
    /// Mapping for this can be found here: https://github.com/torvalds/linux/blob/master/include/uapi/linux/input-event-codes.h
    /// </summary>
    public enum EventCode
    {
        Reserved = 0,
        Esc = 1,
        Num1 = 2,
        Num2 = 3,
        Num3 = 4,
        Num4 = 5,
        Num5 = 6,
        Num6 = 7,
        Num7 = 8,
        Num8 = 9,
        Num9 = 10,
        Num0 = 11,
        Minus = 12,
        Equal = 13,
        Backspace = 14,
        Tab = 15,
        Q = 16,
        W = 17,
        E = 18,
        R = 19,
        T = 20,
        Y = 21,
        U = 22,
        I = 23,
        O = 24,
        P = 25,
        LeftBrace = 26,
        RightBrace = 27,
        Enter = 28,
        LeftCtrl = 29,
        A = 30,
        S = 31,
        D = 32,
        F = 33,
        G = 34,
        H = 35,
        J = 36,
        K = 37,
        L = 38,
        Semicolon = 39,
        Apostrophe = 40,
        Grave = 41,
        LeftShift = 42,
        Backslash = 43,
        Z = 44,
        X = 45,
        C = 46,
        V = 47,
        B = 48,
        N = 49,
        M = 50,
        Comma = 51,
        Dot = 52,
        Slash = 53,
        RightShift = 54,
        KpAsterisk = 55,
        LeftAlt = 56,
        Space = 57,
        Capslock = 58,
        F1 = 59,
        Pf2 = 60,
        F3 = 61,
        F4 = 62,
        F5 = 63,
        F6 = 64,
        F7 = 65,
        F8 = 66,
        Pf9 = 67,
        F10 = 68,
        Numlock = 69,
        ScrollLock = 70,
        Kp7 = 71,
        Kp8 = 72,
        Kp9 = 73,
        PkpMinus = 74,
        Kp4 = 75,
        Kp5 = 76,
        Kp6 = 77,
        KpPlus = 78,
        Kp1 = 79,
        Kp2 = 80,
        Kp3 = 81,
        Kp0 = 82,
        KpDot = 83,
    
        Zenkakuhankaku = 85,
        //102ND = 86,
        F11 = 87,
        F12 = 88,
        Ro = 89,
        Katakana = 90,
        Hiragana = 91,
        Henkan = 92,
        Katakanahiragana = 93,
        Muhenkan = 94,
        KpJpComma = 95,
        KpEnter = 96,
        RightCtrl = 97,
        KpSlash = 98,
        SysRq = 99,
        RightAlt = 100,
        LineFeed = 101,
        Home = 102,
        Up = 103,
        Pageup = 104,
        Left = 105,
        Right = 106,
        End = 107,
        Down = 108,
        Pagedown = 109,
        Insert = 110,
        Delete = 111,
        Macro = 112,
        Mute = 113,
        VolumeDown = 114,
        VolumeUp = 115,
        Power = 116, // SC System Power Down
        KpEqual = 117,
        KpPlusMinus = 118,
        Pause = 119,
        Scale = 120, // AL Compiz Scale (Expose)
    
        KpComma = 121,
        Hangeul = 122,
        Hanja = 123,
        Yen = 124,
        LeftMeta = 125,
        RightMeta = 126,
        Compose = 127,
    
        Stop = 128, // AC Stop
        Again = 129,
        Props = 130, // AC Properties
        Undo = 131, // AC Undo
        Front = 132,
        Copy = 133, // AC Copy
        Open = 134, // AC Open
        Paste = 135, // AC Paste
        Find = 136, // AC Search
        Cut = 137, // AC Cut
        Help = 138, // AL Integrated Help Center
        Menu = 139, // Menu (show menu)
        Calc = 140, // AL Calculator
        Setup = 141,
        Sleep = 142, // SC System Sleep
        Wakeup = 143, // System Wake Up
        File = 144, // AL Local Machine Browser
        Sendfile = 145,
        DeleteFile = 146,
        Xfer = 147,
        Prog1 = 148,
        Prog2 = 149,
        Www = 150, // AL Internet Browser
        MsDos = 151,
        Coffee = 152, // AL Terminal Lock/Screensaver
        RotateDisplay = 153, // Display orientation for e.g. tablets
        CycleWindows = 154,
        Mail = 155,
        Bookmarks = 156, // AC Bookmarks
        Computer = 157,
        Back = 158, // AC Back
        Forward = 159, // AC Forward
        CloseCd = 160,
        EjectCd = 161,
        EjectCloseCd = 162,
        NextSong = 163,
        PlayPause = 164,
        PreviousSong = 165,
        StopCd = 166,
        Record = 167,
        Rewind = 168,
        Phone = 169, // Media Select Telephone
        Iso = 170,
        Config = 171, // AL Consumer Control Configuration
        Homepage = 172, // AC Home
        Refresh = 173, // AC Refresh
        Exit = 174, // AC Exit
        Move = 175,
        Edit = 176,
        ScrollUp = 177,
        ScrollDown = 178,
        KpLeftParen = 179,
        KpRightParen = 180,
        New = 181, // AC New
        Redo = 182, // AC Redo/Repeat
        
        F13 = 183,
        F14 = 184,
        F15 = 185,
        F16 = 186,
        F17 = 187,
        F18 = 188,
        F19 = 189,
        F20 = 190,
        F21 = 191,
        F22 = 192,
        F23 = 193,
        F24 = 194,
        
        PlayCd = 200,
        PauseCd = 201,
        Prog3 = 202,
        Prog4 = 203,
        Dashboard = 204,    // AL Dashboard
        Suspend = 205,
        Close = 206,    // AC Close
        Play = 207,
        FastForward = 208,
        BassBoost = 209,
        Print = 210,    // AC Print
        Hp = 211,
        Camera = 212,
        Sound = 213,
        Question = 214,
        Email = 215,
        Chat = 216,
        Search = 217,
        Connect = 218,
        Finance = 219,  // AL Checkbook/Finance
        Sport = 220,
        Shop = 221,
        AltErase = 222,
        Cancel = 223,   // AC Cancel
        BrightnessDown = 224,
        BrightnessUp = 225,
        Media = 226,
        
        SwitchVideoMode = 227,  // Cycle between available video outputs (Monitor/LCD/TV-out/etc)
        KbdIllumToggle = 228,
        KbdIllumDown = 229,
        KbdIllumUp = 230,
    
        Send = 231, // AC Send
        Reply = 232,    // AC Reply
        ForwardMail = 233,  // AC Forward Msg
        Save = 234, // AC Save
        Documents = 235,
    
        Battery = 236,
    
        Bluetooth = 237,
        Wlan = 238,
        Uwb = 239,
    
        Unknown = 240,
    
        VideoNext = 241,    // drive next video source
        VideoPrev = 242,    // drive previous video source
        BrightnessCycle = 243,  // brightness up, after max is min
        BrightnessAuto = 244,   // Set Auto Brightness: manual brightness control is off, rely on ambient
        DisplayOff = 245,   // display device to off state
    
        Wwan = 246, // Wireless WAN (LTE, UMTS, GSM, etc.)
        RfKill = 247,   // Key that controls all radios
    
        MicMute = 248,  // Mute / unmute the microphone
        LeftMouse = 272,
        RightMouse = 273,
        MiddleMouse = 274,
        MouseBack = 275,
        MouseForward = 276,
        
        ToolFinger = 325,
        ToolQuintTap = 328,
        Touch = 330,
        ToolDoubleTap = 333, 
        ToolTripleTap = 334, 
        ToolQuadTap = 335,
        Mic = 582
    }
    

    鼠标轴.cs

    鼠标移动以移动量和与该变化相关的轴表示。 0 代表 X 轴移动,1 代表 Y 轴移动。

    public enum MouseAxis
    {
        X,
        Y
    }
    

    KeypressEvent.cs

    这是我用来处理按键事件的事件。

    public class KeyPressEvent : EventArgs
    {
        public KeyPressEvent(EventCode code, KeyState state)
        {
            Code = code;
            State = state;
        }
    
        public EventCode Code { get; }
        
        public KeyState State { get; }
    }
    

    MouseMoveEvent.cs

    这是我使用进程鼠标移动更改更新的事件。

    public class MouseMoveEvent : EventArgs
    {
        public MouseMoveEvent(MouseAxis axis, int amount)
        {
            Axis = axis;
            Amount = amount;
        }
        
        public MouseAxis Axis { get; }
        
        public int Amount { get; set; }
    }
    

    InputReader.cs

    这是大部分工作发生的地方。这里我们有一个类,您可以在其中提供一个事件文件的路径,它会在它进入时发布更新。执行此操作的示例文件是“/dev/input/event0”。

    需要更多的研究来支持更多的事件类型,但我只对键盘和鼠标输入感兴趣,所以它符合我的目的。我还选择删除每个按钮事件中包含的时间戳,但如果您有兴趣,可以在缓冲区的前 16 位上找到它。

    public class InputReader : IDisposable
    {
        public delegate void RaiseKeyPress(KeyPressEvent e);
    
        public delegate void RaiseMouseMove(MouseMoveEvent e);
    
        public event RaiseKeyPress OnKeyPress;
        public event RaiseMouseMove OnMouseMove;
    
        private const int BufferLength = 24;
        
        private readonly byte[] _buffer = new byte[BufferLength];
        
        private FileStream _stream;
        private bool _disposing;
    
        public InputReader(string path)
        {
            _stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
    
            Task.Run(Run);
        }
    
        private void Run()
        {
            while (true)
            {
                if (_disposing)
                    break;
    
                _stream.Read(_buffer, 0, BufferLength);
    
                var type = BitConverter.ToInt16(new[] {_buffer[16], _buffer[17]}, 0);
                var code = BitConverter.ToInt16(new[] {_buffer[18], _buffer[19]}, 0);
                var value = BitConverter.ToInt32(new[] {_buffer[20], _buffer[21], _buffer[22], _buffer[23]}, 0);
    
                var eventType = (EventType) type;
    
                switch (eventType)
                {
                    case EventType.EV_KEY:
                        HandleKeyPressEvent(code, value);
                        break;
                    case EventType.EV_REL:
                        var axis = (MouseAxis) code;
                        var e = new MouseMoveEvent(axis, value);
                        OnMouseMove?.Invoke(e);
                        break;
                }
            }
        }
    
        private void HandleKeyPressEvent(short code, int value)
        {
            var c = (EventCode) code;
            var s = (KeyState) value;
            var e = new KeyPressEvent(c, s);
            OnKeyPress?.Invoke(e);
        }
    
        public void Dispose()
        {
            _disposing = true;
            _stream.Dispose();
            _stream = null;
        }
    }
    

    AggregateInputReader.cs

    由于我希望处理来自系统上任何位置的每个设备的输入,我将这些类放在一起以聚合来自“/dev/input”文件夹中所有文件的输入事件。

    已知问题 - 如果 USB 设备在运行时被移除,此代码将引发异常。我确实打算在我自己的应用实现中修复它,但我现在没有时间处理它。

    public class AggregateInputReader : IDisposable
    {
        private List<InputReader> _readers = new();
        
        public event InputReader.RaiseKeyPress OnKeyPress;
    
        public AggregateInputReader()
        {
            var files = Directory.GetFiles("/dev/input/", "event*");
    
            foreach (var file in files)
            {
                var reader = new InputReader(file);
                
                reader.OnKeyPress += ReaderOnOnKeyPress;
    
                _readers.Add(reader);
            }
        }
    
        private void ReaderOnOnKeyPress(KeyPressEvent e)
        {
            OnKeyPress?.Invoke(e);
        }
    
        public void Dispose()
        {
            foreach (var d in _readers)
            {
                d.OnKeyPress -= ReaderOnOnKeyPress;
                d.Dispose();
            }
    
            _readers = null;
        }
    }
    

    示例用法

    现在可以用两行代码完成,这还不错。

    public class Program
    {
        public static void Main(string[] args)
        {
            using var aggHandler = new AggregateInputReader();
    
            aggHandler.OnKeyPress += (e) => { System.Console.WriteLine($"Code:{e.Code} State:{e.State}"); };
    
            System.Console.ReadLine();
        }
    }
    

    感谢您坚持这一点。我希望它对你有用!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-08-17
      • 2010-12-10
      • 1970-01-01
      • 2010-10-23
      • 1970-01-01
      • 1970-01-01
      • 2013-01-18
      相关资源
      最近更新 更多