Feedback

C# - Caputere Keyboard-Input

Veröffentlicht von am 30.10.2016
(0 Bewertungen)
Um die Tastatureingaben des Benutzers abzufangen ist die gängigste Technik, mittels Hooks zu arbeiten.
Allerdings haben diese auch einige Nachteile, unter Anderem lassen diese sich stoppen und umgehen.

Deshalb biete ich hier eine Klassen-Sammlung, die ALLE Tasteneingaben des Benutzers auffängt.
* Hinweis: Für meine Zwecke hatte ich die Klassen so erstellt, dass diese nach bestimmten Tastendrücken suchen und im Falle des Drückens eine Funktion (Callback) aufrufen.

Wenn aber eine Aufzeichnung aller Tastendrücke gewünscht ist, bitte die Klasse <KeyboardSnifferService> anpassen. Die Funktion <void CallbackProc(__KeyEventData d)> in dieser Klasse empfängt ALLE Tastaturvorgänge.

Beachten: d.Message kann einen der folgenden Werte haben:
__Win32.WM_SYSKEYDOWN, __Win32.WM_KEYDOWN, __Win32.WM_SYSKEYUP, __Win32.WM_KEYUP,
und d.VKey gibt einen Virtual-Key an.
Nachzulesen sind diese hier:
https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx

Hinweise:
* Die Eingaben sind unter Umständen nur für den AKTUELLEN DESKTOP gültig.
Nicht getestet! Bitte beachten, wenn CreateDesktop() und SwitchDesktop() der WinAPI verwendet werden!
* Weiterhin scheinen einige Spiele (z.B. mit Unity erstellte) den Input zu verhindern. Wenn das Spiel startet reagiert das System nicht mehr auf die Tasten. Beim beenden des Spiels ist wieder alles wie vorher.
* Dieser Code muss ein gültiges Handle eines Fensters erhalten. Wenn der Code aus einer WPF-Anwendung heraus läuft, einfach das MainWindow als Argument für <KeyboardSnifferService.KeyboardSnifferService(Window)> übergeben.
Bei Winforms den zweiten Konstruktor verwenden, der nimmt das reine IntPtr-Handle entgegen.

Fragen und Anregungen gerne hier stellen!

Beispiel zur Nutzung:

var _KeySniffer = new KeyboardSnifferService(this);
// Hinweis: this muss ein WPF-Window für die App sein.
namespace KeySniffer
{
 

    internal delegate void KeyEventActivated();

    internal sealed class KeyboardSnifferService 
    // Version 2!
    {
        public const int _NO_KEY = 0;

        IntPtr _winhandle;
        __SnifferServiceControlServer _sniffer;

        public KeyboardSnifferService(Window parent)
        {
            _winhandle = (new WindowInteropHelper(parent)).Handle;
            _sniffer = new __SnifferServiceControlServer(_winhandle);
            _sniffer.SetOnKeyEventRaisedCallback(CallbackProc);
        }

        public KeyboardSnifferService(IntPtr parent)
        {
            _sniffer = new __SnifferServiceControlServer(_parent);
        _sniffer.SetOnKeyEventRaisedCallback(CallbackProc);
        }

        void CallbackProc(__KeyEventData d)
        {
           // Mach was...
           // Zum Beispiel ein Callback, oder ein Event, oder direkt einen Vorgang laufen lassen... 
        }
    }

    internal delegate void __OnKeyEventRaised(__KeyEventData data);

    internal class __SnifferServiceControlServer : NativeWindow
    {
        static __RawKeyboard _keyboardDriver;
        readonly IntPtr _devNotifyHandle;
        static readonly Guid DeviceInterfaceHid = new Guid("4D1E55B2-F16F-11CF-88CB-001111000030");
        __OnKeyEventRaised _callback;

        public void SetOnKeyEventRaisedCallback(__OnKeyEventRaised cb)
        {
            _callback = cb;
        }

        public __SnifferServiceControlServer(IntPtr parentHandle)
        {
            AssignHandle(parentHandle);

            _keyboardDriver = new __RawKeyboard(parentHandle);
            _keyboardDriver.EnumerateDevices();
            _devNotifyHandle = RegisterForDeviceNotifications(parentHandle);

            _keyboardDriver.KeyPressed += _keyboardDriver_KeyPressed;
        }

