From cf856752dd64c473a2bc5467b67686f697e78b5c Mon Sep 17 00:00:00 2001 From: Karl Fleischmann Date: Wed, 20 May 2026 16:30:40 +0200 Subject: [PATCH 1/4] daemon: Clarify byte/bit iteration in is_wheel_keybit() This commit renames the variables in is_wheel_keybit() to match the ones used in inputupdate_keys() (and also make more sense). --- src/daemon/input.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/daemon/input.c b/src/daemon/input.c index b48768bb..1fc8cc66 100644 --- a/src/daemon/input.c +++ b/src/daemon/input.c @@ -229,16 +229,17 @@ static void* play_macro(void* param) { // Checks if the macro mask contains any wheels to prevent it from looping endlessly static inline int is_wheel_keybit(const usbdevice* kb, const uchar* macro){ - for(int i = 0; i < N_KEYBYTES_INPUT; i++){ + for(int byte = 0; byte < N_KEYBYTES_INPUT; byte++){ // Most entries are probably going to be 0, so skip over them - if(!macro[i]) + if(!macro[byte]) continue; // Go through each bit - for(int j = 0; j < 8; j++){ - if(!((macro[i] >> j) & 1)) + for(int bit = 0; bit < 8; bit++){ + int keyindex = byte * 8 + bit; + if(!((macro[byte] >> bit) & 1)) continue; // Get the index of the item and look it up in the keymap - const key* ckey = kb->keymap + i * 8 + j; + const key* ckey = kb->keymap + keyindex; // If there's at least a single wheel, return true if(IS_VOLWHEEL(ckey->scan) || IS_SCROLLWHEEL_V(ckey->scan) || IS_SCROLLWHEEL_H(ckey->scan)) return 1; From 8631b804fe027bbafae7f9cb56a26cbeb7e27c7d Mon Sep 17 00:00:00 2001 From: Karl Fleischmann Date: Wed, 20 May 2026 15:57:00 +0200 Subject: [PATCH 2/4] daemon: Clear K100 dial bits Unconditionally clear the controlwheel keybits for all devices, that have a control wheel. --- src/daemon/keymap.c | 33 +++++++++++++++++++-------------- src/daemon/usb.h | 3 +++ 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/daemon/keymap.c b/src/daemon/keymap.c index 86f31a0a..e6d34dc3 100644 --- a/src/daemon/keymap.c +++ b/src/daemon/keymap.c @@ -897,26 +897,31 @@ void process_input_urb(void* context, unsigned char* buffer, int urblen, ushort // We need to split the bragi code because the size check needs to go first if(kb->protocol == PROTO_BRAGI) { +#define BRAGI_MEDIA_MASK 0x60 + // Always clear the control wheel key bits. + // This will enable the daemon to emulate keyup events in inputupdate_keys(). + if (DEV_HAS_CTRLWHEEL(kb)) { + CLEAR_KEYBIT(targetkb->input.keys, 129); + CLEAR_KEYBIT(targetkb->input.keys, 130); + } + if(urblen == 64) { if(buffer[1] == BRAGI_INPUT_HID) { corsair_kbcopy(targetkb->input.keys, buffer + 2); // Check if we need to apply an awful hack to get media keys working handle_bragi_media_keys(targetkb); - } else if(buffer[1] == BRAGI_INPUT_DIAL) { - #define MEDIA_MASK 0x60 - if(buffer[1] == BRAGI_INPUT_DIAL && buffer[2] == MEDIA_MASK) { - // This is a packet from the volume dial - // We need to handle it appropriately then clear the keys so we stop getting garbage - int32_t wheel; - memcpy(&wheel, buffer + 4, sizeof(int32_t)); - if(wheel > 0) { - // Apply fresh key data: clockwise rotation - SET_KEYBIT(targetkb->input.keys, 129); - } else { - // Apply fresh key data: counter-clockwise rotation - SET_KEYBIT(targetkb->input.keys, 130); - } + } else if(buffer[1] == BRAGI_INPUT_DIAL && buffer[2] == BRAGI_MEDIA_MASK) { + // This is a packet from the control dial + // We need to handle it appropriately then clear the keys so we stop getting garbage + int32_t wheel; + memcpy(&wheel, buffer + 4, sizeof(int32_t)); + if(wheel > 0) { + // Apply fresh key data: clockwise rotation + SET_KEYBIT(targetkb->input.keys, 129); + } else { + // Apply fresh key data: counter-clockwise rotation + SET_KEYBIT(targetkb->input.keys, 130); } } else { ckb_err("Unimplemented bragi input packet %hhx\n", buffer[1]); diff --git a/src/daemon/usb.h b/src/daemon/usb.h index 295c694d..19b5d4a5 100644 --- a/src/daemon/usb.h +++ b/src/daemon/usb.h @@ -296,6 +296,9 @@ const char* product_str(ushort product); // Devices that have volume wheels (or don't) #define DEV_HAS_VOLWHEEL(kb) (!(IS_K65(kb) || IS_K63(kb) || (kb)->product == P_K57_U || IS_K55(kb) || IS_K60PRORGB(kb))) +// Devices that have a control wheel +#define DEV_HAS_CTRLWHEEL(kb) ((kb)->vendor == V_CORSAIR && (IS_K70CORERGB(kb) || (kb)->product == P_K100_MECHANICAL || (kb)->product == P_K100_OPTICAL || (kb)->product == P_K100_OPTICAL_VARIANT)) + // Devices that use the NXP protocol and have the DPI stage RGB data in the DPI packet #define NXP_RGB_IN_DPI_PKT(kb) ((kb)->vendor == V_CORSAIR && ((kb)->product == P_GLAIVE_PRO || IS_DARK_CORE_NXP(kb))) From b95f3b272d0cc0c9e31721aea64bf7ee38f8d92e Mon Sep 17 00:00:00 2001 From: Karl Fleischmann Date: Wed, 20 May 2026 15:02:35 +0200 Subject: [PATCH 3/4] daemon: Emulate keyup event for control wheel events This allows for appropriate, repeatedly emitted events from a control wheel dial. --- src/daemon/input.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/daemon/input.c b/src/daemon/input.c index 1fc8cc66..1c3e9820 100644 --- a/src/daemon/input.c +++ b/src/daemon/input.c @@ -9,6 +9,9 @@ #define IS_SCROLLWHEEL_V(scan) ((scan) == BTN_WHEELUP || (scan) == BTN_WHEELDOWN) #define IS_SCROLLWHEEL_H(scan) ((scan) == BTN_WHEELLEFT || (scan) == BTN_WHEELRIGHT) #define IS_VOLWHEEL(scan) (((scan) == KEY_VOLUMEUP || (scan) == KEY_VOLUMEDOWN) && DEV_HAS_VOLWHEEL(kb)) +// Lookup control wheel events by index in keymap. Alternatively one could check for non-null key->name to start with "ctrlwheel". +// FIXME: Replace by proper lookup of map in keymap. +#define IS_CTRLWHEEL(kb, index) ((kb)->protocol == PROTO_BRAGI && ((index) == 129 || (index) == 130)) static int macromask(const uchar* keys, const uchar* macro){ // Scan a macro against key input. Return 0 if any of them don't match @@ -241,7 +244,7 @@ static inline int is_wheel_keybit(const usbdevice* kb, const uchar* macro){ // Get the index of the item and look it up in the keymap const key* ckey = kb->keymap + keyindex; // If there's at least a single wheel, return true - if(IS_VOLWHEEL(ckey->scan) || IS_SCROLLWHEEL_V(ckey->scan) || IS_SCROLLWHEEL_H(ckey->scan)) + if(IS_VOLWHEEL(ckey->scan) || IS_SCROLLWHEEL_V(ckey->scan) || IS_SCROLLWHEEL_H(ckey->scan) || IS_CTRLWHEEL(kb, keyindex)) return 1; } } @@ -391,7 +394,7 @@ static inline void inputupdate_keys(usbdevice* kb, int* sync_kb, int* sync_mouse // If it's a volume wheel, just add a single keyup event to the FIFO // Else if the event originated from a scroll wheel, and is bound to something other than a scroll wheel, // multiply the last few events by the amount of events received from the hid report - if(IS_VOLWHEEL(map->scan)){ + if(IS_VOLWHEEL(map->scan) || IS_CTRLWHEEL(kb, keyindex)){ // Make room for it memmove(events + modcount + keycount + 1, events + modcount + keycount, sizeof(int) * rmodcount); // Duplicate the last event, but with keyup @@ -417,7 +420,7 @@ static inline void inputupdate_keys(usbdevice* kb, int* sync_kb, int* sync_mouse } } // Clear the key bit so that we can receive events of this type in the next run - if(IS_VOLWHEEL(map->scan) || IS_SCROLLWHEEL_V(map->scan)) + if(IS_VOLWHEEL(map->scan) || IS_SCROLLWHEEL_V(map->scan) || IS_CTRLWHEEL(kb, keyindex)) input->keys[byte] &= ~mask; } From 46f2f08e76f749fbafa460a1958f98daa6ced441 Mon Sep 17 00:00:00 2001 From: Karl Fleischmann Date: Tue, 19 May 2026 16:24:04 +0200 Subject: [PATCH 4/4] daemon, gui: Consolidate keyname length in project This commit fixes incorrect behavior of the 'w' key which was linked to the "ctrlwheelcw" event, because the keyname was read in 10 character chunks. Create a consolidated macro, with a large enough limit to allow all currently relevant keys. Replace all custom keyname lengths with this macro. Ensure that relevant sscanf() calls use an appropriate string version of this length (minus one for the trailing '\0'). CKB_KEYNAME_MAX in animation.h still needs to be kept in sync manually due to it being a standalone header. --- src/ckbnextconfig.h.in | 9 +++++++++ src/daemon/command.c | 4 ++-- src/daemon/input.c | 13 +++++++------ src/daemon/led.c | 4 ++-- src/gui/animscript.cpp | 4 ++-- src/libs/ckb-next/include/ckb-next/animation.h | 2 +- 6 files changed, 23 insertions(+), 13 deletions(-) diff --git a/src/ckbnextconfig.h.in b/src/ckbnextconfig.h.in index 3d9590ed..ee93356f 100644 --- a/src/ckbnextconfig.h.in +++ b/src/ckbnextconfig.h.in @@ -18,3 +18,12 @@ #define CKB_NEXT_COPYRIGHT_YEAR "${ckb-next_COPYRIGHT_YEAR}" #cmakedefine ckb_next_VERSION_IS_RELEASE #define CKB_NEXT_DESCRIPTION "Open Source Driver for Corsair devices." + +#include +// Number of characters allowed for key names. +#define N_KEYNAME_LENGTH 40 +#define N_KEYNAME_STRLEN 39 +#define N_KEYNAME_STR_REAL(x) #x +#define N_KEYNAME_STR(x) N_KEYNAME_STR_REAL(x) +#define N_SCANF_KEYNAME N_KEYNAME_STR(N_KEYNAME_STRLEN) +static_assert((N_KEYNAME_LENGTH == N_KEYNAME_STRLEN + 1), "N_KEYNAME_LENGTH must be 1 greater than N_KEYNAME_STRLEN"); diff --git a/src/daemon/command.c b/src/daemon/command.c index 193fec92..8eebf56f 100644 --- a/src/daemon/command.c +++ b/src/daemon/command.c @@ -332,8 +332,8 @@ int readcmd(usbdevice* kb, char* line){ } // Scan the left side for key names and run the requested command int position = 0, field = 0; - char keyname[11] = { 0 }; - while(position < left && sscanf(word + position, "%10[^:,]%n", keyname, &field) == 1){ + char keyname[N_KEYNAME_LENGTH] = { 0 }; + while(position < left && sscanf(word + position, "%"N_SCANF_KEYNAME"[^:,]%n", keyname, &field) == 1){ uint keycode; if(!strcmp(keyname, "all")){ // Set all keys diff --git a/src/daemon/input.c b/src/daemon/input.c index 1c3e9820..2b12f9c3 100644 --- a/src/daemon/input.c +++ b/src/daemon/input.c @@ -645,8 +645,8 @@ static void _cmd_macro(usbmode* mode, const char* keys, const char* assignment, int empty = 1; int left = strlen(keys), right = strlen(assignment); int position = 0, field = 0; - char keyname[40]; - while(position < left && sscanf(keys + position, "%10[^+]%n", keyname, &field) == 1){ + char keyname[N_KEYNAME_LENGTH]; + while(position < left && sscanf(keys + position, "%"N_SCANF_KEYNAME"[^+]%n", keyname, &field) == 1){ // Find this key in the keymap for(unsigned i = 0; i < N_KEYS_INPUT; i++){ if(kb->keymap[i].name && !strcmp(keyname, kb->keymap[i].name)){ @@ -673,7 +673,7 @@ static void _cmd_macro(usbmode* mode, const char* keys, const char* assignment, position = 0; field = 0; // max action = old 11 chars plus 12 chars which is the max 32-bit unsigned int 4294967295 size * 2 + 1 for range underscore - while(position < right && sscanf(assignment + position, "%36[^,]%n", keyname, &field) == 1){ + while(position < right && sscanf(assignment + position, "%"N_SCANF_KEYNAME"[^,]%n", keyname, &field) == 1){ if(!strcmp(keyname, "clear")) break; @@ -682,12 +682,13 @@ static void _cmd_macro(usbmode* mode, const char* keys, const char* assignment, int64_t long_delay_range = 0; uint32_t delay = UINT32_MAX; // computed delay value. UINT32_MAX means use the default value. uint32_t delay_range = 0; - char real_keyname[12]; // temp to hold the left side (key) of the = - int scan_matches = sscanf(keyname, "%11[^=]=%"SCNd64"_%"SCNd64, real_keyname, &long_delay, &long_delay_range); + char real_keyname[N_KEYNAME_LENGTH]; // temp to hold the left side (key) of the = + int scan_matches = sscanf(keyname, "%"N_SCANF_KEYNAME"[^=]=%"SCNd64"_%"SCNd64, real_keyname, &long_delay, &long_delay_range); if (scan_matches == 2 || scan_matches == 3) { if (0 <= long_delay && long_delay < UINT32_MAX) { delay = (uint32_t)long_delay; - strcpy(keyname, real_keyname); // keyname[40], real_keyname[12] + static_assert(sizeof(keyname) >= sizeof(real_keyname), ""); + strcpy(keyname, real_keyname); if(0 < long_delay_range && long_delay_range < UINT32_MAX && long_delay_range > long_delay) { delay_range = (uint32_t)long_delay_range; diff --git a/src/daemon/led.c b/src/daemon/led.c index 4ece826c..30196b77 100644 --- a/src/daemon/led.c +++ b/src/daemon/led.c @@ -157,13 +157,13 @@ char* printrgb(const lighting* light, const usbdevice* kb){ b[i] = mb[k]; } // Make a buffer to track key names and to filter out duplicates - char names[N_KEYS_EXTENDED][11]; + char names[N_KEYS_EXTENDED][N_KEYNAME_LENGTH]; for(int i = 0; i < N_KEYS_EXTENDED; i++){ const char* name = kb->keymap[i].name; if(kb->keymap[i].led < 0 || !has_key(name, kb)) names[i][0] = 0; else - strncpy(names[i], name, 11); + strncpy(names[i], name, N_KEYNAME_LENGTH); } // Check to make sure these aren't all the same color int same = 1; diff --git a/src/gui/animscript.cpp b/src/gui/animscript.cpp index 52fa106f..0198001d 100644 --- a/src/gui/animscript.cpp +++ b/src/gui/animscript.cpp @@ -447,9 +447,9 @@ void AnimScript::readProcess(){ } if(line.startsWith("argb ")){ // Add a color to the buffer - char keyName[31]; + char keyName[N_KEYNAME_LENGTH]; QRgb keyColor = 0; - if(sscanf(line, "argb %30s %x", keyName, &keyColor) != 2) + if(sscanf(line, "argb %" N_SCANF_KEYNAME "s %x", keyName, &keyColor) != 2) continue; QRgb* inMap = _colorBuffer.colorForName(keyName); if(!inMap) diff --git a/src/libs/ckb-next/include/ckb-next/animation.h b/src/libs/ckb-next/include/ckb-next/animation.h index ee51b39c..92acc903 100644 --- a/src/libs/ckb-next/include/ckb-next/animation.h +++ b/src/libs/ckb-next/include/ckb-next/animation.h @@ -127,7 +127,7 @@ #define CKB_REAL_ANGLE(angle) fmod((-(angle) + 90.) * M_PI / 180. + M_PI * 2., M_PI * 2.) // Key definition -#define CKB_KEYNAME_MAX 12 +#define CKB_KEYNAME_MAX 40 typedef struct { char name[CKB_KEYNAME_MAX+1]; int x, y;