├── src ├── RawInputProcessor │ ├── KeyPressState.cs │ ├── RawDeviceType.cs │ ├── RawInputCaptureMode.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── RawInputEventArgs.cs │ ├── RawKeyboardDevice.cs │ ├── Win32 │ │ ├── RegistryAccess.cs │ │ ├── Win32Consts.cs │ │ ├── Enumerations.cs │ │ ├── Win32Methods.cs │ │ └── DataStructures.cs │ ├── RawInput.cs │ ├── RawPresentationInput.cs │ ├── RawFormsInput.cs │ ├── RawInputProcessor.csproj │ └── RawKeyboard.cs ├── RawInputProcessor.Demo │ ├── App.xaml.cs │ ├── App.xaml │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── MainWindow.xaml │ ├── MainWindow.xaml.cs │ └── RawInputProcessor.Demo.csproj └── RawInputProcessor.sln ├── LICENSE.txt ├── README.md ├── .gitattributes └── .gitignore /src/RawInputProcessor/KeyPressState.cs: -------------------------------------------------------------------------------- 1 | namespace RawInputProcessor 2 | { 3 | public enum KeyPressState 4 | { 5 | Up, 6 | Down 7 | } 8 | } -------------------------------------------------------------------------------- /src/RawInputProcessor/RawDeviceType.cs: -------------------------------------------------------------------------------- 1 | namespace RawInputProcessor 2 | { 3 | public enum RawDeviceType 4 | { 5 | Mouse, 6 | Keyboard, 7 | Hid 8 | } 9 | } -------------------------------------------------------------------------------- /src/RawInputProcessor/RawInputCaptureMode.cs: -------------------------------------------------------------------------------- 1 | namespace RawInputProcessor 2 | { 3 | public enum RawInputCaptureMode 4 | { 5 | Foreground, 6 | ForegroundAndBackground, 7 | } 8 | } -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The Code Project Open License (CPOL) 2 | http://www.codeproject.com/info/cpol10.aspx 3 | 4 | Forked from 5 | http://www.codeproject.com/Articles/17123/Using-Raw-Input-from-C-to-handle-multiple-keyboard 6 | -------------------------------------------------------------------------------- /src/RawInputProcessor.Demo/App.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace RawInputProcessor.Demo 2 | { 3 | /// 4 | /// Interaction logic for App.xaml 5 | /// 6 | public partial class App 7 | { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | RawInputProcessor 2 | ================= 3 | 4 | Provides a way to distinguish input from multiple keyboards in WPF and Windows Forms. 5 | 6 | Forked from this CodeProject article: 7 | http://www.codeproject.com/Articles/17123/Using-Raw-Input-from-C-to-handle-multiple-keyboard 8 | -------------------------------------------------------------------------------- /src/RawInputProcessor/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | [assembly: AssemblyTitle("RawInputProcessor")] 5 | [assembly: AssemblyDescription("")] 6 | [assembly: AssemblyConfiguration("")] 7 | [assembly: AssemblyCompany("")] 8 | [assembly: AssemblyProduct("RawInputProcessor")] 9 | [assembly: AssemblyCopyright("Copyright © 2014")] 10 | [assembly: AssemblyTrademark("")] 11 | [assembly: AssemblyCulture("")] 12 | 13 | [assembly: ComVisible(false)] 14 | -------------------------------------------------------------------------------- /src/RawInputProcessor/RawInputEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Input; 3 | 4 | namespace RawInputProcessor 5 | { 6 | public sealed class RawInputEventArgs : EventArgs 7 | { 8 | public RawKeyboardDevice Device { get; private set; } 9 | public KeyPressState KeyPressState { get; private set; } 10 | public uint Message { get; private set; } 11 | public Key Key { get; private set; } 12 | public int VirtualKey { get; private set; } 13 | public bool Handled { get; set; } 14 | 15 | internal RawInputEventArgs(RawKeyboardDevice device, KeyPressState keyPressState, uint message, Key key, 16 | int virtualKey) 17 | { 18 | Device = device; 19 | KeyPressState = keyPressState; 20 | Message = message; 21 | Key = key; 22 | VirtualKey = virtualKey; 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /src/RawInputProcessor/RawKeyboardDevice.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RawInputProcessor 4 | { 5 | public sealed class RawKeyboardDevice 6 | { 7 | public string Name { get; private set; } 8 | public RawDeviceType Type { get; private set; } 9 | public IntPtr Handle { get; private set; } 10 | public string Description { get; private set; } 11 | 12 | internal RawKeyboardDevice(string name, RawDeviceType type, IntPtr handle, string description) 13 | { 14 | Handle = handle; 15 | Type = type; 16 | Name = name; 17 | Description = description; 18 | } 19 | 20 | public override string ToString() 21 | { 22 | return string.Format("Device\n Name: {0}\n Type: {1}\n Handle: {2}\n Name: {3}\n", 23 | Name, 24 | Type, 25 | Handle.ToInt64().ToString("X"), 26 | Description); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/RawInputProcessor.Demo/App.xaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 10 | 19 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/RawInputProcessor.Demo/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | using System.Windows; 4 | 5 | [assembly: AssemblyTitle("RawInputProcessor.Demo")] 6 | [assembly: AssemblyDescription("")] 7 | [assembly: AssemblyConfiguration("")] 8 | [assembly: AssemblyCompany("")] 9 | [assembly: AssemblyProduct("RawInputProcessor.Demo")] 10 | [assembly: AssemblyCopyright("Copyright © 2014")] 11 | [assembly: AssemblyTrademark("")] 12 | [assembly: AssemblyCulture("")] 13 | 14 | [assembly: ComVisible(false)] 15 | 16 | [assembly: ThemeInfo( 17 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 18 | //(used if a resource is not found in the page, 19 | // or application resource dictionaries) 20 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 21 | //(used if a resource is not found in the page, 22 | // app, or any theme specific resource dictionaries) 23 | )] 24 | 25 | [assembly: AssemblyVersion("1.0.0.0")] 26 | [assembly: AssemblyFileVersion("1.0.0.0")] 27 | -------------------------------------------------------------------------------- /src/RawInputProcessor/Win32/RegistryAccess.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Win32; 2 | 3 | namespace RawInputProcessor.Win32 4 | { 5 | internal static class RegistryAccess 6 | { 7 | private const string Prefix = @"\\?\"; 8 | 9 | internal static RegistryKey GetDeviceKey(string device) 10 | { 11 | if (device == null || !device.StartsWith(Prefix)) return null; 12 | string[] array = device.Substring(Prefix.Length).Split('#'); 13 | if (array.Length < 3) return null; 14 | return Registry.LocalMachine.OpenSubKey(string.Format(@"System\CurrentControlSet\Enum\{0}\{1}\{2}", 15 | array[0], array[1], array[2])); 16 | } 17 | 18 | internal static string GetClassType(string classGuid) 19 | { 20 | RegistryKey registryKey = 21 | Registry.LocalMachine.OpenSubKey("SYSTEM\\CurrentControlSet\\Control\\Class\\" + classGuid); 22 | if (registryKey == null) 23 | { 24 | return string.Empty; 25 | } 26 | return (string) registryKey.GetValue("Class"); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/RawInputProcessor/RawInput.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RawInputProcessor 4 | { 5 | public abstract class RawInput : IDisposable 6 | { 7 | private readonly RawKeyboard _keyboardDriver; 8 | 9 | public event EventHandler KeyPressed 10 | { 11 | add { KeyboardDriver.KeyPressed += value; } 12 | remove { KeyboardDriver.KeyPressed -= value; } 13 | } 14 | 15 | public int NumberOfKeyboards 16 | { 17 | get { return KeyboardDriver.NumberOfKeyboards; } 18 | } 19 | 20 | protected RawKeyboard KeyboardDriver 21 | { 22 | get { return _keyboardDriver; } 23 | } 24 | 25 | protected RawInput(IntPtr handle, RawInputCaptureMode captureMode) 26 | { 27 | _keyboardDriver = new RawKeyboard(handle, captureMode == RawInputCaptureMode.Foreground); 28 | } 29 | 30 | public abstract void AddMessageFilter(); 31 | public abstract void RemoveMessageFilter(); 32 | 33 | public void Dispose() 34 | { 35 | KeyboardDriver.Dispose(); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/RawInputProcessor.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.30723.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RawInputProcessor", "RawInputProcessor\RawInputProcessor.csproj", "{6480E255-35A3-4D61-B48C-F6EFBCAEF891}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RawInputProcessor.Demo", "RawInputProcessor.Demo\RawInputProcessor.Demo.csproj", "{6D678BEC-3454-4ADF-89D9-AAD1ED05C71A}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {6480E255-35A3-4D61-B48C-F6EFBCAEF891}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {6480E255-35A3-4D61-B48C-F6EFBCAEF891}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {6480E255-35A3-4D61-B48C-F6EFBCAEF891}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {6480E255-35A3-4D61-B48C-F6EFBCAEF891}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {6D678BEC-3454-4ADF-89D9-AAD1ED05C71A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {6D678BEC-3454-4ADF-89D9-AAD1ED05C71A}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {6D678BEC-3454-4ADF-89D9-AAD1ED05C71A}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {6D678BEC-3454-4ADF-89D9-AAD1ED05C71A}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /src/RawInputProcessor/Win32/Win32Consts.cs: -------------------------------------------------------------------------------- 1 | namespace RawInputProcessor.Win32 2 | { 3 | internal static class Win32Consts 4 | { 5 | // ReSharper disable InconsistentNaming 6 | internal const int KEYBOARD_OVERRUN_MAKE_CODE = 255; 7 | internal const int WM_APPCOMMAND = 793; 8 | internal const int FAPPCOMMANDMASK = 61440; 9 | internal const int FAPPCOMMANDMOUSE = 32768; 10 | internal const int FAPPCOMMANDOEM = 4096; 11 | internal const int WM_KEYDOWN = 256; 12 | internal const int WM_KEYUP = 257; 13 | internal const int WM_SYSKEYDOWN = 260; 14 | internal const int WM_INPUT = 255; 15 | internal const int WM_USB_DEVICECHANGE = 537; 16 | internal const int WM_INPUT_DEVICE_CHANGE = 254; 17 | internal const int PM_REMOVE = 1; 18 | internal const int VK_SHIFT = 16; 19 | internal const int RI_KEY_MAKE = 0; 20 | internal const int RI_KEY_BREAK = 1; 21 | internal const int RI_KEY_E0 = 2; 22 | internal const int RI_KEY_E1 = 4; 23 | internal const int VK_CONTROL = 17; 24 | internal const int VK_MENU = 18; 25 | internal const int VK_ZOOM = 251; 26 | internal const int VK_LSHIFT = 160; 27 | internal const int VK_RSHIFT = 161; 28 | internal const int VK_LCONTROL = 162; 29 | internal const int VK_RCONTROL = 163; 30 | internal const int VK_LMENU = 164; 31 | internal const int VK_RMENU = 165; 32 | internal const int SC_SHIFT_R = 54; 33 | internal const int SC_SHIFT_L = 42; 34 | internal const int RIM_INPUT = 0; 35 | // ReSharper restore InconsistentNaming 36 | } 37 | } -------------------------------------------------------------------------------- /src/RawInputProcessor/RawPresentationInput.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows; 3 | using System.Windows.Interop; 4 | using System.Windows.Media; 5 | 6 | namespace RawInputProcessor 7 | { 8 | public class RawPresentationInput : RawInput 9 | { 10 | private bool _hasFilter; 11 | 12 | public RawPresentationInput(HwndSource hwndSource, RawInputCaptureMode captureMode) 13 | : base(hwndSource.Handle, captureMode) 14 | { 15 | hwndSource.AddHook(Hook); 16 | } 17 | 18 | public RawPresentationInput(Visual visual, RawInputCaptureMode captureMode) 19 | : this(GetHwndSource(visual), captureMode) 20 | { 21 | } 22 | 23 | private static HwndSource GetHwndSource(Visual visual) 24 | { 25 | var source = PresentationSource.FromVisual(visual) as HwndSource; 26 | if (source == null) 27 | { 28 | throw new InvalidOperationException("Cannot find a valid HwndSource"); 29 | } 30 | return source; 31 | } 32 | 33 | private IntPtr Hook(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam, ref bool handled) 34 | { 35 | KeyboardDriver.HandleMessage(msg, wparam, lparam); 36 | return IntPtr.Zero; 37 | } 38 | 39 | public override void AddMessageFilter() 40 | { 41 | if (_hasFilter) 42 | { 43 | return; 44 | } 45 | ComponentDispatcher.ThreadFilterMessage += OnThreadFilterMessage; 46 | _hasFilter = true; 47 | } 48 | 49 | public override void RemoveMessageFilter() 50 | { 51 | ComponentDispatcher.ThreadFilterMessage -= OnThreadFilterMessage; 52 | _hasFilter = false; 53 | } 54 | 55 | // ReSharper disable once RedundantAssignment 56 | private void OnThreadFilterMessage(ref MSG msg, ref bool handled) 57 | { 58 | handled = KeyboardDriver.HandleMessage(msg.message, msg.wParam, msg.lParam); 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /src/RawInputProcessor.Demo/MainWindow.xaml: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 17 | 19 | 20 | 21 | 22 | 24 | 26 | 27 | 28 | 29 | 31 | 32 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/RawInputProcessor.Demo/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Runtime.CompilerServices; 4 | 5 | namespace RawInputProcessor.Demo 6 | { 7 | /// 8 | /// Interaction logic for MainWindow.xaml 9 | /// 10 | public partial class MainWindow : INotifyPropertyChanged 11 | { 12 | private RawPresentationInput _rawInput; 13 | private int _deviceCount; 14 | private RawInputEventArgs _event; 15 | 16 | public MainWindow() 17 | { 18 | DataContext = this; 19 | InitializeComponent(); 20 | } 21 | 22 | public int DeviceCount 23 | { 24 | get { return _deviceCount; } 25 | set 26 | { 27 | _deviceCount = value; 28 | OnPropertyChanged(); 29 | } 30 | } 31 | 32 | public RawInputEventArgs Event 33 | { 34 | get { return _event; } 35 | set 36 | { 37 | _event = value; 38 | OnPropertyChanged(); 39 | } 40 | } 41 | 42 | private void OnKeyPressed(object sender, RawInputEventArgs e) 43 | { 44 | Event = e; 45 | DeviceCount = _rawInput.NumberOfKeyboards; 46 | e.Handled = (ShouldHandle.IsChecked == true); 47 | } 48 | 49 | protected override void OnSourceInitialized(EventArgs e) 50 | { 51 | StartWndProcHandler(); 52 | base.OnSourceInitialized(e); 53 | } 54 | 55 | private void StartWndProcHandler() 56 | { 57 | _rawInput = new RawPresentationInput(this, RawInputCaptureMode.Foreground); 58 | _rawInput.KeyPressed += OnKeyPressed; 59 | DeviceCount = _rawInput.NumberOfKeyboards; 60 | } 61 | 62 | public event PropertyChangedEventHandler PropertyChanged; 63 | 64 | protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) 65 | { 66 | PropertyChangedEventHandler propertyChanged = PropertyChanged; 67 | if (propertyChanged != null) 68 | { 69 | propertyChanged(this, new PropertyChangedEventArgs(propertyName)); 70 | } 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /src/RawInputProcessor/RawFormsInput.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Forms; 3 | 4 | namespace RawInputProcessor 5 | { 6 | public class RawFormsInput : RawInput 7 | { 8 | // ReSharper disable once NotAccessedField.Local 9 | private RawInputNativeWindow _window; 10 | private PreMessageFilter _filter; 11 | 12 | public override void AddMessageFilter() 13 | { 14 | if (_filter != null) 15 | { 16 | return; 17 | } 18 | _filter = new PreMessageFilter(this); 19 | Application.AddMessageFilter(_filter); 20 | } 21 | 22 | public override void RemoveMessageFilter() 23 | { 24 | if (_filter == null) 25 | { 26 | return; 27 | } 28 | Application.RemoveMessageFilter(_filter); 29 | } 30 | 31 | public RawFormsInput(IntPtr parentHandle, RawInputCaptureMode captureMode) 32 | : base(parentHandle, captureMode) 33 | { 34 | _window = new RawInputNativeWindow(this, parentHandle); 35 | } 36 | 37 | public RawFormsInput(IWin32Window window, RawInputCaptureMode captureMode) 38 | : this(window.Handle, captureMode) 39 | { 40 | } 41 | 42 | private class PreMessageFilter : IMessageFilter 43 | { 44 | private readonly RawFormsInput _rawFormsInput; 45 | 46 | public PreMessageFilter(RawFormsInput rawFormsInput) 47 | { 48 | _rawFormsInput = rawFormsInput; 49 | } 50 | 51 | public bool PreFilterMessage(ref Message m) 52 | { 53 | return _rawFormsInput.KeyboardDriver.HandleMessage(m.Msg, m.WParam, m.LParam); 54 | } 55 | } 56 | 57 | private class RawInputNativeWindow : NativeWindow 58 | { 59 | private readonly RawFormsInput _rawFormsInput; 60 | 61 | public RawInputNativeWindow(RawFormsInput rawFormsInput, IntPtr parentHandle) 62 | { 63 | _rawFormsInput = rawFormsInput; 64 | AssignHandle(parentHandle); 65 | } 66 | 67 | protected override void WndProc(ref Message message) 68 | { 69 | _rawFormsInput.KeyboardDriver.HandleMessage(message.Msg, message.WParam, message.LParam); 70 | base.WndProc(ref message); 71 | } 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /src/RawInputProcessor/RawInputProcessor.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {6480E255-35A3-4D61-B48C-F6EFBCAEF891} 5 | Debug 6 | AnyCPU 7 | Library 8 | RawInputProcessor 9 | v4.5 10 | 4 11 | 12 | 13 | AnyCPU 14 | 15 | 16 | bin\Debug\ 17 | true 18 | full 19 | false 20 | 21 | 22 | bin\Release\ 23 | true 24 | pdbonly 25 | true 26 | 27 | 28 | RawInputProcessor 29 | 30 | 31 | TRACE;DEBUG 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | 9 | # Build results 10 | [Dd]ebug/ 11 | [Dd]ebugPublic/ 12 | [Rr]elease/ 13 | [Rr]eleases/ 14 | x64/ 15 | x86/ 16 | build/ 17 | bld/ 18 | [Bb]in/ 19 | [Oo]bj/ 20 | 21 | # Roslyn cache directories 22 | *.ide/ 23 | 24 | # MSTest test Results 25 | [Tt]est[Rr]esult*/ 26 | [Bb]uild[Ll]og.* 27 | 28 | #NUNIT 29 | *.VisualState.xml 30 | TestResult.xml 31 | 32 | # Build Results of an ATL Project 33 | [Dd]ebugPS/ 34 | [Rr]eleasePS/ 35 | dlldata.c 36 | 37 | *_i.c 38 | *_p.c 39 | *_i.h 40 | *.ilk 41 | *.meta 42 | *.obj 43 | *.pch 44 | *.pdb 45 | *.pgc 46 | *.pgd 47 | *.rsp 48 | *.sbr 49 | *.tlb 50 | *.tli 51 | *.tlh 52 | *.tmp 53 | *.tmp_proj 54 | *.log 55 | *.vspscc 56 | *.vssscc 57 | .builds 58 | *.pidb 59 | *.svclog 60 | *.scc 61 | 62 | # Chutzpah Test files 63 | _Chutzpah* 64 | 65 | # Visual C++ cache files 66 | ipch/ 67 | *.aps 68 | *.ncb 69 | *.opensdf 70 | *.sdf 71 | *.cachefile 72 | 73 | # Visual Studio profiler 74 | *.psess 75 | *.vsp 76 | *.vspx 77 | 78 | # TFS 2012 Local Workspace 79 | $tf/ 80 | 81 | # Guidance Automation Toolkit 82 | *.gpState 83 | 84 | # ReSharper is a .NET coding add-in 85 | _ReSharper*/ 86 | *.[Rr]e[Ss]harper 87 | *.DotSettings.user 88 | 89 | # JustCode is a .NET coding addin-in 90 | .JustCode 91 | 92 | # TeamCity is a build add-in 93 | _TeamCity* 94 | 95 | # DotCover is a Code Coverage Tool 96 | *.dotCover 97 | 98 | # NCrunch 99 | _NCrunch_* 100 | .*crunch*.local.xml 101 | 102 | # MightyMoose 103 | *.mm.* 104 | AutoTest.Net/ 105 | 106 | # Web workbench (sass) 107 | .sass-cache/ 108 | 109 | # Installshield output folder 110 | [Ee]xpress/ 111 | 112 | # DocProject is a documentation generator add-in 113 | DocProject/buildhelp/ 114 | DocProject/Help/*.HxT 115 | DocProject/Help/*.HxC 116 | DocProject/Help/*.hhc 117 | DocProject/Help/*.hhk 118 | DocProject/Help/*.hhp 119 | DocProject/Help/Html2 120 | DocProject/Help/html 121 | 122 | # Click-Once directory 123 | publish/ 124 | 125 | # Publish Web Output 126 | *.[Pp]ublish.xml 127 | *.azurePubxml 128 | # TODO: Comment the next line if you want to checkin your web deploy settings 129 | # but database connection strings (with potential passwords) will be unencrypted 130 | *.pubxml 131 | *.publishproj 132 | 133 | # NuGet Packages 134 | *.nupkg 135 | # The packages folder can be ignored because of Package Restore 136 | **/packages/* 137 | # except build/, which is used as an MSBuild target. 138 | !**/packages/build/ 139 | # If using the old MSBuild-Integrated Package Restore, uncomment this: 140 | #!**/packages/repositories.config 141 | 142 | # Windows Azure Build Output 143 | csx/ 144 | *.build.csdef 145 | 146 | # Windows Store app package directory 147 | AppPackages/ 148 | 149 | # Others 150 | sql/ 151 | *.Cache 152 | ClientBin/ 153 | [Ss]tyle[Cc]op.* 154 | ~$* 155 | *~ 156 | *.dbmdl 157 | *.dbproj.schemaview 158 | *.pfx 159 | *.publishsettings 160 | node_modules/ 161 | 162 | # RIA/Silverlight projects 163 | Generated_Code/ 164 | 165 | # Backup & report files from converting an old project file 166 | # to a newer Visual Studio version. Backup files are not needed, 167 | # because we have git ;-) 168 | _UpgradeReport_Files/ 169 | Backup*/ 170 | UpgradeLog*.XML 171 | UpgradeLog*.htm 172 | 173 | # SQL Server files 174 | *.mdf 175 | *.ldf 176 | 177 | # Business Intelligence projects 178 | *.rdl.data 179 | *.bim.layout 180 | *.bim_*.settings 181 | 182 | # Microsoft Fakes 183 | FakesAssemblies/ 184 | -------------------------------------------------------------------------------- /src/RawInputProcessor.Demo/RawInputProcessor.Demo.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {6D678BEC-3454-4ADF-89D9-AAD1ED05C71A} 8 | WinExe 9 | Properties 10 | RawInputProcessor.Demo 11 | RawInputProcessor.Demo 12 | v4.5 13 | 512 14 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 15 | 4 16 | 17 | 18 | AnyCPU 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | 27 | 28 | AnyCPU 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 4.0 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | MSBuild:Compile 54 | Designer 55 | 56 | 57 | MSBuild:Compile 58 | Designer 59 | 60 | 61 | App.xaml 62 | Code 63 | 64 | 65 | MainWindow.xaml 66 | Code 67 | 68 | 69 | 70 | 71 | Code 72 | 73 | 74 | 75 | 76 | 77 | {6480E255-35A3-4D61-B48C-F6EFBCAEF891} 78 | RawInputProcessor 79 | 80 | 81 | 82 | 89 | -------------------------------------------------------------------------------- /src/RawInputProcessor/Win32/Enumerations.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | // ReSharper disable InconsistentNaming 4 | namespace RawInputProcessor.Win32 5 | { 6 | internal enum DataCommand : uint 7 | { 8 | RID_HEADER = 0x10000005, // Get the header information from the RAWINPUT structure. 9 | RID_INPUT = 0x10000003 // Get the raw data from the RAWINPUT structure. 10 | } 11 | 12 | internal static class DeviceType 13 | { 14 | public const int RimTypemouse = 0; 15 | public const int RimTypekeyboard = 1; 16 | public const int RimTypeHid = 2; 17 | } 18 | 19 | internal enum RawInputDeviceInfo : uint 20 | { 21 | RIDI_DEVICENAME = 0x20000007, 22 | RIDI_DEVICEINFO = 0x2000000b, 23 | PREPARSEDDATA = 0x20000005 24 | } 25 | 26 | internal enum BroadcastDeviceType 27 | { 28 | DBT_DEVTYP_OEM = 0, 29 | DBT_DEVTYP_DEVNODE = 1, 30 | DBT_DEVTYP_VOLUME = 2, 31 | DBT_DEVTYP_PORT = 3, 32 | DBT_DEVTYP_NET = 4, 33 | DBT_DEVTYP_DEVICEINTERFACE = 5, 34 | DBT_DEVTYP_HANDLE = 6, 35 | } 36 | 37 | internal enum DeviceNotification 38 | { 39 | /// The hRecipient parameter is a window handle. 40 | DEVICE_NOTIFY_WINDOW_HANDLE = 0x00000000, 41 | 42 | /// The hRecipient parameter is a service status handle. 43 | DEVICE_NOTIFY_SERVICE_HANDLE = 0x00000001, 44 | 45 | /// 46 | /// Notifies the recipient of device interface events for all device interface classes. (The dbcc_classguid member is 47 | /// ignored.) 48 | /// This value can be used only if the dbch_devicetype member is DBT_DEVTYP_DEVICEINTERFACE. 49 | /// 50 | DEVICE_NOTIFY_ALL_INTERFACE_CLASSES = 0x00000004 51 | } 52 | 53 | [Flags] 54 | internal enum RawInputDeviceFlags 55 | { 56 | /// No flags. 57 | NONE = 0, 58 | 59 | /// 60 | /// If set, this removes the top level collection from the inclusion list. This tells the operating system to stop 61 | /// reading from a device which matches the top level collection. 62 | /// 63 | REMOVE = 0x00000001, 64 | 65 | /// 66 | /// If set, this specifies the top level collections to exclude when reading a complete usage page. This flag only 67 | /// affects a TLC whose usage page is already specified with PageOnly. 68 | /// 69 | EXCLUDE = 0x00000010, 70 | 71 | /// 72 | /// If set, this specifies all devices whose top level collection is from the specified UsagePage. Note that Usage 73 | /// must be zero. To exclude a particular top level collection, use Exclude. 74 | /// 75 | PAGEONLY = 0x00000020, 76 | 77 | /// 78 | /// If set, this prevents any devices specified by UsagePage or Usage from generating legacy messages. This is 79 | /// only for the mouse and keyboard. 80 | /// 81 | NOLEGACY = 0x00000030, 82 | 83 | /// 84 | /// If set, this enables the caller to receive the input even when the caller is not in the foreground. Note that 85 | /// WindowHandle must be specified. 86 | /// 87 | INPUTSINK = 0x00000100, 88 | 89 | /// If set, the mouse button click does not activate the other window. 90 | CAPTUREMOUSE = 0x00000200, 91 | 92 | /// 93 | /// If set, the application-defined keyboard device hotkeys are not handled. However, the system hotkeys; for 94 | /// example, ALT+TAB and CTRL+ALT+DEL, are still handled. By default, all keyboard hotkeys are handled. NoHotKeys can 95 | /// be specified even if NoLegacy is not specified and WindowHandle is NULL. 96 | /// 97 | NOHOTKEYS = 0x00000200, 98 | 99 | /// If set, application keys are handled. NoLegacy must be specified. Keyboard only. 100 | APPKEYS = 0x00000400, 101 | 102 | /// 103 | /// If set, this enables the caller to receive input in the background only if the foreground application 104 | /// does not process it. In other words, if the foreground application is not registered for raw input, 105 | /// then the background application that is registered will receive the input. 106 | /// 107 | EXINPUTSINK = 0x00001000, 108 | DEVNOTIFY = 0x00002000 109 | } 110 | 111 | internal enum HidUsagePage : ushort 112 | { 113 | /// Unknown usage page. 114 | UNDEFINED = 0x00, 115 | 116 | /// Generic desktop controls. 117 | GENERIC = 0x01, 118 | 119 | /// Simulation controls. 120 | SIMULATION = 0x02, 121 | 122 | /// Virtual reality controls. 123 | VR = 0x03, 124 | 125 | /// Sports controls. 126 | SPORT = 0x04, 127 | 128 | /// Games controls. 129 | GAME = 0x05, 130 | 131 | /// Keyboard controls. 132 | KEYBOARD = 0x07, 133 | } 134 | 135 | internal enum HidUsage : ushort 136 | { 137 | /// Unknown usage. 138 | Undefined = 0x00, 139 | 140 | /// Pointer 141 | Pointer = 0x01, 142 | 143 | /// Mouse 144 | Mouse = 0x02, 145 | 146 | /// Joystick 147 | Joystick = 0x04, 148 | 149 | /// Game Pad 150 | Gamepad = 0x05, 151 | 152 | /// Keyboard 153 | Keyboard = 0x06, 154 | 155 | /// Keypad 156 | Keypad = 0x07, 157 | 158 | /// Muilt-axis Controller 159 | SystemControl = 0x80, 160 | 161 | /// Tablet PC controls 162 | Tablet = 0x80, 163 | 164 | /// Consumer 165 | Consumer = 0x0C, 166 | } 167 | } 168 | // ReSharper restore InconsistentNaming 169 | -------------------------------------------------------------------------------- /src/RawInputProcessor/Win32/Win32Methods.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Runtime.InteropServices; 4 | using System.Text; 5 | using System.Windows.Interop; 6 | 7 | namespace RawInputProcessor.Win32 8 | { 9 | internal static class Win32Methods 10 | { 11 | [DllImport("user32")] 12 | [return: MarshalAs(UnmanagedType.Bool)] 13 | public static extern bool PeekMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax, 14 | uint wRemoveMsg); 15 | 16 | [DllImport("user32", SetLastError = true)] 17 | public static extern int GetRawInputData(IntPtr hRawInput, DataCommand command, out InputData buffer, 18 | [In] [Out] ref int size, int cbSizeHeader); 19 | 20 | [DllImport("user32", SetLastError = true)] 21 | public static extern int GetRawInputData(IntPtr hRawInput, DataCommand command, [Out] IntPtr pData, 22 | [In] [Out] ref int size, int sizeHeader); 23 | 24 | [DllImport("user32", SetLastError = true)] 25 | public static extern uint GetRawInputDeviceInfo(IntPtr hDevice, RawInputDeviceInfo command, IntPtr pData, 26 | ref uint size); 27 | 28 | [DllImport("user32")] 29 | public static extern uint GetRawInputDeviceInfo(IntPtr hDevice, uint command, ref DeviceInfo data, 30 | ref uint dataSize); 31 | 32 | [DllImport("user32", SetLastError = true)] 33 | public static extern uint GetRawInputDeviceList(IntPtr pRawInputDeviceList, ref uint numberDevices, uint size); 34 | 35 | [DllImport("user32", SetLastError = true)] 36 | public static extern bool RegisterRawInputDevices(RawInputDevice[] pRawInputDevice, uint numberDevices, 37 | uint size); 38 | 39 | [DllImport("user32", SetLastError = true)] 40 | public static extern IntPtr RegisterDeviceNotification(IntPtr hRecipient, IntPtr notificationFilter, 41 | DeviceNotification flags); 42 | 43 | [DllImport("user32", SetLastError = true)] 44 | public static extern bool UnregisterDeviceNotification(IntPtr handle); 45 | 46 | public static int LoWord(int dwValue) 47 | { 48 | return (dwValue & 0xFFFF); 49 | } 50 | 51 | public static int HiWord(Int64 dwValue) 52 | { 53 | return (int)(dwValue >> 16) & ~Win32Consts.FAPPCOMMANDMASK; 54 | } 55 | 56 | public static ushort LowWord(uint val) 57 | { 58 | return (ushort)val; 59 | } 60 | 61 | public static ushort HighWord(uint val) 62 | { 63 | return (ushort)(val >> 16); 64 | } 65 | 66 | public static uint BuildWParam(ushort low, ushort high) 67 | { 68 | return ((uint)high << 16) | low; 69 | } 70 | 71 | public static string GetDeviceDiagnostics() 72 | { 73 | var stringBuilder = new StringBuilder(); 74 | uint devices = 0u; 75 | int listSize = Marshal.SizeOf(typeof(RawInputDeviceList)); 76 | if (GetRawInputDeviceList(IntPtr.Zero, ref devices, (uint)listSize) != 0u) 77 | { 78 | throw new Win32Exception(Marshal.GetLastWin32Error()); 79 | } 80 | var deviceListPtr = Marshal.AllocHGlobal((int)(listSize * devices)); 81 | try 82 | { 83 | GetRawInputDeviceList(deviceListPtr, ref devices, (uint)listSize); 84 | int index = 0; 85 | while (index < devices) 86 | { 87 | uint pcbSize = 0u; 88 | var rawInputDeviceList = (RawInputDeviceList)Marshal.PtrToStructure(new IntPtr(deviceListPtr.ToInt64() + listSize * index), typeof(RawInputDeviceList)); 89 | GetRawInputDeviceInfo(rawInputDeviceList.hDevice, RawInputDeviceInfo.RIDI_DEVICENAME, IntPtr.Zero, ref pcbSize); 90 | if (pcbSize <= 0u) 91 | { 92 | stringBuilder.AppendLine("pcbSize: " + pcbSize); 93 | stringBuilder.AppendLine(Marshal.GetLastWin32Error().ToString()); 94 | string result = stringBuilder.ToString(); 95 | return result; 96 | } 97 | var deviceInfoSize = (uint)Marshal.SizeOf(typeof(DeviceInfo)); 98 | var deviceInfo = new DeviceInfo 99 | { 100 | Size = Marshal.SizeOf(typeof(DeviceInfo)) 101 | }; 102 | if (GetRawInputDeviceInfo(rawInputDeviceList.hDevice, 536870923u, ref deviceInfo, ref deviceInfoSize) <= 0u) 103 | { 104 | stringBuilder.AppendLine(Marshal.GetLastWin32Error().ToString()); 105 | string result = stringBuilder.ToString(); 106 | return result; 107 | } 108 | var deviceInfoPtr = Marshal.AllocHGlobal((int)pcbSize); 109 | try 110 | { 111 | GetRawInputDeviceInfo(rawInputDeviceList.hDevice, RawInputDeviceInfo.RIDI_DEVICENAME, deviceInfoPtr, 112 | ref pcbSize); 113 | string device = Marshal.PtrToStringAnsi(deviceInfoPtr); 114 | if (rawInputDeviceList.dwType == DeviceType.RimTypekeyboard || 115 | rawInputDeviceList.dwType == DeviceType.RimTypeHid) 116 | { 117 | string deviceDescription = GetDeviceDescription(device); 118 | var rawKeyboardDevice = new RawKeyboardDevice(Marshal.PtrToStringAnsi(deviceInfoPtr), 119 | (RawDeviceType)rawInputDeviceList.dwType, rawInputDeviceList.hDevice, deviceDescription); 120 | stringBuilder.AppendLine(rawKeyboardDevice.ToString()); 121 | stringBuilder.AppendLine(deviceInfo.ToString()); 122 | stringBuilder.AppendLine(deviceInfo.KeyboardInfo.ToString()); 123 | stringBuilder.AppendLine(deviceInfo.HIDInfo.ToString()); 124 | } 125 | } 126 | finally 127 | { 128 | Marshal.FreeHGlobal(deviceInfoPtr); 129 | } 130 | index++; 131 | } 132 | } 133 | finally 134 | { 135 | Marshal.FreeHGlobal(deviceListPtr); 136 | } 137 | return stringBuilder.ToString(); 138 | } 139 | 140 | public static string GetDeviceDescription(string device) 141 | { 142 | var deviceKey = RegistryAccess.GetDeviceKey(device); 143 | if (deviceKey == null) return string.Empty; 144 | 145 | string text = deviceKey.GetValue("DeviceDesc").ToString(); 146 | return text.Substring(text.IndexOf(';') + 1); 147 | } 148 | 149 | public static bool InputInForeground(IntPtr wparam) 150 | { 151 | return wparam.ToInt32() == 0; 152 | } 153 | } 154 | } -------------------------------------------------------------------------------- /src/RawInputProcessor/Win32/DataStructures.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace RawInputProcessor.Win32 5 | { 6 | [StructLayout(LayoutKind.Explicit)] 7 | internal struct DeviceInfo 8 | { 9 | [FieldOffset(0)] public int Size; 10 | [FieldOffset(4)] public int Type; 11 | 12 | [FieldOffset(8)] public DeviceInfoMouse MouseInfo; 13 | [FieldOffset(8)] public DeviceInfoKeyboard KeyboardInfo; 14 | [FieldOffset(8)] public DeviceInfoHid HIDInfo; 15 | 16 | public override string ToString() 17 | { 18 | return string.Format("DeviceInfo\n Size: {0}\n Type: {1}\n", Size, Type); 19 | } 20 | } 21 | 22 | [StructLayout(LayoutKind.Sequential)] 23 | internal struct DeviceInfoMouse 24 | { 25 | public uint Id; // Identifier of the mouse device 26 | public uint NumberOfButtons; // Number of buttons for the mouse 27 | public uint SampleRate; // Number of data points per second. 28 | public bool HasHorizontalWheel; // True is mouse has wheel for horizontal scrolling else false. 29 | 30 | public override string ToString() 31 | { 32 | return string.Format( 33 | "MouseInfo\n Id: {0}\n NumberOfButtons: {1}\n SampleRate: {2}\n HorizontalWheel: {3}\n", Id, 34 | NumberOfButtons, SampleRate, HasHorizontalWheel); 35 | } 36 | } 37 | 38 | [StructLayout(LayoutKind.Sequential)] 39 | internal struct DeviceInfoKeyboard 40 | { 41 | public uint Type; // Type of the keyboard 42 | public uint SubType; // Subtype of the keyboard 43 | public uint KeyboardMode; // The scan code mode 44 | public uint NumberOfFunctionKeys; // Number of function keys on the keyboard 45 | public uint NumberOfIndicators; // Number of LED indicators on the keyboard 46 | public uint NumberOfKeysTotal; // Total number of keys on the keyboard 47 | 48 | public override string ToString() 49 | { 50 | return 51 | string.Format( 52 | "DeviceInfoKeyboard\n Type: {0}\n SubType: {1}\n KeyboardMode: {2}\n NumberOfFunctionKeys: {3}\n NumberOfIndicators {4}\n NumberOfKeysTotal: {5}\n", 53 | Type, SubType, KeyboardMode, NumberOfFunctionKeys, NumberOfIndicators, NumberOfKeysTotal); 54 | } 55 | } 56 | 57 | [StructLayout(LayoutKind.Sequential)] 58 | internal struct DeviceInfoHid 59 | { 60 | public uint VendorID; // Vendor identifier for the HID 61 | public uint ProductID; // Product identifier for the HID 62 | public uint VersionNumber; // Version number for the device 63 | public ushort UsagePage; // Top-level collection Usage page for the device 64 | public ushort Usage; // Top-level collection Usage for the device 65 | 66 | public override string ToString() 67 | { 68 | return 69 | string.Format( 70 | "HidInfo\n VendorID: {0}\n ProductID: {1}\n VersionNumber: {2}\n UsagePage: {3}\n Usage: {4}\n", 71 | VendorID, ProductID, VersionNumber, UsagePage, Usage); 72 | } 73 | } 74 | 75 | [StructLayout(LayoutKind.Sequential)] 76 | internal struct BroadcastDeviceInterface 77 | { 78 | public Int32 dbcc_size; 79 | public BroadcastDeviceType BroadcastDeviceType; 80 | private readonly Int32 dbcc_reserved; 81 | public Guid dbcc_classguid; 82 | public char dbcc_name; 83 | } 84 | 85 | [StructLayout(LayoutKind.Sequential)] 86 | internal struct RawInputDeviceList 87 | { 88 | public IntPtr hDevice; 89 | public uint dwType; 90 | } 91 | 92 | [StructLayout(LayoutKind.Explicit)] 93 | internal struct RawData 94 | { 95 | [FieldOffset(0)] internal Rawmouse mouse; 96 | [FieldOffset(0)] internal Rawkeyboard keyboard; 97 | [FieldOffset(0)] internal Rawhid hid; 98 | } 99 | 100 | [StructLayout(LayoutKind.Sequential)] 101 | internal struct InputData 102 | { 103 | public RawInputHeader header; // 64 bit header size is 24 32 bit the header size is 16 104 | public RawData data; // Creating the rest in a struct allows the header size to align correctly for 32 or 64 bit 105 | } 106 | 107 | [StructLayout(LayoutKind.Sequential)] 108 | internal struct RawInputHeader 109 | { 110 | public uint dwType; // Type of raw input (RIM_TYPEHID 2, RIM_TYPEKEYBOARD 1, RIM_TYPEMOUSE 0) 111 | 112 | public uint dwSize; 113 | // Size in bytes of the entire input packet of data. This includes RAWINPUT plus possible extra input reports in the RAWHID variable length array. 114 | 115 | public IntPtr hDevice; // A handle to the device generating the raw input data. 116 | 117 | public IntPtr wParam; 118 | // RIM_INPUT 0 if input occurred while application was in the foreground else RIM_INPUTSINK 1 if it was not. 119 | 120 | public override string ToString() 121 | { 122 | return string.Format("RawInputHeader\n dwType : {0}\n dwSize : {1}\n hDevice : {2}\n wParam : {3}", dwType, 123 | dwSize, hDevice, wParam); 124 | } 125 | } 126 | 127 | [StructLayout(LayoutKind.Sequential)] 128 | internal struct Rawhid 129 | { 130 | public uint dwSizHid; 131 | public uint dwCount; 132 | public byte bRawData; 133 | 134 | public override string ToString() 135 | { 136 | return string.Format("Rawhib\n dwSizeHid : {0}\n dwCount : {1}\n bRawData : {2}\n", dwSizHid, dwCount, 137 | bRawData); 138 | } 139 | } 140 | 141 | [StructLayout(LayoutKind.Explicit)] 142 | internal struct Rawmouse 143 | { 144 | [FieldOffset(0)] public ushort usFlags; 145 | [FieldOffset(4)] public uint ulButtons; 146 | [FieldOffset(4)] public ushort usButtonFlags; 147 | [FieldOffset(6)] public ushort usButtonData; 148 | [FieldOffset(8)] public uint ulRawButtons; 149 | [FieldOffset(12)] public int lLastX; 150 | [FieldOffset(16)] public int lLastY; 151 | [FieldOffset(20)] public uint ulExtraInformation; 152 | } 153 | 154 | [StructLayout(LayoutKind.Sequential)] 155 | internal struct Rawkeyboard 156 | { 157 | public ushort Makecode; // Scan code from the key depression 158 | public ushort Flags; // One or more of RI_KEY_MAKE, RI_KEY_BREAK, RI_KEY_E0, RI_KEY_E1 159 | public ushort Reserved; // Always 0 160 | public ushort VKey; // Virtual Key Code 161 | public uint Message; // Corresponding Windows message for exmaple (WM_KEYDOWN, WM_SYASKEYDOWN etc) 162 | 163 | public uint ExtraInformation; 164 | // The device-specific addition information for the event (seems to always be zero for keyboards) 165 | 166 | public override string ToString() 167 | { 168 | return 169 | string.Format( 170 | "Rawkeyboard\n Makecode: {0}\n Makecode(hex) : {0:X}\n Flags: {1}\n Reserved: {2}\n VKeyName: {3}\n Message: {4}\n ExtraInformation {5}\n", 171 | Makecode, Flags, Reserved, VKey, Message, ExtraInformation); 172 | } 173 | } 174 | 175 | [StructLayout(LayoutKind.Sequential)] 176 | internal struct RawInputDevice 177 | { 178 | internal HidUsagePage UsagePage; 179 | internal HidUsage Usage; 180 | internal RawInputDeviceFlags Flags; 181 | internal IntPtr Target; 182 | 183 | public override string ToString() 184 | { 185 | return string.Format("{0}/{1}, flags: {2}, target: {3}", UsagePage, Usage, Flags, Target); 186 | } 187 | } 188 | } -------------------------------------------------------------------------------- /src/RawInputProcessor/RawKeyboard.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Diagnostics; 5 | using System.Runtime.InteropServices; 6 | using System.Windows.Input; 7 | using System.Windows.Interop; 8 | using RawInputProcessor.Win32; 9 | 10 | namespace RawInputProcessor 11 | { 12 | public sealed class RawKeyboard : IDisposable 13 | { 14 | private static readonly Guid DeviceInterfaceHid = new Guid("4D1E55B2-F16F-11CF-88CB-001111000030"); 15 | 16 | private readonly Dictionary _deviceList = new Dictionary(); 17 | private readonly object _lock = new object(); 18 | 19 | private IntPtr _devNotifyHandle; 20 | 21 | public int NumberOfKeyboards { get; private set; } 22 | 23 | public event EventHandler KeyPressed; 24 | 25 | public RawKeyboard(IntPtr hwnd, bool captureOnlyInForeground) 26 | { 27 | RawInputDevice[] array = 28 | { 29 | new RawInputDevice 30 | { 31 | UsagePage = HidUsagePage.GENERIC, 32 | Usage = HidUsage.Keyboard, 33 | Flags = (captureOnlyInForeground ? RawInputDeviceFlags.NONE : RawInputDeviceFlags.INPUTSINK) | RawInputDeviceFlags.DEVNOTIFY, 34 | Target = hwnd 35 | } 36 | }; 37 | if (!Win32Methods.RegisterRawInputDevices(array, (uint)array.Length, (uint)Marshal.SizeOf(array[0]))) 38 | { 39 | throw new ApplicationException("Failed to register raw input device(s).", new Win32Exception()); 40 | } 41 | EnumerateDevices(); 42 | _devNotifyHandle = RegisterForDeviceNotifications(hwnd); 43 | } 44 | 45 | ~RawKeyboard() 46 | { 47 | Dispose(); 48 | } 49 | 50 | public void Dispose() 51 | { 52 | GC.SuppressFinalize(this); 53 | if (_devNotifyHandle != IntPtr.Zero) 54 | { 55 | Win32Methods.UnregisterDeviceNotification(_devNotifyHandle); 56 | _devNotifyHandle = IntPtr.Zero; 57 | } 58 | } 59 | 60 | private static IntPtr RegisterForDeviceNotifications(IntPtr parent) 61 | { 62 | IntPtr notifyHandle = IntPtr.Zero; 63 | BroadcastDeviceInterface broadcastDeviceInterface = default(BroadcastDeviceInterface); 64 | broadcastDeviceInterface.dbcc_size = Marshal.SizeOf(broadcastDeviceInterface); 65 | broadcastDeviceInterface.BroadcastDeviceType = BroadcastDeviceType.DBT_DEVTYP_DEVICEINTERFACE; 66 | broadcastDeviceInterface.dbcc_classguid = DeviceInterfaceHid; 67 | IntPtr interfacePtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(BroadcastDeviceInterface))); 68 | try 69 | { 70 | Marshal.StructureToPtr(broadcastDeviceInterface, interfacePtr, false); 71 | notifyHandle = Win32Methods.RegisterDeviceNotification(parent, interfacePtr, 72 | DeviceNotification.DEVICE_NOTIFY_WINDOW_HANDLE); 73 | } 74 | catch (Exception ex) 75 | { 76 | Debug.Print("Registration for device notifications Failed. Error: {0}", Marshal.GetLastWin32Error()); 77 | Debug.Print(ex.StackTrace); 78 | } 79 | finally 80 | { 81 | Marshal.FreeHGlobal(interfacePtr); 82 | } 83 | 84 | if (notifyHandle == IntPtr.Zero) 85 | { 86 | Debug.Print("Registration for device notifications Failed. Error: {0}", Marshal.GetLastWin32Error()); 87 | } 88 | return notifyHandle; 89 | } 90 | 91 | public void EnumerateDevices() 92 | { 93 | lock (_lock) 94 | { 95 | _deviceList.Clear(); 96 | var rawKeyboardDevice = new RawKeyboardDevice("Global Keyboard", RawDeviceType.Keyboard, IntPtr.Zero, 97 | "Fake Keyboard. Some keys (ZOOM, MUTE, VOLUMEUP, VOLUMEDOWN) are sent to rawinput with a handle of zero."); 98 | _deviceList.Add(rawKeyboardDevice.Handle, rawKeyboardDevice); 99 | uint devices = 0u; 100 | int size = Marshal.SizeOf(typeof(RawInputDeviceList)); 101 | if (Win32Methods.GetRawInputDeviceList(IntPtr.Zero, ref devices, (uint)size) != 0u) 102 | { 103 | throw new Win32Exception(Marshal.GetLastWin32Error()); 104 | } 105 | IntPtr pRawInputDeviceList = Marshal.AllocHGlobal((int)(size * devices)); 106 | try 107 | { 108 | Win32Methods.GetRawInputDeviceList(pRawInputDeviceList, ref devices, (uint)size); 109 | int index = 0; 110 | while (index < devices) 111 | { 112 | RawKeyboardDevice device = GetDevice(pRawInputDeviceList, size, index); 113 | if (device != null && !_deviceList.ContainsKey(device.Handle)) 114 | { 115 | _deviceList.Add(device.Handle, device); 116 | } 117 | index++; 118 | } 119 | } 120 | finally 121 | { 122 | Marshal.FreeHGlobal(pRawInputDeviceList); 123 | } 124 | NumberOfKeyboards = _deviceList.Count; 125 | } 126 | } 127 | 128 | private static RawKeyboardDevice GetDevice(IntPtr pRawInputDeviceList, int dwSize, int index) 129 | { 130 | uint size = 0u; 131 | // 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 132 | var rawInputDeviceList = (RawInputDeviceList)Marshal.PtrToStructure(new IntPtr(pRawInputDeviceList.ToInt64() + dwSize * index), typeof(RawInputDeviceList)); 133 | Win32Methods.GetRawInputDeviceInfo(rawInputDeviceList.hDevice, RawInputDeviceInfo.RIDI_DEVICENAME, IntPtr.Zero, ref size); 134 | if (size <= 0u) 135 | { 136 | return null; 137 | } 138 | IntPtr intPtr = Marshal.AllocHGlobal((int)size); 139 | try 140 | { 141 | Win32Methods.GetRawInputDeviceInfo(rawInputDeviceList.hDevice, RawInputDeviceInfo.RIDI_DEVICENAME, intPtr, ref size); 142 | string device = Marshal.PtrToStringAnsi(intPtr); 143 | if (rawInputDeviceList.dwType == DeviceType.RimTypekeyboard || 144 | rawInputDeviceList.dwType == DeviceType.RimTypeHid) 145 | { 146 | string deviceDescription = Win32Methods.GetDeviceDescription(device); 147 | return new RawKeyboardDevice(Marshal.PtrToStringAnsi(intPtr), 148 | (RawDeviceType)rawInputDeviceList.dwType, rawInputDeviceList.hDevice, deviceDescription); 149 | } 150 | } 151 | finally 152 | { 153 | if (intPtr != IntPtr.Zero) 154 | { 155 | Marshal.FreeHGlobal(intPtr); 156 | } 157 | } 158 | return null; 159 | } 160 | 161 | private bool ProcessRawInput(IntPtr hdevice) 162 | { 163 | if (_deviceList.Count == 0) 164 | { 165 | return false; 166 | } 167 | int size = 0; 168 | Win32Methods.GetRawInputData(hdevice, DataCommand.RID_INPUT, IntPtr.Zero, ref size, Marshal.SizeOf(typeof(RawInputHeader))); 169 | InputData rawBuffer; 170 | if (Win32Methods.GetRawInputData(hdevice, DataCommand.RID_INPUT, out rawBuffer, ref size, Marshal.SizeOf(typeof(RawInputHeader))) != size) 171 | { 172 | Debug.WriteLine("Error getting the rawinput buffer"); 173 | return false; 174 | } 175 | int vKey = rawBuffer.data.keyboard.VKey; 176 | int makecode = rawBuffer.data.keyboard.Makecode; 177 | int flags = rawBuffer.data.keyboard.Flags; 178 | if (vKey == Win32Consts.KEYBOARD_OVERRUN_MAKE_CODE) 179 | { 180 | return false; 181 | } 182 | 183 | RawKeyboardDevice device; 184 | lock (_lock) 185 | { 186 | if (!_deviceList.TryGetValue(rawBuffer.header.hDevice, out device)) 187 | { 188 | Debug.WriteLine("Handle: {0} was not in the device list.", rawBuffer.header.hDevice); 189 | return false; 190 | } 191 | } 192 | 193 | var isE0BitSet = ((flags & Win32Consts.RI_KEY_E0) != 0); 194 | bool isBreakBitSet = (flags & Win32Consts.RI_KEY_BREAK) != 0; 195 | 196 | uint message = rawBuffer.data.keyboard.Message; 197 | Key key = KeyInterop.KeyFromVirtualKey(AdjustVirtualKey(rawBuffer, vKey, isE0BitSet, makecode)); 198 | EventHandler keyPressed = KeyPressed; 199 | if (keyPressed != null) 200 | { 201 | var rawInputEventArgs = new RawInputEventArgs(device, isBreakBitSet ? KeyPressState.Up : KeyPressState.Down, 202 | message, key, vKey); 203 | keyPressed(this, rawInputEventArgs); 204 | if (rawInputEventArgs.Handled) 205 | { 206 | MSG msg; 207 | Win32Methods.PeekMessage(out msg, IntPtr.Zero, Win32Consts.WM_KEYDOWN, Win32Consts.WM_KEYUP, Win32Consts.PM_REMOVE); 208 | } 209 | return rawInputEventArgs.Handled; 210 | } 211 | return false; 212 | } 213 | 214 | private static int AdjustVirtualKey(InputData rawBuffer, int virtualKey, bool isE0BitSet, int makeCode) 215 | { 216 | var adjustedKey = virtualKey; 217 | 218 | if (rawBuffer.header.hDevice == IntPtr.Zero) 219 | { 220 | // When hDevice is 0 and the vkey is VK_CONTROL indicates the ZOOM key 221 | if (rawBuffer.data.keyboard.VKey == Win32Consts.VK_CONTROL) 222 | { 223 | adjustedKey = Win32Consts.VK_ZOOM; 224 | } 225 | } 226 | else 227 | { 228 | switch (virtualKey) 229 | { 230 | // Right-hand CTRL and ALT have their e0 bit set 231 | case Win32Consts.VK_CONTROL: 232 | adjustedKey = isE0BitSet ? Win32Consts.VK_RCONTROL : Win32Consts.VK_LCONTROL; 233 | break; 234 | case Win32Consts.VK_MENU: 235 | adjustedKey = isE0BitSet ? Win32Consts.VK_RMENU : Win32Consts.VK_LMENU; 236 | break; 237 | case Win32Consts.VK_SHIFT: 238 | adjustedKey = makeCode == Win32Consts.SC_SHIFT_R ? Win32Consts.VK_RSHIFT : Win32Consts.VK_LSHIFT; 239 | break; 240 | default: 241 | adjustedKey = virtualKey; 242 | break; 243 | } 244 | } 245 | 246 | return adjustedKey; 247 | } 248 | 249 | public bool HandleMessage(int msg, IntPtr wparam, IntPtr lparam) 250 | { 251 | switch (msg) 252 | { 253 | case Win32Consts.WM_INPUT_DEVICE_CHANGE: 254 | EnumerateDevices(); 255 | break; 256 | case Win32Consts.WM_INPUT: 257 | return ProcessRawInput(lparam); 258 | } 259 | return false; 260 | } 261 | 262 | public static string GetDeviceDianostics() 263 | { 264 | return Win32Methods.GetDeviceDiagnostics(); 265 | } 266 | } 267 | } --------------------------------------------------------------------------------