feat(q5_max): implement bi-directional Raw HID protocol #12
@@ -85,6 +85,11 @@ void get_firmware_version(uint8_t *data) {
|
||||
|
||||
__attribute__((weak)) void kc_rgb_matrix_rx(uint8_t *data, uint8_t length) {}
|
||||
|
||||
/* Default no-op implementation; override in keymap.c to handle custom IDs. */
|
||||
__attribute__((weak)) bool kc_raw_hid_rx_kb(uint8_t *data, uint8_t length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool kc_raw_hid_rx(uint8_t *data, uint8_t length) {
|
||||
switch (data[0]) {
|
||||
case KC_GET_PROTOCOL_VERSION:
|
||||
@@ -185,7 +190,7 @@ bool kc_raw_hid_rx(uint8_t *data, uint8_t length) {
|
||||
|
||||
#endif
|
||||
default:
|
||||
return false;
|
||||
return kc_raw_hid_rx_kb(data, length);
|
||||
}
|
||||
|
||||
raw_hid_send(data, length);
|
||||
|
||||
@@ -63,3 +63,17 @@ enum {
|
||||
REPORT_RATE_GET,
|
||||
REPORT_RATE_SET,
|
||||
};
|
||||
|
||||
/**
|
||||
* Keyboard-level Raw HID extension hook.
|
||||
*
|
||||
* Called by kc_raw_hid_rx() for any command ID not handled by Keychron's own
|
||||
* protocol (0xA0-0xAB range). The default weak implementation returns false
|
||||
* so that unrecognised commands fall through to VIA.
|
||||
*
|
||||
* Override this in your keymap to handle custom command IDs without
|
||||
* conflicting with Keychron's via_command_kb() definition.
|
||||
* Return true if the packet was fully handled (including calling
|
||||
* raw_hid_send() if a reply is needed), false to let VIA process it.
|
||||
*/
|
||||
bool kc_raw_hid_rx_kb(uint8_t *data, uint8_t length);
|
||||
|
||||
@@ -80,6 +80,14 @@
|
||||
// ---------------------------------------------------------------------------
|
||||
#define HID_APP_NAME_MAX 28u
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Packet size
|
||||
// ---------------------------------------------------------------------------
|
||||
// Matches RAW_EPSIZE (QMK raw HID endpoint size = 32 bytes). Defined here
|
||||
// so keymap code does not depend on usb_descriptor.h being in scope, which
|
||||
// it is not when compiled through Keychron's build path.
|
||||
#define HID_PACKET_SIZE 32u
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Convenience: first byte of payload as an absolute packet index
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "keychron_common.h"
|
||||
#include "chord_unicode.h"
|
||||
#include "raw_hid.h"
|
||||
#include "keychron_raw_hid.h"
|
||||
#include "hid_protocol.h"
|
||||
|
||||
// Tap Dance declarations
|
||||
@@ -77,18 +78,20 @@ static uint8_t g_hid_brightness = 0;
|
||||
|
||||
// Send the current layer state to the host / bridge application.
|
||||
static void hid_send_layer_sync(uint8_t layer, uint8_t locked_mask) {
|
||||
uint8_t data[RAW_EPSIZE] = {0};
|
||||
uint8_t data[HID_PACKET_SIZE] = {0};
|
||||
data[HID_OFF_CMD] = HID_CMD_LAYER_SYNC;
|
||||
data[HID_OFF_SRC] = HID_DEV_Q5MAX;
|
||||
data[HID_OFF_FLAGS] = 0;
|
||||
data[HID_PAYLOAD(HID_LAYER_OFF_ACTIVE)] = layer;
|
||||
data[HID_PAYLOAD(HID_LAYER_OFF_LOCKED)] = locked_mask;
|
||||
raw_hid_send(data, RAW_EPSIZE);
|
||||
raw_hid_send(data, HID_PACKET_SIZE);
|
||||
}
|
||||
|
||||
// Handle a Raw HID packet for our custom command range (0x40-0x7E).
|
||||
// Called from via_command_kb(); must call raw_hid_send() for any reply.
|
||||
bool via_command_kb(uint8_t *data, uint8_t length) {
|
||||
// Overrides the weak kc_raw_hid_rx_kb() hook in keychron_raw_hid.c, which is
|
||||
// called by kc_raw_hid_rx() for any command ID not handled by Keychron's own
|
||||
// protocol. Must call raw_hid_send() directly for any reply.
|
||||
bool kc_raw_hid_rx_kb(uint8_t *data, uint8_t length) {
|
||||
uint8_t cmd = data[HID_OFF_CMD];
|
||||
|
||||
// Only intercept our custom command range; let VIA handle everything else.
|
||||
@@ -102,13 +105,13 @@ bool via_command_kb(uint8_t *data, uint8_t length) {
|
||||
case HID_CMD_LAYER_SYNC: {
|
||||
if (flags & HID_FLAG_QUERY) {
|
||||
// Host requests current state — reply without changing anything.
|
||||
uint8_t resp[RAW_EPSIZE] = {0};
|
||||
uint8_t resp[HID_PACKET_SIZE] = {0};
|
||||
resp[HID_OFF_CMD] = HID_CMD_LAYER_SYNC;
|
||||
resp[HID_OFF_SRC] = HID_DEV_Q5MAX;
|
||||
resp[HID_OFF_FLAGS] = HID_FLAG_RESPONSE;
|
||||
resp[HID_PAYLOAD(HID_LAYER_OFF_ACTIVE)] = get_highest_layer(layer_state);
|
||||
resp[HID_PAYLOAD(HID_LAYER_OFF_LOCKED)] = (uint8_t)locked_layers;
|
||||
raw_hid_send(resp, RAW_EPSIZE);
|
||||
raw_hid_send(resp, HID_PACKET_SIZE);
|
||||
} else {
|
||||
// Host or peer keyboard is pushing a new active layer.
|
||||
uint8_t new_layer = data[HID_PAYLOAD(HID_LAYER_OFF_ACTIVE)];
|
||||
|
||||
Reference in New Issue
Block a user