Skip to content

Add K8850 4x4+3knob key binding support (native FD protocol)#174

Open
skarard wants to merge 1 commit intokriomant:masterfrom
skarard:feat/k8850-keybinding
Open

Add K8850 4x4+3knob key binding support (native FD protocol)#174
skarard wants to merge 1 commit intokriomant:masterfrom
skarard:feat/k8850-keybinding

Conversation

@skarard
Copy link
Copy Markdown

@skarard skarard commented Mar 10, 2026

Summary

Adds a dedicated driver (k8850.rs) for the K8850 4x4+3knob keyboard variant (VID 0x514C, PID 0x8850). This device uses a different key binding protocol (0xFD command) than the K884x family (0xFE command).

Protocol details were reverse-engineered via Frida-based HID captures of the official mini_keyboard.exe tool and confirmed through extensive hardware testing on an XZKJ-16key_3knob device.

Relation to PR #154

This PR covers the same device as @yawor's WIP PR #154. Key differences:

This PR PR #154
Keyboard format Compact mode (binding_mode=1) for single keys; triplet format [code, 0x00, 0x32] for modifiers/macros Reversed triplets [0x00, 0x00, code] for everything
Media format [0x00, 0x02, 0x00, 0x00, low] [0, 2, 0, 0, low, 0, 0, high] (includes high byte)
Mouse drag Allowed with warning (firmware limitation) Bails with error
Constructor new(buttons, knobs) with validation new() no args
Tests 8 unit tests None
Verification All formats captured from mini_keyboard.exe via Frida, all binding types hardware tested WIP

Both PRs share: FD command byte, modifier IDs (F1-F8), mouse data format, finalize packet, endpoint 0x04.

Changes

  • k8850.rs (new): Native FD protocol driver with bind_key, mouse support, modifier mapping, and unit tests
  • mod.rs: Add k8850 module (+1 line)
  • consts.rs: Add 0x8850 to product ID list (+1 line)
  • main.rs: Add k8850 import and create_driver match arm (+5 lines)

No changes to k884x.rs, the Keyboard trait, or open_device.

Binding formats (confirmed via Frida capture)

Type Format
Simple key [03 FD key_id layer 01 00 01 00 00 keycode 00]
Modifier combo [03 FD key_id layer 01 00 N 00 00 (triplets...)] where N = total entries
Macro sequence Same triplet format, N = total entry count
Media [03 FD key_id layer 02 00 02 00 00 consumer_code]
Mouse [03 FD key_id layer 03 mouse_data...] (17-byte mouse_data)
Finalize [03 FD FE FF] after each bind_key

Note on VID

This device uses VID 0x514C (not 0x1189), so requires --vendor-id 0x514c. Automatic VID handling can be addressed separately.

Note on PID conflict

As noted in #154 and #136, PID 0x8850 may be shared by different keyboard variants (e.g. 4x3+4knob). This PR maps 0x8850 directly to the 4x4+3knob driver. Variant auto-detection can be added later if needed.

Test plan

  • 8 unit tests for key binding packet format
  • All 48 project tests pass
  • Hardware tested on XZKJ-16key_3knob (4x4 keys + 3 rotary encoders):
    • Simple keys, numpad keys
    • Single modifier (ctrl-d, shift-f13)
    • Multi-modifier (ctrl-alt-space, ctrl-alt-win-f13)
    • Right-hand modifiers (rctrl-a, rshift-b, ralt-c, rwin-d)
    • Macro sequences (a,b / h,e,l,l,o / ctrl-c,ctrl-v / shift-h,e,l,l,o)
    • Media keys (play, next)
    • Mouse clicks (left, right, middle)
    • Mouse wheel (scroll up/down)
    • Mouse move (all 4 directions)
    • Mouse drag (works with limitation - firmware sends click+move separately)
    • Knob bindings (ccw/press/cw, all 3 knobs)

Generated with Claude Code, reviewed and hardware tested by @skarard

@skarard skarard force-pushed the feat/k8850-keybinding branch from 45ad56c to 5f3394a Compare March 10, 2026 15:14
@skarard skarard changed the title Fix K8850 key binding protocol (use native FD command) Add K8850 4x4+3knob key binding support (native FD protocol) Mar 10, 2026
The K8850 (VID 0x514C, PID 0x8850) uses a different key binding
protocol (0xFD command) than the K884x family (0xFE command).

Protocol details reverse-engineered via Frida HID captures of
mini_keyboard.exe and verified on hardware for all binding types:
keyboard, modifiers, macros, media, mouse, and knobs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@skarard skarard force-pushed the feat/k8850-keybinding branch from 5749ae4 to 1ec60b7 Compare March 10, 2026 15:58
@skarard skarard mentioned this pull request Mar 10, 2026
8 tasks
@yawor
Copy link
Copy Markdown

yawor commented Mar 10, 2026

I have no issues with this being a competition for my PR :) and if this becomes a better one then I'm all for it. But I have some comments:

  • Why do you use only a 8-bit code for the HID consumer page (the media keys)? HID consumer page codes are 16-bit wide, so the high byte is also required. You can of course cut the 3 bytes after the low byte if the high byte is 0, but I think this unnecessarily complicates the code. It's much easier to just always split the code into two bytes and use low and high, even if high is 0.
  • The shortened commands work only because the HID report being sent is always extended to full 64 bytes and is padded with zeros. By shortening the command in the driver you don't make the actual USB transfer shorter in any way: it's always 64 bytes.
  • I don't get why it's necessary to provide a separate path to binding single key without modifiers. This doesn't do anything different to binding a key with modifiers or a key sequence. The command is the same, you just cut it after the key code byte (the rest is padded with zeros anyway). This only complicates the code by splitting the code in if/else and doesn't gain anything.

Like in my PR, you also don't provide any way to differentiate this keyboard from other ones with the same VID/PID. This is the main reason I've marked my PR as WIP (Work In Progress) so it won't get merged. Merging either this or my PR will break things for other already supported keyboards that reuse the same VID/PID. Making this driver a default one for this VID/PID combination doesn't make any sense. I think that this program needs to have an option to manually override the driver in a command line.

It's nice that it provides the tests. And of course the addition to control LEDs is also nice.

match expansion {
Macro::Keyboard(KeyboardEvent(_, presses)) => {
// Single key, no modifiers: binding_mode=1 (compact format)
if presses.len() == 1 && presses[0].modifiers.is_empty() {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't make sense. There's no such thing as a binding_mode=1. You're just setting the key sequence length field to 1. You can remove this condition and just leave the code in else block and it'll still work (the only difference is that the 50 ms delay is going to be set after the key).

}
Macro::Media(code) => {
// Media mode: binding_mode=2, consumer code at [9]
let [low, _high] = (*code as u16).to_le_bytes();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HID consumer codes are 16-bit wide. It doesn't make sense to only use the low byte.


fn create_driver(id_product: u16, buttons: u8, knobs: u8) -> Result<Box<dyn Keyboard>> {
let keyboard: Box<dyn Keyboard> = match id_product {
0x8840 | 0x8842 | 0x8850 => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Making this driver the default one for 0x8850 PID is going to break support for other 0x8850 keyboards that are already supported.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants