From 7c33e52580e7e414b3765b3c4fc21496f2c712b1 Mon Sep 17 00:00:00 2001 From: Karel Tucek Date: Mon, 19 Jan 2026 15:58:48 +0100 Subject: [PATCH 1/8] Build: Return dummy values from get_git_info() if not in a git repository --- ...ndle-None-revision-in-offline-projec.patch | 29 +++++++++++++++++++ scripts/common.py | 13 +++++++++ 2 files changed, 42 insertions(+) create mode 100644 patches/zephyr/0004-zephyr_module-handle-None-revision-in-offline-projec.patch diff --git a/patches/zephyr/0004-zephyr_module-handle-None-revision-in-offline-projec.patch b/patches/zephyr/0004-zephyr_module-handle-None-revision-in-offline-projec.patch new file mode 100644 index 000000000..87479e3bc --- /dev/null +++ b/patches/zephyr/0004-zephyr_module-handle-None-revision-in-offline-projec.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Karel Tucek +Date: Sun, 19 Jan 2026 16:00:00 +0100 +Subject: [PATCH] zephyr_module: handle None revision in offline projects + +When running in a directory without a .git (e.g., a jj workspace), +the revision can be None. Check for this before appending "-off" +suffix to avoid TypeError. + +--- + scripts/zephyr_module.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/scripts/zephyr_module.py b/scripts/zephyr_module.py +index 000000000..000000001 100755 +--- a/scripts/zephyr_module.py ++++ b/scripts/zephyr_module.py +@@ -585,7 +585,7 @@ def process_meta(zephyr_base, west_projs, modules, + manifest_project, manifest_dirty = _create_meta_project( + projects[0].posixpath) + manifest_off = manifest_project.get("remote") is None +- if manifest_off: ++ if manifest_off and manifest_project.get("revision") is not None: + manifest_project["revision"] += "-off" + + if manifest_project: +-- +2.44.0 + diff --git a/scripts/common.py b/scripts/common.py index 001bd64d1..8d5313aaf 100644 --- a/scripts/common.py +++ b/scripts/common.py @@ -5,7 +5,20 @@ def exec(cmd): return subprocess.check_output(cmd, shell=True).decode('utf-8').strip() +def is_git_repo(): + try: + subprocess.check_output(['git', 'rev-parse', '--git-dir'], stderr=subprocess.DEVNULL) + return True + except subprocess.CalledProcessError: + return False + def get_git_info(): + if not is_git_repo(): + return { + 'repo': 'unknown/unknown', + 'tag': 'unknown', + 'root': os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + } return { 'repo': exec('git remote get-url origin').replace('https://github.com/', '').replace('git@github.com:', '').replace('.git', ''), 'tag': exec('git tag --points-at HEAD') or exec('git rev-parse --short HEAD'), From 135ef70208b9228e4294aca7e738c71a2671bd0c Mon Sep 17 00:00:00 2001 From: Karel Tucek Date: Mon, 19 Jan 2026 17:53:25 +0100 Subject: [PATCH 2/8] Extend gitignore. --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index 2d48b6104..e9cd65948 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,8 @@ build .vscode/settings.json .jj mcux_include.json +compile_commands.json +.devices +Makefile +createTags.sh +tags From a0f305ff87dd6ac65f3fd44574ea8f877a5b67c4 Mon Sep 17 00:00:00 2001 From: Karel Tucek Date: Mon, 19 Jan 2026 11:31:25 +0100 Subject: [PATCH 3/8] Usb cmds: Led overrides mask USB variable --- device/src/keyboard/oled/oled.c | 4 +++ device/src/keyboard/oled/oled.h | 2 ++ right/src/led_manager.c | 4 ++- right/src/led_manager.h | 31 +++++++++++++++++ .../usb_commands/usb_command_get_variable.c | 28 +++++++++++++++ .../usb_commands/usb_command_set_variable.c | 34 +++++++++++++++++++ right/src/usb_protocol_handler.h | 1 + 7 files changed, 103 insertions(+), 1 deletion(-) diff --git a/device/src/keyboard/oled/oled.c b/device/src/keyboard/oled/oled.c index d04b2583e..f6de2c12f 100644 --- a/device/src/keyboard/oled/oled.c +++ b/device/src/keyboard/oled/oled.c @@ -12,6 +12,8 @@ #if DEVICE_HAS_OLED +uint8_t OledOverrideMode = 0; + #include #include #include "keyboard/oled/oled.h" @@ -349,6 +351,8 @@ void InitOled(void) { #else // DEVICE_HAS_OLED +uint8_t OledOverrideMode = 0; + void Oled_ActivateScreen(widget_t* screen, bool forceRedraw){}; void Oled_RequestRedraw(){}; void Oled_ShiftScreen(){}; diff --git a/device/src/keyboard/oled/oled.h b/device/src/keyboard/oled/oled.h index 45f258894..500e8fa0f 100644 --- a/device/src/keyboard/oled/oled.h +++ b/device/src/keyboard/oled/oled.h @@ -7,6 +7,8 @@ // Variables: + extern uint8_t OledOverrideMode; + extern const struct gpio_dt_spec oledEn; extern const struct gpio_dt_spec oledResetDt; extern const struct gpio_dt_spec oledCsDt; diff --git a/right/src/led_manager.c b/right/src/led_manager.c index fd46f0c6d..8e408a68e 100644 --- a/right/src/led_manager.c +++ b/right/src/led_manager.c @@ -28,6 +28,9 @@ bool KeyBacklightSleepModeActive = false; bool DisplaySleepModeActive = false; +led_override_uhk60_t Uhk60LedOverride = {0}; +uhk60_led_state_t Uhk60LedState = {0}; + uint8_t DisplayBrightness = 0xff; uint8_t KeyBacklightBrightness = 0xff; @@ -140,4 +143,3 @@ void LedManager_UpdateSleepModes() { EventVector_Set(EventVector_LedManagerFullUpdateNeeded); } } - diff --git a/right/src/led_manager.h b/right/src/led_manager.h index eb626e967..ad3fd3b21 100644 --- a/right/src/led_manager.h +++ b/right/src/led_manager.h @@ -8,8 +8,39 @@ #include "attributes.h" // Macros: + + #define UHK60_LED_COUNT 6 + #define UHK60_SEGMENT_DISPLAY_CHARS 3 + #define UHK60_SEGMENT_COUNT_PER_CHAR 14 + #define UHK60_SEGMENT_DISPLAY_SIZE (UHK60_SEGMENT_DISPLAY_CHARS * UHK60_SEGMENT_COUNT_PER_CHAR) + + #define LED_VALUE_OFF 0 + #define LED_VALUE_ON 1 + #define LED_VALUE_UNCHANGED 2 + +// Typedefs: + + typedef struct { + uint8_t mod : 1; + uint8_t fn : 1; + uint8_t mouse : 1; + uint8_t capsLock : 1; + uint8_t agent : 1; + uint8_t adaptive : 1; + uint8_t segmentDisplay : 1; + uint8_t reserved : 1; + } ATTR_PACKED led_override_uhk60_t; + + typedef struct { + uint8_t leds[UHK60_LED_COUNT]; // 6 bytes: mod, fn, mouse, capsLock, agent, adaptive + uint8_t segments[UHK60_SEGMENT_DISPLAY_SIZE]; // 42 bytes: 3 chars * 14 segments + } ATTR_PACKED uhk60_led_state_t; + // Variables: + extern led_override_uhk60_t Uhk60LedOverride; + extern uhk60_led_state_t Uhk60LedState; + extern uint8_t DisplayBrightness; extern uint8_t KeyBacklightBrightness; diff --git a/right/src/usb_commands/usb_command_get_variable.c b/right/src/usb_commands/usb_command_get_variable.c index d78b8a428..a976ed576 100644 --- a/right/src/usb_commands/usb_command_get_variable.c +++ b/right/src/usb_commands/usb_command_get_variable.c @@ -1,15 +1,21 @@ +#include #include "usb_protocol_handler.h" #include "usb_commands/usb_command_get_variable.h" +#include "led_manager.h" #include "key_matrix.h" #include "test_switches.h" #include "usb_report_updater.h" #include "macros/core.h" #include "config_manager.h" #include "usb_interfaces/usb_interface_generic_hid.h" +#include "keymap.h" +#include "layer.h" +#include "slot.h" #ifdef __ZEPHYR__ #include "proxy_log_backend.h" #include "state_sync.h" +#include "keyboard/oled/oled.h" #endif void UsbCommand_GetVariable(const uint8_t *GenericHidOutBuffer, uint8_t *GenericHidInBuffer) @@ -61,5 +67,27 @@ void UsbCommand_GetVariable(const uint8_t *GenericHidOutBuffer, uint8_t *Generic SetUsbTxBufferUint8(1, StateSync_VersionCheckEnabled); #endif break; + case UsbVariable_LedOverride: { + // Byte 1: UHK60 LED override flags + GenericHidInBuffer[1] = *(uint8_t*)&Uhk60LedOverride; + // Byte 2: OLED override mode +#if DEVICE_HAS_OLED + GenericHidInBuffer[2] = OledOverrideMode; +#else + GenericHidInBuffer[2] = 0; +#endif + // Bytes 3-34: per-key RGB override bitmap (32 bytes = 256 bits) + // Serialize colorOverridden from CurrentKeymap (layer 0, same on all layers) + memset(GenericHidInBuffer + 3, 0, 32); + for (uint8_t slotIdx = 0; slotIdx < SLOT_COUNT; slotIdx++) { + for (uint8_t inSlotIdx = 0; inSlotIdx < MAX_KEY_COUNT_PER_MODULE; inSlotIdx++) { + if (CurrentKeymap[0][slotIdx][inSlotIdx].colorOverridden) { + uint8_t keyId = slotIdx * 64 + inSlotIdx; + GenericHidInBuffer[3 + keyId / 8] |= (1 << (keyId % 8)); + } + } + } + break; + } } } diff --git a/right/src/usb_commands/usb_command_set_variable.c b/right/src/usb_commands/usb_command_set_variable.c index 6aff755a0..e3abe2979 100644 --- a/right/src/usb_commands/usb_command_set_variable.c +++ b/right/src/usb_commands/usb_command_set_variable.c @@ -1,3 +1,4 @@ +#include #include "keymap.h" #include "led_manager.h" #include "usb_protocol_handler.h" @@ -7,6 +8,10 @@ #include "usb_report_updater.h" #include "config_manager.h" #include "ledmap.h" +#include "layer.h" +#include "slot.h" +#include "event_scheduler.h" +#include "usb_interfaces/usb_interface_generic_hid.h" #if defined(__ZEPHYR__) && DEVICE_IS_KEYBOARD #include "keyboard/leds.h" @@ -15,6 +20,7 @@ #ifdef __ZEPHYR__ #include "proxy_log_backend.h" #include "state_sync.h" +#include "keyboard/oled/oled.h" #endif void UsbCommand_SetVariable(const uint8_t *GenericHidOutBuffer, uint8_t *GenericHidInBuffer) @@ -66,6 +72,34 @@ void UsbCommand_SetVariable(const uint8_t *GenericHidOutBuffer, uint8_t *Generic #endif break; + case UsbVariable_LedOverride: { + // Byte 2: UHK60 LED override flags + Uhk60LedOverride = *(led_override_uhk60_t*)&GenericHidOutBuffer[2]; + // Byte 3: OLED override mode +#if DEVICE_HAS_OLED + OledOverrideMode = GenericHidOutBuffer[3]; +#endif + // Bytes 4-35: per-key RGB override bitmap (32 bytes = 256 bits) + // Deserialize into colorOverridden for ALL layers + for (uint8_t slotIdx = 0; slotIdx < SLOT_COUNT; slotIdx++) { + for (uint8_t inSlotIdx = 0; inSlotIdx < MAX_KEY_COUNT_PER_MODULE; inSlotIdx++) { + uint8_t keyId = slotIdx * 64 + inSlotIdx; + bool isOverridden = (GenericHidOutBuffer[4 + keyId / 8] >> (keyId % 8)) & 1; + for (uint8_t layerId = 0; layerId < LayerId_Count; layerId++) { + CurrentKeymap[layerId][slotIdx][inSlotIdx].colorOverridden = isOverridden; + } + } + } +#ifdef __ZEPHYR__ + // Sync all layers to the left half + for (uint8_t layerId = 0; layerId < LayerId_Count; layerId++) { + // TODO: optimize this somehow + StateSync_UpdateLayer(layerId, true); + } +#endif + EventVector_Set(EventVector_LedMapUpdateNeeded); + break; + } default: break; } diff --git a/right/src/usb_protocol_handler.h b/right/src/usb_protocol_handler.h index 744923117..718c1df59 100644 --- a/right/src/usb_protocol_handler.h +++ b/right/src/usb_protocol_handler.h @@ -82,6 +82,7 @@ UsbVariable_ShellEnabled = 0x07, UsbVariable_ShellBuffer = 0x08, UsbVariable_FirmwareVersionCheckEnabled = 0x09, + UsbVariable_LedOverride = 0x0a, } usb_variable_id_t; typedef enum { From 189599eedaa1012b4d05af585ca5fc86f5963a82 Mon Sep 17 00:00:00 2001 From: Karel Tucek Date: Mon, 19 Jan 2026 11:34:43 +0100 Subject: [PATCH 4/8] Usb cmds: Add UsbCommandId_SetUhk60LedState command for UHK60 LED control --- right/src/led_display.c | 60 +++++++++++++++++-- right/src/usb_commands/CMakeLists.txt | 1 + .../usb_command_set_uhk60_led_state.c | 29 +++++++++ .../usb_command_set_uhk60_led_state.h | 12 ++++ .../usb_commands/usb_command_set_variable.c | 2 +- right/src/usb_protocol_handler.c | 4 ++ right/src/usb_protocol_handler.h | 1 + 7 files changed, 103 insertions(+), 6 deletions(-) create mode 100644 right/src/usb_commands/usb_command_set_uhk60_led_state.c create mode 100644 right/src/usb_commands/usb_command_set_uhk60_led_state.h diff --git a/right/src/led_display.c b/right/src/led_display.c index f03830438..de7f88b19 100644 --- a/right/src/led_display.c +++ b/right/src/led_display.c @@ -152,6 +152,13 @@ void LedDisplay_SetText(uint8_t length, const char* text) for (uint8_t ledId=0; ledId= 1 && layerId <= 3) { + layerStates[layerId - 1] = true; } if (LayerId_Fn2 <= layerId && layerId <= LayerId_Fn5) { - LedDriverValues[LedDriverId_Left][layerLedIds[LayerId_Fn-1]] = DisplayBrightness; + layerStates[LayerId_Fn - 1] = true; + } + + // Apply overrides if set + if (Uhk60LedOverride.mod) { + layerStates[0] = Uhk60LedState.leds[0] == LED_VALUE_ON; + } + if (Uhk60LedOverride.fn) { + layerStates[1] = Uhk60LedState.leds[1] == LED_VALUE_ON; + } + if (Uhk60LedOverride.mouse) { + layerStates[2] = Uhk60LedState.leds[2] == LED_VALUE_ON; + } + + // Set the actual LED values + for (uint8_t i = 0; i < 3; i++) { + LedDriverValues[LedDriverId_Left][layerLedIds[i]] = layerStates[i] ? DisplayBrightness : 0; } #endif } @@ -184,7 +210,31 @@ void LedDisplay_SetIcon(led_display_icon_t icon, bool isEnabled) { ledIconStates[icon] = isEnabled; #ifndef __ZEPHYR__ - LedDriverValues[LedDriverId_Left][iconLedIds[icon]] = isEnabled ? DisplayBrightness : 0; + bool effectiveState = isEnabled; + + // Check if this icon is overridden and use user-set value instead + // Icon LED indices in Uhk60LedState.leds: 3=capsLock, 4=agent, 5=adaptive + switch (icon) { + case LedDisplayIcon_CapsLock: + if (Uhk60LedOverride.capsLock) { + effectiveState = Uhk60LedState.leds[3] == LED_VALUE_ON; + } + break; + case LedDisplayIcon_Agent: + if (Uhk60LedOverride.agent) { + effectiveState = Uhk60LedState.leds[4] == LED_VALUE_ON; + } + break; + case LedDisplayIcon_Adaptive: + if (Uhk60LedOverride.adaptive) { + effectiveState = Uhk60LedState.leds[5] == LED_VALUE_ON; + } + break; + default: + break; + } + + LedDriverValues[LedDriverId_Left][iconLedIds[icon]] = effectiveState ? DisplayBrightness : 0; #endif } diff --git a/right/src/usb_commands/CMakeLists.txt b/right/src/usb_commands/CMakeLists.txt index 9ba4fc83c..680eab8a8 100644 --- a/right/src/usb_commands/CMakeLists.txt +++ b/right/src/usb_commands/CMakeLists.txt @@ -22,6 +22,7 @@ target_sources(${PROJECT_NAME} PRIVATE $<$:${CMAKE_CURRENT_SOURCE_DIR}/usb_command_set_i2c_baud_rate.c> $<$:${CMAKE_CURRENT_SOURCE_DIR}/usb_command_set_led_pwm_brightness.c> $<$:${CMAKE_CURRENT_SOURCE_DIR}/usb_command_set_test_led.c> + $<$:${CMAKE_CURRENT_SOURCE_DIR}/usb_command_set_uhk60_led_state.c> usb_command_set_variable.c usb_command_switch_keymap.c usb_command_write_config.c diff --git a/right/src/usb_commands/usb_command_set_uhk60_led_state.c b/right/src/usb_commands/usb_command_set_uhk60_led_state.c new file mode 100644 index 000000000..8130a3cfd --- /dev/null +++ b/right/src/usb_commands/usb_command_set_uhk60_led_state.c @@ -0,0 +1,29 @@ +#include "usb_protocol_handler.h" +#include "usb_commands/usb_command_set_uhk60_led_state.h" +#include "led_manager.h" +#include "led_display.h" + +void UsbCommand_SetUhk60LedState(const uint8_t *GenericHidOutBuffer, uint8_t *GenericHidInBuffer) +{ +#ifndef __ZEPHYR__ + const uint8_t *data = GenericHidOutBuffer + 1; + + // Process 6 LED values + for (uint8_t i = 0; i < UHK60_LED_COUNT; i++) { + uint8_t value = data[i]; + if (value != LED_VALUE_UNCHANGED) { + Uhk60LedState.leds[i] = value; + } + } + + // Process 42 segment display values (3 chars * 14 segments) + for (uint8_t i = 0; i < UHK60_SEGMENT_DISPLAY_SIZE; i++) { + uint8_t value = data[UHK60_LED_COUNT + i]; + if (value != LED_VALUE_UNCHANGED) { + Uhk60LedState.segments[i] = value; + } + } + + LedDisplay_UpdateAll(); +#endif +} diff --git a/right/src/usb_commands/usb_command_set_uhk60_led_state.h b/right/src/usb_commands/usb_command_set_uhk60_led_state.h new file mode 100644 index 000000000..fdd25662d --- /dev/null +++ b/right/src/usb_commands/usb_command_set_uhk60_led_state.h @@ -0,0 +1,12 @@ +#ifndef __USB_COMMAND_SET_UHK60_LED_STATE_H__ +#define __USB_COMMAND_SET_UHK60_LED_STATE_H__ + +// Includes: + + #include + +// Functions: + + void UsbCommand_SetUhk60LedState(const uint8_t *GenericHidOutBuffer, uint8_t *GenericHidInBuffer); + +#endif diff --git a/right/src/usb_commands/usb_command_set_variable.c b/right/src/usb_commands/usb_command_set_variable.c index e3abe2979..1e90d8337 100644 --- a/right/src/usb_commands/usb_command_set_variable.c +++ b/right/src/usb_commands/usb_command_set_variable.c @@ -97,7 +97,7 @@ void UsbCommand_SetVariable(const uint8_t *GenericHidOutBuffer, uint8_t *Generic StateSync_UpdateLayer(layerId, true); } #endif - EventVector_Set(EventVector_LedMapUpdateNeeded); + EventVector_Set(EventVector_LedManagerFullUpdateNeeded); break; } default: diff --git a/right/src/usb_protocol_handler.c b/right/src/usb_protocol_handler.c index edae63f14..e112b86eb 100644 --- a/right/src/usb_protocol_handler.c +++ b/right/src/usb_protocol_handler.c @@ -26,6 +26,7 @@ #else #include "usb_commands/usb_command_set_test_led.h" #include "usb_commands/usb_command_set_led_pwm_brightness.h" +#include "usb_commands/usb_command_set_uhk60_led_state.h" #include "usb_commands/usb_command_get_adc_value.h" #include "usb_commands/usb_command_jump_to_module_bootloader.h" #include "usb_commands/usb_command_send_kboot_command_to_module.h" @@ -142,6 +143,9 @@ void UsbProtocolHandler(uint8_t *GenericHidOutBuffer, uint8_t *GenericHidInBuffe case UsbCommandId_SetI2cBaudRate: UsbCommand_SetI2cBaudRate(GenericHidOutBuffer, GenericHidInBuffer); break; + case UsbCommandId_SetUhk60LedState: + UsbCommand_SetUhk60LedState(GenericHidOutBuffer, GenericHidInBuffer); + break; #endif default: SetUsbTxBufferUint8(0, UsbStatusCode_InvalidCommand); diff --git a/right/src/usb_protocol_handler.h b/right/src/usb_protocol_handler.h index 718c1df59..aedc2efa8 100644 --- a/right/src/usb_protocol_handler.h +++ b/right/src/usb_protocol_handler.h @@ -69,6 +69,7 @@ UsbCommandId_EraseBleSettings = 0x1d, UsbCommandId_ExecShellCommand = 0x1e, UsbCommandId_ReadOled = 0x1f, + UsbCommandId_SetUhk60LedState = 0x20, } usb_command_id_t; typedef enum { From 2e44a1218f20e67260cb8a3c1e0f24cba7f5576c Mon Sep 17 00:00:00 2001 From: Karel Tucek Date: Mon, 19 Jan 2026 11:38:14 +0100 Subject: [PATCH 5/8] Usb cmds: Refactor OLED pixel writing command --- .../src/keyboard/oled/screens/canvas_screen.c | 26 +++++++++++++++++++ .../src/keyboard/oled/screens/canvas_screen.h | 1 + .../src/usb_commands/usb_command_draw_oled.c | 25 +++++++++++++++--- 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/device/src/keyboard/oled/screens/canvas_screen.c b/device/src/keyboard/oled/screens/canvas_screen.c index 67e68afb1..9a7efb8d4 100644 --- a/device/src/keyboard/oled/screens/canvas_screen.c +++ b/device/src/keyboard/oled/screens/canvas_screen.c @@ -27,6 +27,32 @@ void CanvasScreen_Draw(uint16_t x, uint16_t y, uint8_t* data, uint16_t len) { Oled_RequestRedraw(); } +void CanvasScreen_DrawPacked(uint16_t x, uint16_t y, uint8_t* data, uint16_t pixelCount) { + ScreenManager_ActivateScreen(ScreenId_Canvas); + widget_t canvas = (widget_t) { + .x = x, + .y = y, + .w = MIN(DISPLAY_WIDTH-x, pixelCount), + .h = 1, + }; + // Mark the buffer region as dirty + Framebuffer_Clear(&canvas, OledBuffer); + + for (uint16_t i = 0; i < pixelCount; i++) { + if (x >= DISPLAY_WIDTH) { + printk("Usb attempting to draw outside of display range!\n"); + break; + } + // 2 pixels per byte: high nibble is first pixel, low nibble is second + uint8_t packedByte = data[i / 2]; + uint8_t pixelValue = (i % 2 == 0) ? (packedByte >> 4) : (packedByte & 0x0F); + // Scale 4-bit value to 8-bit for Framebuffer_SetPixel + Framebuffer_SetPixel(OledBuffer, x, y, pixelValue << 4); + x++; + } + Oled_RequestRedraw(); +} + void CanvasScreen_Init() { CanvasScreen = &CanvasWidget; } diff --git a/device/src/keyboard/oled/screens/canvas_screen.h b/device/src/keyboard/oled/screens/canvas_screen.h index e18022874..c1b481e45 100644 --- a/device/src/keyboard/oled/screens/canvas_screen.h +++ b/device/src/keyboard/oled/screens/canvas_screen.h @@ -21,5 +21,6 @@ void CanvasScreen_Init(); void CanvasScreen_Draw(uint16_t x, uint16_t y, uint8_t* data, uint16_t len); + void CanvasScreen_DrawPacked(uint16_t x, uint16_t y, uint8_t* data, uint16_t pixelCount); #endif diff --git a/right/src/usb_commands/usb_command_draw_oled.c b/right/src/usb_commands/usb_command_draw_oled.c index 08b10f7bf..661a12b2d 100644 --- a/right/src/usb_commands/usb_command_draw_oled.c +++ b/right/src/usb_commands/usb_command_draw_oled.c @@ -14,10 +14,27 @@ void UsbCommand_DrawOled(const uint8_t *GenericHidOutBuffer, uint8_t *GenericHidInBuffer) { #if defined(__ZEPHYR__) && DEVICE_HAS_OLED - uint8_t x = GetUsbRxBufferUint8(1); - uint8_t y = GetUsbRxBufferUint8(2); - uint8_t len = GetUsbRxBufferUint8(3); + uint8_t offset = 1; - CanvasScreen_Draw(x, y, ((uint8_t*)GenericHidOutBuffer) + 4, len); + while (offset < USB_GENERIC_HID_OUT_BUFFER_LENGTH - 3) { + uint8_t x = GenericHidOutBuffer[offset]; + if (x == 255) { + break; // End of data marker + } + + uint8_t y = GenericHidOutBuffer[offset + 1]; + uint8_t pixelCount = GenericHidOutBuffer[offset + 2]; + offset += 3; + + // Payload contains 2 pixels per byte (4-bit grayscale each) + uint8_t payloadBytes = (pixelCount + 1) / 2; + + if (offset + payloadBytes > USB_GENERIC_HID_OUT_BUFFER_LENGTH) { + break; // Not enough data + } + + CanvasScreen_DrawPacked(x, y, ((uint8_t*)GenericHidOutBuffer) + offset, pixelCount); + offset += payloadBytes; + } #endif } From 07d208af6a56561a872f61cf6c47c24ceae20d4c Mon Sep 17 00:00:00 2001 From: Karel Tucek Date: Mon, 19 Jan 2026 11:39:56 +0100 Subject: [PATCH 6/8] Usb cmds: Add set per-key-RGB command --- .../keyboard/oled/screens/screen_manager.c | 10 +++- doc-dev/other/usb_led_controls.md | 0 right/src/usb_commands/CMakeLists.txt | 1 + .../usb_commands/usb_command_get_variable.c | 1 + .../usb_command_set_per_key_rgb.c | 53 +++++++++++++++++++ .../usb_command_set_per_key_rgb.h | 12 +++++ .../usb_commands/usb_command_set_variable.c | 2 + right/src/usb_protocol_handler.c | 4 ++ right/src/usb_protocol_handler.h | 1 + 9 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 doc-dev/other/usb_led_controls.md create mode 100644 right/src/usb_commands/usb_command_set_per_key_rgb.c create mode 100644 right/src/usb_commands/usb_command_set_per_key_rgb.h diff --git a/device/src/keyboard/oled/screens/screen_manager.c b/device/src/keyboard/oled/screens/screen_manager.c index 3638f2d9d..b5dcccdfc 100644 --- a/device/src/keyboard/oled/screens/screen_manager.c +++ b/device/src/keyboard/oled/screens/screen_manager.c @@ -48,7 +48,9 @@ void ScreenManager_ActivateScreen(screen_id_t screen) screenPtr = MainScreen; break; case ScreenId_Canvas: - EventScheduler_Reschedule(Timer_GetCurrentTime() + CANVAS_TIMEOUT, EventSchedulerEvent_SwitchScreen, "ScreenManager - switch to main screen"); + if (!OledOverrideMode) { + EventScheduler_Reschedule(Timer_GetCurrentTime() + CANVAS_TIMEOUT, EventSchedulerEvent_SwitchScreen, "ScreenManager - switch to main screen"); + } screenPtr = CanvasScreen; break; case ScreenId_Notification: @@ -70,7 +72,11 @@ void ScreenManager_ActivateScreen(screen_id_t screen) void ScreenManager_SwitchScreenEvent() { - ScreenManager_ActivateScreen(ScreenId_Main); + if (OledOverrideMode) { + ScreenManager_ActivateScreen(ScreenId_Canvas); + } else { + ScreenManager_ActivateScreen(ScreenId_Main); + } } void ScreenManager_Init() diff --git a/doc-dev/other/usb_led_controls.md b/doc-dev/other/usb_led_controls.md new file mode 100644 index 000000000..e69de29bb diff --git a/right/src/usb_commands/CMakeLists.txt b/right/src/usb_commands/CMakeLists.txt index 680eab8a8..55ae61489 100644 --- a/right/src/usb_commands/CMakeLists.txt +++ b/right/src/usb_commands/CMakeLists.txt @@ -23,6 +23,7 @@ target_sources(${PROJECT_NAME} PRIVATE $<$:${CMAKE_CURRENT_SOURCE_DIR}/usb_command_set_led_pwm_brightness.c> $<$:${CMAKE_CURRENT_SOURCE_DIR}/usb_command_set_test_led.c> $<$:${CMAKE_CURRENT_SOURCE_DIR}/usb_command_set_uhk60_led_state.c> + usb_command_set_per_key_rgb.c usb_command_set_variable.c usb_command_switch_keymap.c usb_command_write_config.c diff --git a/right/src/usb_commands/usb_command_get_variable.c b/right/src/usb_commands/usb_command_get_variable.c index a976ed576..a7a3f3ec7 100644 --- a/right/src/usb_commands/usb_command_get_variable.c +++ b/right/src/usb_commands/usb_command_get_variable.c @@ -73,6 +73,7 @@ void UsbCommand_GetVariable(const uint8_t *GenericHidOutBuffer, uint8_t *Generic // Byte 2: OLED override mode #if DEVICE_HAS_OLED GenericHidInBuffer[2] = OledOverrideMode; + printk("========= reading oled %d\n", OledOverrideMode); #else GenericHidInBuffer[2] = 0; #endif diff --git a/right/src/usb_commands/usb_command_set_per_key_rgb.c b/right/src/usb_commands/usb_command_set_per_key_rgb.c new file mode 100644 index 000000000..f26a17746 --- /dev/null +++ b/right/src/usb_commands/usb_command_set_per_key_rgb.c @@ -0,0 +1,53 @@ +#include "usb_commands/usb_command_set_per_key_rgb.h" +#include "usb_protocol_handler.h" +#include "usb_interfaces/usb_interface_generic_hid.h" +#include "keymap.h" +#include "layer.h" +#include "slot.h" +#include "event_scheduler.h" +#include + +#ifdef __ZEPHYR__ +#include "state_sync.h" +#endif + +void UsbCommand_SetPerKeyRgb(const uint8_t *GenericHidOutBuffer, uint8_t *GenericHidInBuffer) +{ + uint8_t keyCount = GenericHidOutBuffer[1]; + uint8_t offset = 2; + + for (uint8_t i = 0; i < keyCount; i++) { + if (offset + 4 > USB_GENERIC_HID_OUT_BUFFER_LENGTH) { + break; // Not enough data + } + + uint8_t keyId = GenericHidOutBuffer[offset]; + rgb_t rgb; + rgb.red = GenericHidOutBuffer[offset + 1]; + rgb.green = GenericHidOutBuffer[offset + 2]; + rgb.blue = GenericHidOutBuffer[offset + 3]; + offset += 4; + + uint8_t slotIdx = keyId / 64; + uint8_t inSlotIdx = keyId % 64; + + if (slotIdx >= SLOT_COUNT || inSlotIdx >= MAX_KEY_COUNT_PER_MODULE) { + continue; // Invalid key ID, skip + } + + // Set the color on all layers + for (uint8_t layerId = 0; layerId < LayerId_Count; layerId++) { + CurrentKeymap[layerId][slotIdx][inSlotIdx].colorOverridden = true; + CurrentKeymap[layerId][slotIdx][inSlotIdx].color = rgb; + } + } + +#ifdef __ZEPHYR__ + // Sync all layers to the left half + for (uint8_t layerId = 0; layerId < LayerId_Count; layerId++) { + StateSync_UpdateLayer(layerId, true); + } +#endif + + EventVector_Set(EventVector_LedMapUpdateNeeded); +} diff --git a/right/src/usb_commands/usb_command_set_per_key_rgb.h b/right/src/usb_commands/usb_command_set_per_key_rgb.h new file mode 100644 index 000000000..ddf33d455 --- /dev/null +++ b/right/src/usb_commands/usb_command_set_per_key_rgb.h @@ -0,0 +1,12 @@ +#ifndef __USB_COMMAND_SET_PER_KEY_RGB_H__ +#define __USB_COMMAND_SET_PER_KEY_RGB_H__ + +// Includes: + + #include + +// Functions: + + void UsbCommand_SetPerKeyRgb(const uint8_t *GenericHidOutBuffer, uint8_t *GenericHidInBuffer); + +#endif diff --git a/right/src/usb_commands/usb_command_set_variable.c b/right/src/usb_commands/usb_command_set_variable.c index 1e90d8337..a863869e3 100644 --- a/right/src/usb_commands/usb_command_set_variable.c +++ b/right/src/usb_commands/usb_command_set_variable.c @@ -79,6 +79,8 @@ void UsbCommand_SetVariable(const uint8_t *GenericHidOutBuffer, uint8_t *Generic #if DEVICE_HAS_OLED OledOverrideMode = GenericHidOutBuffer[3]; #endif + + printk("============= Writing oled %d\n", OledOverrideMode); // Bytes 4-35: per-key RGB override bitmap (32 bytes = 256 bits) // Deserialize into colorOverridden for ALL layers for (uint8_t slotIdx = 0; slotIdx < SLOT_COUNT; slotIdx++) { diff --git a/right/src/usb_protocol_handler.c b/right/src/usb_protocol_handler.c index e112b86eb..b1bfb1ad2 100644 --- a/right/src/usb_protocol_handler.c +++ b/right/src/usb_protocol_handler.c @@ -16,6 +16,7 @@ #include "usb_commands/usb_command_launch_storage_transfer.h" #include "usb_commands/usb_command_get_module_property.h" #include "usb_commands/usb_command_exec_shell_command.h" +#include "usb_commands/usb_command_set_per_key_rgb.h" #ifdef __ZEPHYR__ #include "usb_commands/usb_command_draw_oled.h" @@ -147,6 +148,9 @@ void UsbProtocolHandler(uint8_t *GenericHidOutBuffer, uint8_t *GenericHidInBuffe UsbCommand_SetUhk60LedState(GenericHidOutBuffer, GenericHidInBuffer); break; #endif + case UsbCommandId_SetPerKeyRgb: + UsbCommand_SetPerKeyRgb(GenericHidOutBuffer, GenericHidInBuffer); + break; default: SetUsbTxBufferUint8(0, UsbStatusCode_InvalidCommand); break; diff --git a/right/src/usb_protocol_handler.h b/right/src/usb_protocol_handler.h index aedc2efa8..bacead595 100644 --- a/right/src/usb_protocol_handler.h +++ b/right/src/usb_protocol_handler.h @@ -70,6 +70,7 @@ UsbCommandId_ExecShellCommand = 0x1e, UsbCommandId_ReadOled = 0x1f, UsbCommandId_SetUhk60LedState = 0x20, + UsbCommandId_SetPerKeyRgb = 0x21, } usb_command_id_t; typedef enum { From 4e636347e7efdcd56903eb8090da56c9c766a291 Mon Sep 17 00:00:00 2001 From: Karel Tucek Date: Mon, 19 Jan 2026 17:46:30 +0100 Subject: [PATCH 7/8] DOCS: add led controls documentation --- doc-dev/other/usb_led_controls.md | 101 ++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/doc-dev/other/usb_led_controls.md b/doc-dev/other/usb_led_controls.md index e69de29bb..d463b2843 100644 --- a/doc-dev/other/usb_led_controls.md +++ b/doc-dev/other/usb_led_controls.md @@ -0,0 +1,101 @@ +# USB LED Controls + +This document describes USB commands for controlling LEDs and displays on UHK keyboards. + +For easy testing, we include an `UHKID` argument that can be used to target a specific UHK in a multi-UHK environment. Feel free to set it to empty string + +```bash +UHKID=--serial-number=1777777765 +``` + +## LED Override Mask (0x0a) + +Get/set override flags for LEDs. The mask controls which LED subsystems are in override mode. + +**Get current mask:** + +```bash +./send-command.ts $UHKID 0x12 0x0a +``` + +**Set mask:** +```bash +./send-command.ts $UHKID 0x13 0x0a 255 255 128 17 +``` + +Format: `0x13 0x0a ` + +- Byte 1: UHK60 LED override flags (bitfield: mod, fn, mouse, capsLock, agent, adaptive, segmentDisplay) +- Byte 2: OLED override mode +- Bytes 3-34: Per-key RGB override bitmap (256 bits for key IDs) + +## UHK60 LED Panel (0x20) + +Set UHK60 indicator LEDs and 14-segment display. + +```bash +./send-command.ts $UHKID 0x20 0 1 0 1 0 1 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 1 0 0 0 0 0 0 1 0 0 0 0 +``` + +Format: `0x20 <6 LED bytes> <42 segment bytes>` + +- Bytes 1-6: LED brightness (mod, fn, mouse, capsLock, agent, adaptive) +- Bytes 7-48: 14-segment display (3 chars × 14 segments) + +Reset override: +```bash +./send-command.ts $UHKID 0x13 0x0a 0 0 0 0 +``` + +## Per-Key RGB (0x21) + +Set individual key colors. + +```bash +# First enable per-key override in mask +./send-command.ts $UHKID 0x13 0x0a 0 0 255 255 255 255 255 255 255 + +# Set key 5 to red, key 10 to green +./send-command.ts $UHKID 0x21 2 5 255 0 0 10 0 255 0 +``` + +Format: `0x21 [ ]...` + +- Byte 1: Number of keys to set +- Then for each key: keyId (slot×64 + inSlotIdx), R, G, B values + +Reset override: +```bash +./send-command.ts $UHKID 0x13 0x0a 000 000 000 000 000 000 000 000 000 +``` + +## OLED Display (0x15) + +Draw pixels on the UHK80 OLED (256×64, 4-bit grayscale). + +**Enable OLED override:** +```bash +./send-command.ts $UHKID 0x13 0x0a 0 255 0 +``` + +**Show endianity:** +```bash +./send-command.ts $UHKID 0x15 128 32 8 0xFF 0x0F 0xF0 0xFF 255 +``` + +**Draw a heart:** +```bash +./send-command.ts $UHKID 0x15 \ + 8 13 6 0x0f 0xf0 0xff \ + 8 14 7 0xf0 0x0f 0x00 0xf0 \ + 8 15 6 0x0f 0x00 0x0f \ + 8 16 6 0x00 0xf0 0xf0 \ + 8 17 6 0x00 0x0f 0x00 \ + 0xff +``` + +Format: `0x15 [ ...]... 0xff` + +- Each segment: x, y, pixel count, then packed pixel data +- Pixels are 4-bit grayscale (0x0=black, 0xf=white), 2 per byte (high nibble first) +- Terminate with x=0xff From ab3c5ea9116da19c57541e41660a831c2fde21eb Mon Sep 17 00:00:00 2001 From: Karel Tucek Date: Tue, 20 Jan 2026 17:15:51 +0100 Subject: [PATCH 8/8] Fix compilation. --- right/src/usb_commands/usb_command_get_variable.c | 1 - right/src/usb_commands/usb_command_set_variable.c | 1 - 2 files changed, 2 deletions(-) diff --git a/right/src/usb_commands/usb_command_get_variable.c b/right/src/usb_commands/usb_command_get_variable.c index a7a3f3ec7..a976ed576 100644 --- a/right/src/usb_commands/usb_command_get_variable.c +++ b/right/src/usb_commands/usb_command_get_variable.c @@ -73,7 +73,6 @@ void UsbCommand_GetVariable(const uint8_t *GenericHidOutBuffer, uint8_t *Generic // Byte 2: OLED override mode #if DEVICE_HAS_OLED GenericHidInBuffer[2] = OledOverrideMode; - printk("========= reading oled %d\n", OledOverrideMode); #else GenericHidInBuffer[2] = 0; #endif diff --git a/right/src/usb_commands/usb_command_set_variable.c b/right/src/usb_commands/usb_command_set_variable.c index a863869e3..b8e0b487c 100644 --- a/right/src/usb_commands/usb_command_set_variable.c +++ b/right/src/usb_commands/usb_command_set_variable.c @@ -80,7 +80,6 @@ void UsbCommand_SetVariable(const uint8_t *GenericHidOutBuffer, uint8_t *Generic OledOverrideMode = GenericHidOutBuffer[3]; #endif - printk("============= Writing oled %d\n", OledOverrideMode); // Bytes 4-35: per-key RGB override bitmap (32 bytes = 256 bits) // Deserialize into colorOverridden for ALL layers for (uint8_t slotIdx = 0; slotIdx < SLOT_COUNT; slotIdx++) {