Capture global hotkeys on Windows through raw input

A message-only window registers the keyboard and mouse with
RIDEV_INPUTSINK, so every key and button arrives as WM_INPUT whether or
not the client is in front. Raw input only observes, where a
WH_KEYBOARD_LL hook sits in the path of the system input queue and can
swallow a keystroke; it also reports the side buttons and both edges of
every key, which push-to-talk needs.

Keyboard codes stay in each platform's own numbering — X keycodes on
X11, set-1 scan codes with the E0/E1 escape folded into the high byte on
Windows — since that is what the input API reports and the key-naming
call expects. Bindings are per-machine either way, as TeamSpeak's own
per-OS keydefs are. Mouse buttons are unified on the X numbering, so
"Mouse 4" means the same thing on both.

The RAWINPUT decoding lives in its own class so it can be tested off
Windows; the window and its pump have not been run on Windows yet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-16 18:40:50 +00:00
parent edc8a0ca3a
commit 752676e863
8 changed files with 720 additions and 10 deletions

View File

@@ -1,12 +1,19 @@
package com.ts3client.hotkey;
/**
* One physical key or mouse button, identified the way the X server numbers them:
* keyboards by keycode (Linux evdev code + 8) and mice by button number, so a binding
* is independent of the keyboard layout in force when it was recorded.
* One physical key or mouse button. Keys are identified by their position rather than by
* the character they produce, so a binding is independent of the keyboard layout in force
* when it was recorded.
*
* <p>The keyboard numbering is whatever the platform's own is — X keycodes (Linux evdev
* code + 8) on X11, set-1 scan codes with the {@code E0}/{@code E1} escape in the high
* byte on Windows — because that is what its input API reports and its key-naming call
* expects. Bindings are therefore per-machine, which is also how TeamSpeak stores them.
* Mouse buttons are unified on the X numbering everywhere, side buttons included.
*
* @param device which input device the code belongs to
* @param code X keycode for {@link Device#KEYBOARD}, X button number for {@link Device#MOUSE}
* @param code the platform's key code for {@link Device#KEYBOARD}, an X-style button
* number for {@link Device#MOUSE}
*/
public record HotkeyKey(HotkeyKey.Device device, int code) {