Repository navigation
Expand file tree
/
Copy pathKeyProbe.cs
More file actions
159 lines (138 loc) · 7.89 KB
/
Copy pathKeyProbe.cs
File metadata and controls
159 lines (138 loc) · 7.89 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
// KeyProbe - watches every path a keypress can take into Windows, at once:
// 1. WH_KEYBOARD_LL - the cooked keyboard stream (what remappers can see)
// 2. Raw Input, Generic Desktop / Keyboard (0x01/0x06)
// 3. Raw Input, Consumer Control (0x0C/0x01) - where media keys normally live
// Whichever of these lights up when a volume key is pressed tells us how to
// build the fix - and if none of them do, the keypress is not reaching Windows.
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
class KeyProbe {
const int WH_KEYBOARD_LL = 13;
const int WM_INPUT = 0x00FF;
const int HWND_MESSAGE = -3;
const uint RIDEV_INPUTSINK = 0x00000100;
const uint RID_INPUT = 0x10000003;
const uint RIDI_DEVICENAME = 0x20000007;
delegate IntPtr HookProc(int code, IntPtr wParam, IntPtr lParam);
delegate IntPtr WndProcDel(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
[StructLayout(LayoutKind.Sequential)]
struct KBDLLHOOKSTRUCT { public uint vkCode, scanCode, flags, time; public IntPtr dwExtraInfo; }
[StructLayout(LayoutKind.Sequential)]
struct RAWINPUTDEVICE { public ushort UsagePage, Usage; public uint Flags; public IntPtr hwndTarget; }
[StructLayout(LayoutKind.Sequential)]
struct WNDCLASS {
public uint style; public IntPtr lpfnWndProc; public int cbClsExtra, cbWndExtra;
public IntPtr hInstance, hIcon, hCursor, hbrBackground;
[MarshalAs(UnmanagedType.LPWStr)] public string lpszMenuName;
[MarshalAs(UnmanagedType.LPWStr)] public string lpszClassName;
}
[StructLayout(LayoutKind.Sequential)]
struct MSG { public IntPtr hwnd; public uint message; public IntPtr wParam, lParam; public uint time; public int ptX, ptY; }
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr SetWindowsHookEx(int id, HookProc fn, IntPtr mod, uint tid);
[DllImport("user32.dll")] static extern IntPtr CallNextHookEx(IntPtr hk, int code, IntPtr w, IntPtr l);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)] static extern IntPtr GetModuleHandle(string n);
[DllImport("user32.dll", CharSet = CharSet.Unicode)] static extern ushort RegisterClassW(ref WNDCLASS c);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
static extern IntPtr CreateWindowExW(uint ex, string cls, string name, uint style,
int x, int y, int w, int h, IntPtr parent, IntPtr menu, IntPtr inst, IntPtr param);
[DllImport("user32.dll")] static extern IntPtr DefWindowProcW(IntPtr h, uint m, IntPtr w, IntPtr l);
[DllImport("user32.dll")] static extern bool RegisterRawInputDevices(RAWINPUTDEVICE[] d, uint num, uint size);
[DllImport("user32.dll")]
static extern uint GetRawInputData(IntPtr hRaw, uint cmd, byte[] data, ref uint size, uint hdrSize);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
static extern uint GetRawInputDeviceInfoW(IntPtr dev, uint cmd, StringBuilder data, ref uint size);
[DllImport("user32.dll")] static extern int GetMessageW(out MSG m, IntPtr h, uint min, uint max);
[DllImport("user32.dll")] static extern bool TranslateMessage(ref MSG m);
[DllImport("user32.dll")] static extern IntPtr DispatchMessageW(ref MSG m);
static HookProc _hookProc; // kept alive against GC
static WndProcDel _wndProc; // kept alive against GC
static Dictionary<long, string> _devNames = new Dictionary<long, string>();
static string DevName(IntPtr dev) {
long k = dev.ToInt64();
string s;
if (_devNames.TryGetValue(k, out s)) return s;
uint size = 0;
GetRawInputDeviceInfoW(dev, RIDI_DEVICENAME, null, ref size);
var sb = new StringBuilder((int)size + 2);
GetRawInputDeviceInfoW(dev, RIDI_DEVICENAME, sb, ref size);
s = sb.ToString();
string tag = s.IndexOf("004c", StringComparison.OrdinalIgnoreCase) >= 0 ||
s.IndexOf("004C", StringComparison.OrdinalIgnoreCase) >= 0
? " <<< APPLE" : "";
s = s + tag;
_devNames[k] = s;
return s;
}
static IntPtr Hook(int code, IntPtr wParam, IntPtr lParam) {
if (code >= 0) {
var k = (KBDLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(KBDLLHOOKSTRUCT));
int msg = wParam.ToInt32();
bool down = (msg == 0x0100 || msg == 0x0104);
Console.WriteLine(string.Format("HOOK {0,-4} vk=0x{1:X2} ({1,3}) scan=0x{2:X2} flags=0x{3:X2}",
down ? "DOWN" : "UP", k.vkCode, k.scanCode, k.flags));
}
return CallNextHookEx(IntPtr.Zero, code, wParam, lParam);
}
static string Hex(byte[] b, int off, int n) {
var sb = new StringBuilder();
for (int i = 0; i < n; i++) sb.Append(b[off + i].ToString("X2")).Append(' ');
return sb.ToString().TrimEnd();
}
static void OnInput(IntPtr hRaw) {
uint size = 0;
GetRawInputData(hRaw, RID_INPUT, null, ref size, (uint)(IntPtr.Size == 8 ? 24 : 16));
if (size == 0) return;
var buf = new byte[size];
uint got = GetRawInputData(hRaw, RID_INPUT, buf, ref size, (uint)(IntPtr.Size == 8 ? 24 : 16));
if (got == 0 || got > size) return;
uint type = BitConverter.ToUInt32(buf, 0);
IntPtr dev = IntPtr.Size == 8 ? new IntPtr(BitConverter.ToInt64(buf, 8))
: new IntPtr(BitConverter.ToInt32(buf, 8));
int off = IntPtr.Size == 8 ? 24 : 16;
if (type == 1) { // RIM_TYPEKEYBOARD
ushort make = BitConverter.ToUInt16(buf, off);
ushort flags = BitConverter.ToUInt16(buf, off + 2);
ushort vkey = BitConverter.ToUInt16(buf, off + 6);
// vk 0xFF means Windows received the key but had no translation for
// it - exactly what an unmapped vendor key looks like. Do NOT skip it.
Console.WriteLine(string.Format("RAW-KBD {0,-4} vk=0x{1:X2} ({1,3}) make=0x{2:X2} flags=0x{3:X2} {4}{5}",
(flags & 1) == 0 ? "DOWN" : "UP", vkey, make, flags, DevName(dev),
vkey == 0xFF ? " <<< UNTRANSLATED KEY" : ""));
} else if (type == 2) { // RIM_TYPEHID
uint sz = BitConverter.ToUInt32(buf, off);
uint cnt = BitConverter.ToUInt32(buf, off + 4);
int d = off + 8;
for (int i = 0; i < cnt && d + sz <= got; i++, d += (int)sz)
Console.WriteLine(string.Format("RAW-HID {0} {1}", Hex(buf, d, (int)sz), DevName(dev)));
}
}
static IntPtr WndProc(IntPtr h, uint msg, IntPtr w, IntPtr l) {
if (msg == WM_INPUT) OnInput(l);
return DefWindowProcW(h, msg, w, l);
}
static void Main() {
IntPtr inst = GetModuleHandle(null);
_wndProc = WndProc;
var wc = new WNDCLASS();
wc.lpfnWndProc = Marshal.GetFunctionPointerForDelegate(_wndProc);
wc.hInstance = inst;
wc.lpszClassName = "MagicVolumeKeyProbe";
RegisterClassW(ref wc);
IntPtr hwnd = CreateWindowExW(0, "MagicVolumeKeyProbe", "probe", 0, 0, 0, 0, 0,
new IntPtr(HWND_MESSAGE), IntPtr.Zero, inst, IntPtr.Zero);
var devs = new RAWINPUTDEVICE[2];
devs[0].UsagePage = 0x01; devs[0].Usage = 0x06; devs[0].Flags = RIDEV_INPUTSINK; devs[0].hwndTarget = hwnd;
devs[1].UsagePage = 0x0C; devs[1].Usage = 0x01; devs[1].Flags = RIDEV_INPUTSINK; devs[1].hwndTarget = hwnd;
bool ok = RegisterRawInputDevices(devs, 2, (uint)Marshal.SizeOf(typeof(RAWINPUTDEVICE)));
_hookProc = Hook;
IntPtr hk = SetWindowsHookEx(WH_KEYBOARD_LL, _hookProc, inst, 0);
Console.WriteLine("raw input registered = " + ok + " ; keyboard hook = " + (hk != IntPtr.Zero));
Console.WriteLine("=== press F10 / F11 / F12 (and fn+F10..F12) now ===");
Console.WriteLine();
MSG m;
while (GetMessageW(out m, IntPtr.Zero, 0, 0) > 0) { TranslateMessage(ref m); DispatchMessageW(ref m); }
}
}