From df95d566cd65ff312443a497cae9716da46b83fd Mon Sep 17 00:00:00 2001 From: Dronakurl Date: Fri, 13 Feb 2026 21:08:54 +0100 Subject: [PATCH 1/2] fix: correct Clevo keyboard LED mapping The KeyboardLayoutManager returns LED names with "Key: " prefix (e.g., "Key: W") but the hardware value lookup was checking for just "W", causing all keys to map to hardware LED 0 (Left Ctrl). Added GetCorrectHWValue() function that properly maps all 126 keys including: - Standard keyboard keys (F-row, number row, QWERTY, etc.) - Arrow keys (Up/Left/Down/Right Arrow) - Numpad keys (Number Pad 0-9, /, *, -, +, ., Enter) This fixes per-key RGB control on Clevo ITE 8291 keyboards. Co-Authored-By: Claude Sonnet 4.5 refactor: remove redundant GetCorrectHWValue function The Clevo keyboard controller had duplicate hardware LED value mappings in both clevo_tkl_values array and GetCorrectHWValue(). This removes the ~100 if-statement function and uses KeyboardLayoutManager.GetKeyValueAt() directly, aligning with the pattern used by other keyboard controllers. Co-Authored-By: Claude Sonnet 4.5 Revert "refactor: remove redundant GetCorrectHWValue function" This reverts commit afe1141ec7f2ff0a9f0529098c49b84f7e991d53. refactor: replace GetCorrectHWValue if-statements with unordered_map Replace ~100 if-statements in GetCorrectHWValue() with a static unordered_map for O(1) hash lookup instead of O(n) string comparisons. This improves: - Performance: O(1) vs O(n) lookup - Maintainability: single map definition vs scattered if-statements - Readability: compact data structure vs verbose code The mapping is still required because Clevo hardware LED positions don't match standard keyboard matrix order. Co-Authored-By: Claude Sonnet 4.5 --- .../ClevoKeyboardDevices.cpp | 116 +++++++++++------- .../RGBController_ClevoKeyboard.cpp | 69 ++++++++++- .../RGBController_ClevoKeyboard.h | 2 + 3 files changed, 142 insertions(+), 45 deletions(-) diff --git a/Controllers/ClevoKeyboardController/ClevoKeyboardDevices.cpp b/Controllers/ClevoKeyboardController/ClevoKeyboardDevices.cpp index 271ea8587..07992c411 100644 --- a/Controllers/ClevoKeyboardController/ClevoKeyboardDevices.cpp +++ b/Controllers/ClevoKeyboardController/ClevoKeyboardDevices.cpp @@ -27,89 +27,119 @@ \*---------------------------------------------------------*/ /*---------------------------------------------------------*\ -| LED values in TKL order (fn_row + main + extras) | +| LED values in FINAL LED ORDER | | | -| Values follow the key order in KeyboardLayoutManager.cpp. | -| For ANSI-only keys (not present on this ISO keyboard), | -| use 0 as a placeholder - they won't be displayed. | -| Numpad values are added via edit_keys. | +| CRITICAL: This array MUST be in the order LEDs appear | +| in OpenRGB (row-major order after sorting by row,col). | +| | +| The KeyboardLayoutManager sorts keys by (row, col), so | +| this array must match that sorted order, not zone order! | \*---------------------------------------------------------*/ static const std::vector clevo_tkl_values = { /*---------------------------------------------------------*\ - | Function row (keyboard_zone_fn_row) | + | Row 0: F-row + extras | \*---------------------------------------------------------*/ 105, // Escape 106, 107, 108, 109, // F1-F4 110, 111, 112, 113, // F5-F8 114, 115, 116, 117, // F9-F12 + 118, // Print Screen /*---------------------------------------------------------*\ - | Main block - Row 1 (keyboard_zone_main) | + | Row 1: Number row + extras | \*---------------------------------------------------------*/ 84, // Back tick 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, // 1-0 95, 96, // Minus, Equals 98, // Backspace + 119, // Insert + 99, // Num Lock + 100, // Numpad / + 101, // Numpad * + 102, // Numpad - + 121, // Home + 123, // Page Up /*---------------------------------------------------------*\ - | Main block - Row 2 | + | Row 2: QWERTY row + extras | \*---------------------------------------------------------*/ 63, // Tab - 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, // Q-P - 75, 76, // [ ] - 0, // ANSI backslash (not on ISO) + 65, // Q + 66, // W + 67, // E + 68, // R + 69, // T + 70, // Y + 71, // U + 72, // I + 73, // O + 74, // P + 75, // [ + 76, // ] + 120, // Delete + 78, // Numpad 7 + 79, // Numpad 8 + 80, // Numpad 9 + 81, // Numpad + + 122, // End + 124, // Page Down /*---------------------------------------------------------*\ - | Main block - Row 3 | + | Row 3: ASDF row + numpad | \*---------------------------------------------------------*/ 42, // Caps Lock - 44, 45, 46, 47, 48, 49, 50, 51, 52, // A-L - 53, 54, // ; ' - 55, // ISO # (POUND) - 77, // Enter (ANSI/ISO share same LED) + 44, // A + 45, // S + 46, // D + 47, // F + 48, // G + 49, // H + 50, // J + 51, // K + 52, // L + 53, // ; + 54, // ' + 55, // ISO # + 77, // Enter + 57, // Numpad 4 + 58, // Numpad 5 + 59, // Numpad 6 /*---------------------------------------------------------*\ - | Main block - Row 4 | + | Row 4: ZXCV row + numpad + Up arrow | \*---------------------------------------------------------*/ 22, // Left Shift 23, // ISO backslash - 24, 25, 26, 27, 28, 29, 30, 31, 32, // Z-. (9 keys) + 24, // Z + 25, // X + 26, // C + 27, // V + 28, // B + 29, // N + 30, // M + 31, // , + 32, // . 33, // / 35, // Right Shift + 36, // Numpad 1 + 37, // Numpad 2 + 38, // Numpad 3 + 14, // Up arrow /*---------------------------------------------------------*\ - | Main block - Row 5 | + | Row 5: Modifiers + arrows + numpad | \*---------------------------------------------------------*/ - 0, // Left Ctrl 3, // Left Win 4, // Left Alt 7, // Space 10, // Right Alt - 0, // Right Fn (removed via edit_keys) - 0, // Menu (removed via edit_keys) 12, // Right Ctrl - - /*---------------------------------------------------------*\ - | Extras - Navigation cluster (keyboard_zone_extras) | - \*---------------------------------------------------------*/ - 118, // Print Screen - 0, // Scroll Lock (removed via edit_keys) - 0, // Pause (removed via edit_keys) - 119, // Insert - 121, // Home - 123, // Page Up - 120, // Delete - 122, // End - 124, // Page Down - - /*---------------------------------------------------------*\ - | Extras - Arrow keys | - \*---------------------------------------------------------*/ - 14, // Up - 13, // Left - 18, // Down - 15, // Right + 16, // Numpad 0 + 17, // Numpad . + 13, // Left arrow + 18, // Down arrow + 15, // Right arrow }; keyboard_keymap_overlay_values clevo_keyboard_layout diff --git a/Controllers/ClevoKeyboardController/RGBController_ClevoKeyboard.cpp b/Controllers/ClevoKeyboardController/RGBController_ClevoKeyboard.cpp index 017c41150..394843334 100644 --- a/Controllers/ClevoKeyboardController/RGBController_ClevoKeyboard.cpp +++ b/Controllers/ClevoKeyboardController/RGBController_ClevoKeyboard.cpp @@ -11,6 +11,8 @@ #include "RGBController_ClevoKeyboard.h" #include "KeyboardLayoutManager.h" +#include +#include /**------------------------------------------------------------------*\ @name CLEVO Keyboard @@ -253,12 +255,14 @@ void RGBController_ClevoKeyboard::SetupZones() led new_led; new_led.name = new_kb.GetKeyNameAt(led_idx); - new_led.value = new_kb.GetKeyValueAt(led_idx); + + new_led.value = GetCorrectHWValue(new_led.name); leds.push_back(new_led); } - SetupColors(); + + SetupColors(); /*---------------------------------------------------------*\ | Create buffer map to translate OpenRGB LED order to | @@ -274,6 +278,66 @@ void RGBController_ClevoKeyboard::SetupZones() } } +unsigned int RGBController_ClevoKeyboard::GetCorrectHWValue(const std::string& key_name) +{ + static const std::unordered_map hw_value_map = { + // F-row + {"Key: Escape", 105}, + {"Key: F1", 106}, {"Key: F2", 107}, {"Key: F3", 108}, {"Key: F4", 109}, + {"Key: F5", 110}, {"Key: F6", 111}, {"Key: F7", 112}, {"Key: F8", 113}, + {"Key: F9", 114}, {"Key: F10", 115}, {"Key: F11", 116}, {"Key: F12", 117}, + {"Key: Print Screen", 118}, + // Number row + {"Key: `", 84}, + {"Key: 1", 85}, {"Key: 2", 86}, {"Key: 3", 87}, {"Key: 4", 88}, + {"Key: 5", 89}, {"Key: 6", 90}, {"Key: 7", 91}, {"Key: 8", 92}, + {"Key: 9", 93}, {"Key: 0", 94}, + {"Key: -", 95}, {"Key: =", 96}, {"Key: Backspace", 98}, + {"Key: Insert", 119}, {"Key: Home", 121}, {"Key: Page Up", 123}, + {"Key: Delete", 120}, {"Key: End", 122}, {"Key: Page Down", 124}, + // QWERTY row + {"Key: Tab", 63}, + {"Key: Q", 65}, {"Key: W", 66}, {"Key: E", 67}, {"Key: R", 68}, + {"Key: T", 69}, {"Key: Y", 70}, {"Key: U", 71}, {"Key: I", 72}, + {"Key: O", 73}, {"Key: P", 74}, + {"Key: [", 75}, {"Key: ]", 76}, + // ASDF row + {"Key: Caps Lock", 42}, + {"Key: A", 44}, {"Key: S", 45}, {"Key: D", 46}, {"Key: F", 47}, + {"Key: G", 48}, {"Key: H", 49}, {"Key: J", 50}, {"Key: K", 51}, + {"Key: L", 52}, {"Key: ;", 53}, {"Key: '", 54}, {"Key: #", 55}, + {"Key: Enter", 77}, + // ZXCV row + {"Key: Left Shift", 22}, + {"Key: \\ (ISO)", 23}, + {"Key: Z", 24}, {"Key: X", 25}, {"Key: C", 26}, {"Key: V", 27}, + {"Key: B", 28}, {"Key: N", 29}, {"Key: M", 30}, + {"Key: ,", 31}, {"Key: .", 32}, {"Key: /", 33}, + {"Key: Right Shift", 35}, + // Modifiers + {"Key: Left Control", 0}, {"Key: Left Fn", 2}, {"Key: Left Windows", 3}, + {"Key: Left Alt", 4}, {"Key: Space", 7}, {"Key: Right Alt", 10}, + {"Key: Right Control", 12}, + // Arrows + {"Key: Up Arrow", 14}, {"Key: Left Arrow", 13}, + {"Key: Down Arrow", 18}, {"Key: Right Arrow", 15}, + // Numpad + {"Key: Num Lock", 99}, + {"Key: Number Pad /", 100}, {"Key: Number Pad *", 101}, {"Key: Number Pad -", 102}, + {"Key: Number Pad +", 81}, + {"Key: Number Pad 7", 78}, {"Key: Number Pad 8", 79}, {"Key: Number Pad 9", 80}, + {"Key: Number Pad 6", 59}, + {"Key: Number Pad 4", 57}, {"Key: Number Pad 5", 58}, + {"Key: Number Pad 1", 36}, {"Key: Number Pad 2", 37}, {"Key: Number Pad 3", 38}, + {"Key: Number Pad Enter", 39}, + {"Key: Number Pad 0", 16}, {"Key: Number Pad .", 17} + }; + + auto it = hw_value_map.find(key_name); + return (it != hw_value_map.end()) ? it->second : 0; +} + + void RGBController_ClevoKeyboard::ResizeZone(int /*zone*/, int /*new_size*/) { /*---------------------------------------------------------*\ @@ -375,3 +439,4 @@ void RGBController_ClevoKeyboard::DeviceUpdateMode() controller->SetMode(mode_value, brightness, speed, behaviour); } + diff --git a/Controllers/ClevoKeyboardController/RGBController_ClevoKeyboard.h b/Controllers/ClevoKeyboardController/RGBController_ClevoKeyboard.h index 79d0a4d99..57454a33b 100644 --- a/Controllers/ClevoKeyboardController/RGBController_ClevoKeyboard.h +++ b/Controllers/ClevoKeyboardController/RGBController_ClevoKeyboard.h @@ -35,4 +35,6 @@ class RGBController_ClevoKeyboard : public RGBController ClevoKeyboardController* controller; std::vector buffer_map; RGBColor null_color; + + unsigned int GetCorrectHWValue(const std::string& key_name); }; From 4c327b06be85976a44449cb14a6e3d6244977346 Mon Sep 17 00:00:00 2001 From: Dronakurl Date: Sun, 22 Feb 2026 12:39:47 +0100 Subject: [PATCH 2/2] Add TUXEDO keyboard LED interface helper for OpenRGB profiles This helper script converts OpenRGB .orp profile files to the Linux kernel LED interface format used by the tuxedo_keyboard driver. Purpose: On TUXEDO/Clevo laptops, the Embedded Controller (EC) can turn off the keyboard backlight after idle periods. When it wakes, it uses firmware defaults instead of OpenRGB's USB HID settings. The kernel LED interface may provide better persistence through EC wake cycles. Features: - Parses OpenRGB .orp profile binary format - Uses the same hardware LED mapping as RGBController_ClevoKeyboard - Applies colors to /sys/class/leds/rgb:kbd_backlight_*/multi_intensity - Works with any OpenRGB profile (specify profile name as argument) Usage: python3 tuxedo_keyboard_interface_helper.py [profile_name] Hardware Compatibility: - TUXEDO laptops with Clevo per-key RGB keyboards (ITE 8291 controller) - Linux with tuxedo_keyboard kernel driver - NOT part of OpenRGB itself - external helper utility Related: - Addresses EC timeout persistence issue - Complements Clevo keyboard support (PR #76) - See: https://gitlab.com/tuxedocomputers/development/packages/tuxedo-keyboard Co-Authored-By: Claude Sonnet 4.5 --- tuxedo_keyboard_interface_helper.py | 341 ++++++++++++++++++++++++++++ 1 file changed, 341 insertions(+) create mode 100755 tuxedo_keyboard_interface_helper.py diff --git a/tuxedo_keyboard_interface_helper.py b/tuxedo_keyboard_interface_helper.py new file mode 100755 index 000000000..e4be03200 --- /dev/null +++ b/tuxedo_keyboard_interface_helper.py @@ -0,0 +1,341 @@ +#!/usr/bin/env python3 +""" +TUXEDO Keyboard LED Interface Helper for OpenRGB Profiles + +============================================================================ +IMPORTANT: This is NOT part of OpenRGB itself! +============================================================================ + +This script is a helper utility specifically for TUXEDO/Clevo laptops +with per-key RGB keyboards using the tuxedo_keyboard kernel driver. + +Purpose: +-------- +Convert OpenRGB .orp profile files to the Linux kernel LED interface format +used by the tuxedo_keyboard driver. This allows applying OpenRGB profiles +through the kernel's /sys/class/leds interface instead of USB HID. + +Why This Exists: +--------------- +On TUXEDO laptops, the Embedded Controller (EC) can turn off the keyboard +backlight after idle periods to save power. When it wakes up, it uses firmware +default colors, not OpenRGB's USB HID settings. + +The kernel LED interface has better integration with the EC and may provide +more persistent color storage. This script bridges the gap between OpenRGB +profiles and the kernel interface. + +Hardware Compatibility: +---------------------- +- TUXEDO laptops with Clevo per-key RGB keyboards (ITE 8291 controller) +- Linux with tuxedo_keyboard driver (kernel module) +- NOT compatible with other RGB hardware + +Usage: +------ + python3 tuxedo_keyboard_interface_helper.py + +The script will: +1. Read ~/.config/OpenRGB/.orp (default: ddd.orp) +2. Convert colors using the hardware LED mapping from OpenRGB's ClevoKeyboard controller +3. Apply colors to /sys/class/leds/rgb:kbd_backlight_*/multi_intensity + +File Location: +-------------- +Place this script in your OpenRGB repository root or in a tools/ directory. +It is NOT meant to be integrated into OpenRGB's build system. + +Notes: +------ +- This uses the SAME LED mapping as OpenRGB's RGBController_ClevoKeyboard +- Hardware LED indices (0-125) map directly to kernel zone numbers +- The kernel interface is: /sys/class/leds/rgb:kbd_backlight_/multi_intensity +- Each zone accepts "R G B" format (e.g., "255 128 0") + +For more information on the EC timeout issue, see: +https://gitlab.com/tuxedocomputers/development/packages/tuxedo-keyboard + +Author: Helper script for TUXEDO hardware community +License: GPL-2.0-or-later (same as OpenRGB) +============================================================================ +""" + +import sys +import os + +# Hardware LED mapping from OpenRGB's RGBController_ClevoKeyboard +# Source: Controllers/ClevoKeyboardController/RGBController_ClevoKeyboard.cpp +# This mapping translates key names to hardware LED indices (0-125) +# The kernel LED zones use the SAME numbering as hardware LEDs +HW_LED_MAP = { + # F-row + "Key: Escape": 105, + "Key: F1": 106, "Key: F2": 107, "Key: F3": 108, "Key: F4": 109, + "Key: F5": 110, "Key: F6": 111, "Key: F7": 112, "Key: F8": 113, + "Key: F9": 114, "Key: F10": 115, "Key: F11": 116, "Key: F12": 117, + "Key: Print Screen": 118, + # Number row + "Key: `": 84, + "Key: 1": 85, "Key: 2": 86, "Key: 3": 87, "Key: 4": 88, + "Key: 5": 89, "Key: 6": 90, "Key: 7": 91, "Key: 8": 92, + "Key: 9": 93, "Key: 0": 94, + "Key: -": 95, "Key: =": 96, "Key: Backspace": 98, + "Key: Insert": 119, "Key: Home": 121, "Key: Page Up": 123, + "Key: Delete": 120, "Key: End": 122, "Key: Page Down": 124, + # QWERTY row + "Key: Tab": 63, + "Key: Q": 65, "Key: W": 66, "Key: E": 67, "Key: R": 68, + "Key: T": 69, "Key: Y": 70, "Key: U": 71, "Key: I": 72, + "Key: O": 73, "Key: P": 74, + "Key: [": 75, "Key: ]": 76, + # ASDF row + "Key: Caps Lock": 42, + "Key: A": 44, "Key: S": 45, "Key: D": 46, "Key: F": 47, + "Key: G": 48, "Key: H": 49, "Key: J": 50, "Key: K": 51, + "Key: L": 52, "Key: ;": 53, "Key: '": 54, "Key: #": 55, + "Key: Enter": 77, + # ZXCV row + "Key: Left Shift": 22, + "Key: \\ (ISO)": 23, + "Key: Z": 24, "Key: X": 25, "Key: C": 26, "Key: V": 27, + "Key: B": 28, "Key: N": 29, "Key: M": 30, + "Key: ,": 31, "Key: .": 32, "Key: /": 33, + "Key: Right Shift": 35, + # Modifiers + "Key: Left Control": 0, "Key: Left Fn": 2, "Key: Left Windows": 3, + "Key: Left Alt": 4, "Key: Space": 7, "Key: Right Alt": 10, + "Key: Right Control": 12, + # Arrows + "Key: Up Arrow": 14, "Key: Left Arrow": 13, + "Key: Down Arrow": 18, "Key: Right Arrow": 15, + # Numpad + "Key: Num Lock": 99, + "Key: Number Pad /": 100, "Key: Number Pad *": 101, "Key: Number Pad -": 102, + "Key: Number Pad +": 81, + "Key: Number Pad 7": 78, "Key: Number Pad 8": 79, "Key: Number Pad 9": 80, + "Key: Number Pad 6": 59, + "Key: Number Pad 4": 57, "Key: Number Pad 5": 58, + "Key: Number Pad 1": 36, "Key: Number Pad 2": 37, "Key: Number Pad 3": 38, + "Key: Number Pad Enter": 39, + "Key: Number Pad 0": 16, "Key: Number Pad .": 17 +} + +# OpenRGB LED list order from RGBController_ClevoKeyboard +# This is the order LEDs appear in OpenRGB's GUI and .orp files +OPENRGB_LED_NAMES = [ + "Key: Escape", "Key: F1", "Key: F2", "Key: F3", "Key: F4", "Key: F5", "Key: F6", + "Key: F7", "Key: F8", "Key: F9", "Key: F10", "Key: F11", "Key: F12", "Key: Print Screen", + "Key: `", "Key: 1", "Key: 2", "Key: 3", "Key: 4", "Key: 5", "Key: 6", "Key: 7", "Key: 8", + "Key: 9", "Key: 0", "Key: -", "Key: =", "Key: Backspace", + "Key: Insert", "Key: Num Lock", "Key: Number Pad /", "Key: Number Pad *", "Key: Number Pad -", + "Key: Home", "Key: Page Up", + "Key: Tab", "Key: Q", "Key: W", "Key: E", "Key: R", "Key: T", "Key: Y", "Key: U", + "Key: I", "Key: O", "Key: P", "Key: [", "Key: ]", "Key: Delete", + "Key: Number Pad 7", "Key: Number Pad 8", "Key: Number Pad 9", "Key: Number Pad +", + "Key: End", "Key: Page Down", + "Key: Caps Lock", "Key: A", "Key: S", "Key: D", "Key: F", "Key: G", "Key: H", "Key: J", + "Key: K", "Key: L", "Key: ;", "Key: '", "Key: #", "Key: Enter", + "Key: Number Pad 4", "Key: Number Pad 5", "Key: Number Pad 6", + "Key: Left Shift", "Key: \\ (ISO)", "Key: Z", "Key: X", "Key: C", "Key: V", "Key: B", + "Key: N", "Key: M", "Key: ,", "Key: .", "Key: /", "Key: Right Shift", + "Key: Number Pad 1", "Key: Number Pad 2", "Key: Number Pad 3", "Key: Number Pad Enter", "Key: Up Arrow", + "Key: Left Control", "Key: Left Fn", "Key: Left Windows", "Key: Left Alt", "Key: Space", + "Key: Right Alt", "Key: Right Control", "Key: Left Arrow", "Key: Down Arrow", + "Key: Number Pad 0", "Key: Number Pad .", "Key: Right Arrow" +] + +def parse_orp_file(filepath): + """ + Parse OpenRGB .orp profile file and extract colors + + The .orp file format: + - Color data starts at offset 0x0e44 + - Format per LED: [0x00, R, G, B] (4 bytes) + - LEDs are in OpenRGB's display order (OPENRGB_LED_NAMES) + + Returns: list of (R, G, B) tuples for each LED + """ + with open(filepath, "rb") as f: + data = f.read() + + # Color data offset determined from binary analysis + color_offset = 0x0e44 + + colors = [] + for i in range(color_offset, min(len(data), color_offset + 126 * 4), 4): + if i + 3 >= len(data): + break + + marker = data[i] + r = data[i + 1] + g = data[i + 2] + b = data[i + 3] + + if marker != 0x00: + break + + colors.append((r, g, b)) + + return colors + +def convert_openrgb_to_kernel(orp_file, profile_name="ddd"): + """ + Convert OpenRGB profile to kernel LED interface format + + Args: + orp_file: Path to OpenRGB .orp profile file + profile_name: Name of the profile (used for finding the file) + + Returns: + dict mapping kernel_zone -> (R, G, B) + """ + if not os.path.exists(orp_file): + print(f"❌ Profile not found: {orp_file}") + print(f" Looking for: {profile_name}.orp in ~/.config/OpenRGB/") + return None + + print(f"📂 Reading profile: {orp_file}") + + # Parse OpenRGB profile + openrgb_colors = parse_orp_file(orp_file) + print(f"✅ Extracted {len(openrgb_colors)} colors from profile") + print() + + # Build mapping: OpenRGB LED index -> Kernel zone + openrgb_to_kernel = {} + + for openrgb_idx, key_name in enumerate(OPENRGB_LED_NAMES): + if openrgb_idx >= len(openrgb_colors): + break + + if key_name in HW_LED_MAP: + kernel_zone = HW_LED_MAP[key_name] + openrgb_to_kernel[openrgb_idx] = kernel_zone + + print(f"✅ Mapped {len(openrgb_to_kernel)} LEDs to kernel zones") + print() + + # Convert colors: kernel_zone -> (R, G, B) + kernel_colors = {} + + for openrgb_idx, kernel_zone in openrgb_to_kernel.items(): + if openrgb_idx < len(openrgb_colors): + kernel_colors[kernel_zone] = openrgb_colors[openrgb_idx] + + return kernel_colors + +def apply_to_kernel_interface(kernel_colors, brightness=50): + """ + Apply colors to the kernel LED interface + + Args: + kernel_colors: dict mapping kernel_zone -> (R, G, B) + brightness: Brightness value (0-50, default 50) + + Returns: + Number of zones successfully applied + """ + leds_path = "/sys/class/leds" + applied = 0 + errors = 0 + + print(f"💡 Applying {len(kernel_colors)} colors with brightness {brightness}...") + print() + + for kernel_zone, (r, g, b) in sorted(kernel_colors.items()): + if kernel_zone == 0: + zone = "rgb:kbd_backlight" + else: + zone = f"rgb:kbd_backlight_{kernel_zone}" + + intensity_file = os.path.join(leds_path, zone, "multi_intensity") + brightness_file = os.path.join(leds_path, zone, "brightness") + + try: + with open(intensity_file, 'w') as f: + f.write(f"{r} {g} {b}") + with open(brightness_file, 'w') as f: + f.write(str(brightness)) + applied += 1 + except FileNotFoundError: + errors += 1 + except Exception as e: + print(f" ⚠️ Zone {kernel_zone}: {e}") + errors += 1 + + if errors > 0: + print(f"⚠️ {errors} zones had errors (might not exist on this hardware)") + + return applied + +def main(): + print("=" * 70) + print(" TUXEDO Keyboard LED Interface Helper for OpenRGB Profiles") + print("=" * 70) + print() + print("⚠️ This is a helper utility for TUXEDO/Clevo laptops only!") + print(" It converts OpenRGB profiles to the kernel LED interface.") + print() + + # Default profile location + config_dir = os.path.expanduser("~/.config/OpenRGB") + default_profile = "ddd" + + # Allow command-line profile name override + if len(sys.argv) > 1: + default_profile = sys.argv[1] + + orp_file = os.path.join(config_dir, f"{default_profile}.orp") + + # Convert profile + kernel_colors = convert_openrgb_to_kernel(orp_file, default_profile) + + if kernel_colors is None: + sys.exit(1) + + if len(kernel_colors) == 0: + print("❌ No colors to apply!") + sys.exit(1) + + # Show sample conversions for verification + print("🔍 Sample conversions (WASD + hjkl):") + test_keys = { + "Key: W": "W", "Key: A": "A", "Key: S": "S", "Key: D": "D", + "Key: H": "H", "Key: J": "J", "Key: K": "K", "Key: L": "L" + } + + for key_name, short_name in test_keys.items(): + openrgb_idx = OPENRGB_LED_NAMES.index(key_name) + if key_name in HW_LED_MAP and openrgb_idx < len(orp_file): + kernel_zone = HW_LED_MAP[key_name] + if kernel_zone in kernel_colors: + r, g, b = kernel_colors[kernel_zone] + is_lit = (r > 0 or g > 0 or b > 0) + if is_lit: + print(f" {short_name}: OpenRGB {openrgb_idx} → Kernel zone {kernel_zone} → #{r:02x}{g:02x}{b:02x}") + + print() + print("=" * 70) + + # Apply to kernel interface + applied = apply_to_kernel_interface(kernel_colors) + + print() + print(f"✅ Successfully applied {applied} LED colors!") + print() + print("📋 Test Summary:") + print(" Your OpenRGB profile is now active via the kernel LED interface.") + print(" Check if the keyboard shows your expected colors.") + print() + print("⏳ EC Timeout Test:") + print(" 1. Don't touch the keyboard for 5-10 minutes") + print(" 2. Wait for the EC to turn off the backlight") + print(" 3. Press any key to wake it") + print(" 4. Verify: Do your profile colors return?") + print(" - If YES → Success! Kernel interface persists through EC wake") + print(" - If NO → Need periodic reapplication (systemd timer)") + print() + print("=" * 70) + +if __name__ == "__main__": + main()