Raw Input on Windows

An actual technical post! Whooo!

To start, why am I posting about this? Well, for one thing, this is one of the most recent things I’ve implemented in Gleam. First thing to note is, when I saw “raw input”, I do not mean DirectInput. Modern versions of DirectInput are simply wrappers around Windows’ raw input API. While you can still use DirectInput, it is not recommended, as it has been deprecated for quite some time.

First thing you may wish to do is read this MSDN article. It gives you a basic run-down of how to use raw input on Windows. From the information obtained in that article, I made this header file.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#ifndef HID_USAGE_PAGE_GENERIC
	#define HID_USAGE_PAGE_GENERIC ((unsigned short) 0x01)
#endif

#define RAW_INPUT_MOUSE ((unsigned short)0x02)
#define RAW_INPUT_JOYSTICK ((unsigned short)0x04)
#define RAW_INPUT_GAMEPAD ((unsigned short)0x05)
#define RAW_INPUT_KEYBOARD ((unsigned short)0x06)

NS_GLEAM

INLINE bool RegisterForRawInput(unsigned short device, const Window& window);

NS_END

The Window class is the class I made in my library Gleam. These #define’s are values I’ve obtained from the aforementioned MSDN article. Using the MSDN article linked above, we can implement the RegisterForRawInput() function like so:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
bool RegisterForRawInput(unsigned short device, const Window& window)
{
	assert(device == RAW_INPUT_MOUSE || (device >= RAW_INPUT_JOYSTICK && device <= RAW_INPUT_KEYBOARD));

	RAWINPUTDEVICE rid;
	rid.usUsagePage = HID_USAGE_PAGE_GENERIC;
	rid.usUsage = device;
	rid.dwFlags = 0;
	rid.hwndTarget = window.getHWnd();

	return RegisterRawInputDevices(&rid, 1, sizeof(RAWINPUTDEVICE)) == TRUE;
}

Now all that’s left is to modify our message pump callback to handle raw input events. Like the MSDN article says, we’ll have to handle the case of WM_INPUT. Here’s a sample from some of my code (modified to make it shorter and easier to read):

 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
case WM_INPUT: {
	UINT dwSize = 64;
	BYTE lpb[64];
	bool key_up = false;
	char key = 0;

#ifdef _DEBUG
	GetRawInputData((HRAWINPUT)l, RID_INPUT, nullptr, &dwSize, sizeof(RAWINPUTHEADER));
	assert(dwSize <= 64);
#endif

	GetRawInputData((HRAWINPUT)l, RID_INPUT, lpb, &dwSize, sizeof(RAWINPUTHEADER));
	RAWINPUT* raw = (RAWINPUT*)lpb;

	if (raw->header.dwType == RIM_TYPEMOUSE && raw->data.mouse.usFlags == MOUSE_MOVE_RELATIVE) {
		// Get mouse x/y deltas from raw->data.mouse.lLastX/lLastY and do stuff.
	} else if (raw->header.dwType == RIM_TYPEKEYBOARD) {
		key_up = raw->data.keyboard.Flags & RI_KEY_BREAK;

		switch (raw->data.keyboard.VKey) {
			case VK_CONTROL:
			case VK_SHIFT:
				// For some reason, right shift isn't getting the RI_KEY_E0 flag set.
				// Special case it so that we send the correct key.
				if (raw->data.keyboard.MakeCode == 54) {
					// Handle right shift
					key = RSHIFT;
					break;
				}

			case VK_MENU:
				if (raw->data.keyboard.Flags & RI_KEY_E0) {
					// Handle LCTRL, LALT, and LSHIFT
					key = _right_keys[raw->data.keyboard.VKey]; // Handle keys from right side of keyboard
				} else {
					// Handle RCTRL and RALT
					key = _left_keys[raw->data.keyboard.VKey]; // Handle keys from left side of keyboard
				}
				break;
			default:
				key = (KeyCode)raw->data.keyboard.VKey;
				break;
		}
	}
} break;

If you look at the Gleam source, _right_keys and _left_keys are hashmaps that map the virtual key codes to the correct key code. The message pump will only send VK_SHIFT, VK_CONTROL, and VK_MENU when you push any of the shift, alt, or control keys on the keyboard. Using the example message pump code above, you can filter and find which shift, control, or alt key is pressed.

And we’re done! Well, if we only care about mouse and keyboard input. I don’t have gamepad or joystick support yet, but I’ll post about it sometime in the future when I do get that implemented. Anyhow, getting raw input on Windows is really that simple and took me only a few minutes to convert all of my old input code to raw input. The mouse structure has some extra information for which mouse buttons are down/up that you can extract information from. I already had my message pump set up for the normal mouse button messages, so I just left those in, as I don’t think raw input is really providing a better way of detecting mouse buttons being up/down. It’s possible there’s more information in there for non-standard mice, but I’m unsure. Most people I know don’t really use mice that have more than the standard five buttons and mouse wheel. Mice with more than that are using special drivers that map the extra buttons to either macros or other button/key inputs.

Anyhow, while this post didn’t provide much more information than the MSDN article does, I hope this was helpful! The next post I’m going to make is how to achieve raw input on Linux via X11! While Windows’ raw input API is nice and clean and simple and well documented, X11 is the exact opposite. It is completely undocumented, has a terrible API, and the only way I figured out how to implement it was by looking through a bunch of other people’s code. Hopefully Wayland’s APIs are/will be a billion times better than X11.

Until next time!

Tags// , , ,