        private void _keyboardDriver_KeyPressed(object sender, __KeyEventData e)
        {
            if (_callback != null)
                _callback(e);
        }

        static IntPtr RegisterForDeviceNotifications(IntPtr parent)
        {
            var usbNotifyHandle = IntPtr.Zero;
            var bdi = new __BroadcastDeviceInterface();
            bdi.dbcc_size = Marshal.SizeOf(bdi);
            bdi.BroadcastDeviceType = __BroadcastDeviceType.DBT_DEVTYP_DEVICEINTERFACE;
            bdi.dbcc_classguid = DeviceInterfaceHid;

            var mem = IntPtr.Zero;
            try
            {
                mem = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(__BroadcastDeviceInterface)));
                Marshal.StructureToPtr(bdi, mem, false);
                usbNotifyHandle = __Win32.RegisterDeviceNotification(parent, mem, __DeviceNotification.DEVICE_NOTIFY_WINDOW_HANDLE);
            }
            catch (Exception e)
            {
            }
            finally
            {
                Marshal.FreeHGlobal(mem);
            }

            if (usbNotifyHandle == IntPtr.Zero)
            {
            }

            return usbNotifyHandle;
        }

        protected override void WndProc(ref Message message)
        {
            switch (message.Msg)
            {
                case __Win32.WM_INPUT:
                    {
                        // Should never get here if you are using PreMessageFiltering
                        _keyboardDriver.ProcessRawInput(message.LParam);
                    }
                    break;

                case __Win32.WM_USB_DEVICECHANGE:
                    {
                        _keyboardDriver.EnumerateDevices();
                    }
                    break;
            }

            if(this.Handle != IntPtr.Zero)
                base.WndProc(ref message);  
        }

        ~__SnifferServiceControlServer()
        {
            __Win32.UnregisterDeviceNotification(_devNotifyHandle);
        }
    }

    internal sealed class __RawKeyboard
    {
        private readonly Dictionary<IntPtr, __KeyEventData> _deviceList = new Dictionary<IntPtr, __KeyEventData>();

        public delegate void DeviceEventHandler(object sender, __KeyEventData e);

        public event DeviceEventHandler KeyPressed;

        readonly object _padLock = new object();
        static __InputData _rawBuffer;

        public __RawKeyboard(IntPtr hwnd)
        {
            var rid = new __RawInputDevice[1];

            rid[0].UsagePage = __HidUsagePage.GENERIC;
            rid[0].Usage = __HidUsage.Keyboard;
            rid[0].Flags = __RawInputDeviceFlags.INPUTSINK;
            rid[0].Target = hwnd;

            if (!__Win32.RegisterRawInputDevices(rid, (uint)rid.Length, (uint)Marshal.SizeOf(rid[0])))
            {
            }
        }

        public void EnumerateDevices()
        {
            lock (_padLock)
            {
                _deviceList.Clear();

                var globalDevice = new __KeyEventData
                {
                };

                //_deviceList.Add(globalDevice.DeviceHandle, globalDevice);

                var numberOfDevices = 0;
                uint deviceCount = 0;
                var dwSize = (Marshal.SizeOf(typeof(__Rawinputdevicelist)));

                if (__Win32.GetRawInputDeviceList(IntPtr.Zero, ref deviceCount, (uint)dwSize) == 0)
                {
                    var pRawInputDeviceList = Marshal.AllocHGlobal((int)(dwSize * deviceCount));
                    __Win32.GetRawInputDeviceList(pRawInputDeviceList, ref deviceCount, (uint)dwSize);

                    for (var i = 0; i < deviceCount; i++)
                    {
                        uint pcbSize = 0;

                        // On Window 8 64bit when compiling against .Net > 3.5 using .ToInt32 you will generate an arithmetic overflow. Leave as it is for 32bit/64bit applications
                        var rid = (__Rawinputdevicelist)Marshal.PtrToStructure(new IntPtr((pRawInputDeviceList.ToInt64() + (dwSize * i))), typeof(__Rawinputdevicelist));

                        __Win32.GetRawInputDeviceInfo(rid.hDevice, __RawInputDeviceInfo.RIDI_DEVICENAME, IntPtr.Zero, ref pcbSize);

                        if (pcbSize <= 0) continue;

                        var pData = Marshal.AllocHGlobal((int)pcbSize);
                        __Win32.GetRawInputDeviceInfo(rid.hDevice, __RawInputDeviceInfo.RIDI_DEVICENAME, pData, ref pcbSize);
                        var deviceName = Marshal.PtrToStringAnsi(pData);

                        if (rid.dwType == __DeviceType.RimTypekeyboard || rid.dwType == __DeviceType.RimTypeHid)
                        {
                            var dInfo = new __KeyEventData();

                            if (!_deviceList.ContainsKey(rid.hDevice))
                            {
                                numberOfDevices++;
                                _deviceList.Add(rid.hDevice, dInfo);
                            }
                        }

                        Marshal.FreeHGlobal(pData);
                    }

                    Marshal.FreeHGlobal(pRawInputDeviceList);
                    return;
                }
            }

            throw new Win32Exception(Marshal.GetLastWin32Error());
        }

        public bool ProcessRawInput(IntPtr hdevice)
        {
            if (_deviceList.Count == 0) return false;

            var dwSize = 0;
            __Win32.GetRawInputData(hdevice, __DataCommand.RID_INPUT, IntPtr.Zero, ref dwSize, Marshal.SizeOf(typeof(__Rawinputheader)));

            if (dwSize != __Win32.GetRawInputData(hdevice, __DataCommand.RID_INPUT, out _rawBuffer, ref dwSize, Marshal.SizeOf(typeof(__Rawinputheader))))
            {
                return false;
            }

            int virtualKey = _rawBuffer.data.keyboard.VKey;
            int makeCode = _rawBuffer.data.keyboard.Makecode;
            int flags = _rawBuffer.data.keyboard.Flags;

            if (virtualKey == __Win32.KEYBOARD_OVERRUN_MAKE_CODE) return false;

            var isE0BitSet = ((flags & __Win32.RI_KEY_E0) != 0);

            __KeyEventData keyPressEvent;

            if (_deviceList.ContainsKey(_rawBuffer.header.hDevice))
            {
                lock (_padLock)
                {
                    keyPressEvent = _deviceList[_rawBuffer.header.hDevice];
                }
            }
            else
            {
                return false;
            }

            keyPressEvent.Message = _rawBuffer.data.keyboard.Message;
            keyPressEvent.VKey = virtualKey;

            if (KeyPressed != null)
            {
                KeyPressed(this, keyPressEvent);
            }

            return true;
        }
    }


    internal class __KeyEventData
    {
        public int VKey;                // Virtual Key. Corrected for L/R keys(i.e. LSHIFT/RSHIFT) and Zoom
        public uint Message;            // WM_KEYDOWN or WM_KEYUP    
    }

        [StructLayout(LayoutKind.Explicit)]
    internal struct __DeviceInfo
    {
        [FieldOffset(0)]
        public int Size;
        [FieldOffset(4)]
        public int Type;
    }

    internal struct __BroadcastDeviceInterface
    {
        public Int32 dbcc_size;
        public __BroadcastDeviceType BroadcastDeviceType;
        Int32 dbcc_reserved; // Korrekt!
        public Guid dbcc_classguid;
        public char dbcc_name; // Korrekt!
    }

    [StructLayout(LayoutKind.Sequential)]
    internal struct __Rawinputdevicelist
    {
        public IntPtr hDevice;
        public uint dwType;
    }

    [StructLayout(LayoutKind.Explicit)]
    internal struct __RawData
    {
        [FieldOffset(0)]
        internal __Rawkeyboard keyboard;
        [FieldOffset(0)]
        internal __Rawhid hid;
    }

    [StructLayout(LayoutKind.Sequential)]
    internal struct __InputData
    {
        public __Rawinputheader header;           // 64 bit header size is 24  32 bit the header size is 16
        public __RawData data;                    // Creating the rest in a struct allows the header size to align correctly for 32 or 64 bit
    }

    [StructLayout(LayoutKind.Sequential)]
    internal struct __Rawinputheader
    {
        public uint dwType;                     // Type of raw input (RIM_TYPEHID 2, RIM_TYPEKEYBOARD 1, RIM_TYPEMOUSE 0)
        public uint dwSize;                     // Size in bytes of the entire input packet of data. This includes RAWINPUT plus possible extra input reports in the RAWHID variable length array. 
        public IntPtr hDevice;                  // A handle to the device generating the raw input data. 
        public IntPtr wParam;                   // RIM_INPUT 0 if input occurred while application was in the foreground else RIM_INPUTSINK 1 if it was not.
    }

    [StructLayout(LayoutKind.Sequential)]
    internal struct __Rawhid
    {
        public uint dwSizHid;
        public uint dwCount;
        public byte bRawData;
    }

    [StructLayout(LayoutKind.Sequential)]
    internal struct __Rawkeyboard
    {
        public ushort Makecode;                 // Scan code from the key depression
        public ushort Flags;                    // One or more of RI_KEY_MAKE, RI_KEY_BREAK, RI_KEY_E0, RI_KEY_E1
        public ushort Reserved;                 // Always 0    
        public ushort VKey;                     // Virtual Key Code
        public uint Message;                    // Corresponding Windows message for exmaple (WM_KEYDOWN, WM_SYASKEYDOWN etc)
        public uint ExtraInformation;           // The device-specific addition information for the event (seems to always be zero for keyboards)
    }

    [StructLayout(LayoutKind.Sequential)]
    internal struct __RawInputDevice
    {
        internal __HidUsagePage UsagePage;
        internal __HidUsage Usage;
        internal __RawInputDeviceFlags Flags;
        internal IntPtr Target;
    }

    internal enum __DataCommand : uint
    {
        RID_HEADER = 0x10000005, // Get the header information from the RAWINPUT structure.
        RID_INPUT = 0x10000003   // Get the raw data from the RAWINPUT structure.
    }

    internal static class __DeviceType
    {
        public const int RimTypemouse = 0;
        public const int RimTypekeyboard = 1;
        public const int RimTypeHid = 2;
    }

    internal enum __RawInputDeviceInfo : uint
    {
        RIDI_DEVICENAME = 0x20000007,
        RIDI_DEVICEINFO = 0x2000000b,
        PREPARSEDDATA = 0x20000005
    }

    internal enum __BroadcastDeviceType
    {
        DBT_DEVTYP_OEM = 0,
        DBT_DEVTYP_DEVNODE = 1,
        DBT_DEVTYP_VOLUME = 2,
        DBT_DEVTYP_PORT = 3,
        DBT_DEVTYP_NET = 4,
        DBT_DEVTYP_DEVICEINTERFACE = 5,
        DBT_DEVTYP_HANDLE = 6,
    }

    internal enum __DeviceNotification
    {
        DEVICE_NOTIFY_WINDOW_HANDLE = 0x00000000,
        DEVICE_NOTIFY_SERVICE_HANDLE = 0x00000001,
        DEVICE_NOTIFY_ALL_INTERFACE_CLASSES = 0x00000004
    }

    [Flags]
    internal enum __RawInputDeviceFlags
    {
        NONE = 0,
        REMOVE = 0x00000001,
        EXCLUDE = 0x00000010,
        PAGEONLY = 0x00000020,
        NOLEGACY = 0x00000030,
        INPUTSINK = 0x00000100,
        CAPTUREMOUSE = 0x00000200,
        NOHOTKEYS = 0x00000200,
        APPKEYS = 0x00000400,
        EXINPUTSINK = 0x00001000,
        DEVNOTIFY = 0x00002000
    }

    internal enum __HidUsagePage : ushort
    {
        UNDEFINED = 0x00,
        GENERIC = 0x01,
        SIMULATION = 0x02,
        VR = 0x03,
        SPORT = 0x04,
        GAME = 0x05,
        KEYBOARD = 0x07,
    }

    internal enum __HidUsage : ushort
    {
        Undefined = 0x00,
        Pointer = 0x01,
        Mouse = 0x02,
        Joystick = 0x04,
        Gamepad = 0x05,
        Keyboard = 0x06,
        Keypad = 0x07,
        SystemControl = 0x80,
        Tablet = 0x80,
        Consumer = 0x0C,
    }

    internal static class __RegistryAccess
    {
        static internal RegistryKey GetDeviceKey(string device)
        {
            var split = device.Substring(4).Split('#');

            var classCode = split[0];       // ACPI (Class code)
            var subClassCode = split[1];    // PNP0303 (SubClass code)
            var protocolCode = split[2];    // 3&13c0b0c5&0 (Protocol code)

            return Registry.LocalMachine.OpenSubKey(string.Format("System\\CurrentControlSet\\Enum\\{0}\\{1}\\{2}", classCode, subClassCode, protocolCode));
        }

        static internal string GetClassType(string classGuid)
        {
            var classGuidKey = Registry.LocalMachine.OpenSubKey("SYSTEM\\CurrentControlSet\\Control\\Class\\" + classGuid);

            return classGuidKey != null ? (string)classGuidKey.GetValue("Class") : string.Empty;
        }
    }

    internal static class __Win32
    {
        public const int KEYBOARD_OVERRUN_MAKE_CODE = 0xFF;

        public const int WM_KEYDOWN = 0x0100;
        public const int WM_KEYUP = 0x0101;
        internal const int WM_SYSKEYDOWN = 0x0104;
        internal const int WM_SYSKEYUP = 0x0105;
        internal const int WM_INPUT = 0x00FF;
        internal const int WM_USB_DEVICECHANGE = 0x0219;

        internal const int VK_SHIFT = 0x10;

        internal const int RI_KEY_MAKE = 0x00;      // Key Down
        internal const int RI_KEY_BREAK = 0x01;     // Key Up
        internal const int RI_KEY_E0 = 0x02;        // Left version of the key
        internal const int RI_KEY_E1 = 0x04;        // Right version of the key. Only seems to be set for the Pause/Break key.

        internal const int VK_CONTROL = 0x11;
        internal const int VK_MENU = 0x12;
        internal const int VK_ZOOM = 0xFB;
        internal const int VK_LSHIFT = 0xA0;
        internal const int VK_RSHIFT = 0xA1;
        internal const int VK_LCONTROL = 0xA2;
        internal const int VK_RCONTROL = 0xA3;
        internal const int VK_LMENU = 0xA4;
        internal const int VK_RMENU = 0xA5;

        internal const int SC_SHIFT_R = 0x36;
        internal const int SC_SHIFT_L = 0x2a;
        internal const int RIM_INPUT = 0x00;

        [DllImport("User32.dll", SetLastError = true)]
        internal static extern int GetRawInputData(IntPtr hRawInput, __DataCommand command, [Out] out __InputData buffer, [In, Out] ref int size, int cbSizeHeader);

        [DllImport("User32.dll", SetLastError = true)]
        internal static extern int GetRawInputData(IntPtr hRawInput, __DataCommand command, [Out] IntPtr pData, [In, Out] ref int size, int sizeHeader);

        [DllImport("User32.dll", SetLastError = true)]
        internal static extern uint GetRawInputDeviceInfo(IntPtr hDevice, __RawInputDeviceInfo command, IntPtr pData, ref uint size);

        [DllImport("user32.dll")]
        public static extern uint GetRawInputDeviceInfo(IntPtr hDevice, uint command, ref __DeviceInfo data, ref uint dataSize);


        [DllImport("User32.dll", SetLastError = true)]
        internal static extern uint GetRawInputDeviceList(IntPtr pRawInputDeviceList, ref uint numberDevices, uint size);

        [DllImport("User32.dll", SetLastError = true)]
        internal static extern bool RegisterRawInputDevices(__RawInputDevice[] pRawInputDevice, uint numberDevices, uint size);

        [DllImport("user32.dll", SetLastError = true)]
        internal static extern IntPtr RegisterDeviceNotification(IntPtr hRecipient, IntPtr notificationFilter, __DeviceNotification flags);

        [DllImport("user32.dll", SetLastError = true)]
        internal static extern bool UnregisterDeviceNotification(IntPtr handle);
    }
}
Abgelegt unter System, API, WPF, RawInput, c#, Win32.

Kommentare zum Snippet

 

Logge dich ein, um hier zu kommentieren!