From e2cd0cc9c08ae18183387e403b9c2e1f27bf65f1 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Sun, 24 Nov 2024 13:27:40 +0800 Subject: [PATCH 1/4] python: Avoid lamba variable capture in loop All buttons would open the same URL because we capture the variable and not the value. So the variable would get updated to the last loop value in every lambda. With default parameter, we don't capture the value. See: https://gist.github.com/gisbi-kim/2e5648225cc118fc72ac933ef63c2d64 Signed-off-by: Daniel Schaefer --- python/qmk_hid/gui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/qmk_hid/gui.py b/python/qmk_hid/gui.py index 5fe7ae0..fd83bf7 100644 --- a/python/qmk_hid/gui.py +++ b/python/qmk_hid/gui.py @@ -194,7 +194,7 @@ def main(): # Organize in columns of three row = int(i / 3) column = i % 3 - btn = ttk.Button(info_frame, text=text, command=lambda: webbrowser.open(url), style="TButton") + btn = ttk.Button(info_frame, text=text, command=lambda url=url: open_browser_func(url), style="TButton") btn.grid(row=row, column=column) # Device Control Buttons From cb076bcab1a2bc72c754b864629b67940068cf4e Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Sun, 24 Nov 2024 14:01:24 +0800 Subject: [PATCH 2/4] python: iconbitmap doesn't work on linux Signed-off-by: Daniel Schaefer --- python/qmk_hid/gui.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python/qmk_hid/gui.py b/python/qmk_hid/gui.py index fd83bf7..082a737 100644 --- a/python/qmk_hid/gui.py +++ b/python/qmk_hid/gui.py @@ -151,7 +151,8 @@ def main(): root.title("QMK GUI") ico = "logo_cropped_transparent_keyboard_48x48.ico" res_path = resource_path() - root.iconbitmap(f"{res_path}/res/{ico}") + if os.name == 'nt': + root.iconbitmap(f"{res_path}/res/{ico}") tabControl = ttk.Notebook(root) tab1 = ttk.Frame(tabControl) From db5ea4bd769444e97431eef5024a7a7125545c79 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Tue, 26 Nov 2024 14:21:30 +0800 Subject: [PATCH 3/4] python: Split out Make firmware_update.py same as in FrameworkComputer/ledmatrix-rs Signed-off-by: Daniel Schaefer --- python/qmk_hid/firmware_update.py | 81 +++++++ python/qmk_hid/gui.py | 347 +----------------------------- python/qmk_hid/protocol.py | 265 +++++++++++++++++++++++ 3 files changed, 353 insertions(+), 340 deletions(-) create mode 100644 python/qmk_hid/firmware_update.py create mode 100644 python/qmk_hid/protocol.py diff --git a/python/qmk_hid/firmware_update.py b/python/qmk_hid/firmware_update.py new file mode 100644 index 0000000..92d0bd5 --- /dev/null +++ b/python/qmk_hid/firmware_update.py @@ -0,0 +1,81 @@ +import os +import time + +from qmk_hid.protocol import bootloader_jump +from qmk_hid import uf2conv + +def dev_to_str(dev): + return dev['path'] + +def flash_firmware(dev, fw_path): + print(f"Flashing {fw_path} onto {dev_to_str(dev)}") + + # First jump to bootloader + drives = uf2conv.list_drives() + if not drives: + print("Jump to bootloader") + bootloader_jump(dev) + + timeout = 10 # 5s + while not drives: + if timeout == 0: + print("Failed to find device in bootloader") + # TODO: Handle return value + return False + # Wait for it to appear + time.sleep(0.5) + timeout -= 1 + drives = uf2conv.get_drives() + + + if len(drives) == 0: + print("No drive to deploy.") + return False + + # Firmware is pretty small, can just fit it all into memory + with open(fw_path, 'rb') as f: + fw_buf = f.read() + + for d in drives: + print("Flashing {} ({})".format(d, uf2conv.board_id(d))) + uf2conv.write_file(d + "/NEW.UF2", fw_buf) + + print("Flashing finished") + + +# Example return value +# { +# '0.1.7': { +# 'ansi': 'framework_ansi_default_v0.1.7.uf2', +# 'gridpad': 'framework_gridpad_default_v0.1.7.uf2' +# }, +# '0.1.8': { +# 'ansi': 'framework_ansi_default.uf2', +# 'gridpad': 'framework_gridpad_default.uf2', +# } +# } +def find_releases(res_path, filename_format): + from os import listdir + from os.path import isfile, join + import re + + releases = {} + try: + versions = listdir(os.path.join(res_path, "releases")) + except FileNotFoundError: + return releases + + for version in versions: + path = join(res_path, "releases", version) + releases[version] = {} + for filename in listdir(path): + if not isfile(join(path, filename)): + continue + type_search = re.search(filename_format, filename) + if not type_search: + print(f"Filename '{filename}' not matching patten!") + sys.exit(1) + continue + fw_type = type_search.group(1) + releases[version][fw_type] = os.path.join(res_path, "releases", version, filename) + return releases diff --git a/python/qmk_hid/gui.py b/python/qmk_hid/gui.py index 082a737..46aa1aa 100644 --- a/python/qmk_hid/gui.py +++ b/python/qmk_hid/gui.py @@ -7,7 +7,6 @@ import tkinter as tk from tkinter import ttk, messagebox -import hid if os.name == 'nt': from win32api import GetKeyState, keybd_event from win32con import VK_NUMLOCK, VK_CAPITAL @@ -15,107 +14,17 @@ import webbrowser -from qmk_hid import uf2conv +from qmk_hid.protocol import * +from qmk_hid import firmware_update # TODO: # - Get current values # - Set sliders to current values PROGRAM_VERSION = "0.2.0" -FWK_VID = 0x32AC DEBUG_PRINT = False -QMK_INTERFACE = 0x01 -RAW_HID_BUFFER_SIZE = 32 - -RAW_USAGE_PAGE = 0xFF60 -CONSOLE_USAGE_PAGE = 0xFF31 -# Generic Desktop -G_DESK_USAGE_PAGE = 0x01 -CONSUMER_USAGE_PAGE = 0x0C - -GET_PROTOCOL_VERSION = 0x01 # always 0x01 -GET_KEYBOARD_VALUE = 0x02 -SET_KEYBOARD_VALUE = 0x03 -# DynamicKeymapGetKeycode = 0x04 -# DynamicKeymapSetKeycode = 0x05 -# DynamicKeymapReset = 0x06 -CUSTOM_SET_VALUE = 0x07 -CUSTOM_GET_VALUE = 0x08 -CUSTOM_SAVE = 0x09 -EEPROM_RESET = 0x0A -BOOTLOADER_JUMP = 0x0B - -CHANNEL_CUSTOM = 0 -CHANNEL_BACKLIGHT = 1 -CHANNEL_RGB_LIGHT = 2 -CHANNEL_RGB_MATRIX = 3 -CHANNEL_AUDIO = 4 - -BACKLIGHT_VALUE_BRIGHTNESS = 1 -BACKLIGHT_VALUE_EFFECT = 2 - -RGB_MATRIX_VALUE_BRIGHTNESS = 1 -RGB_MATRIX_VALUE_EFFECT = 2 -RGB_MATRIX_VALUE_EFFECT_SPEED = 3 -RGB_MATRIX_VALUE_COLOR = 4 - -RED_HUE = 0 -YELLOW_HUE = 43 -GREEN_HUE = 85 -CYAN_HUE = 125 -BLUE_HUE = 170 -PURPLE_HUE = 213 - -RGB_EFFECTS = [ - "Off", - "SOLID_COLOR", - "ALPHAS_MODS", - "GRADIENT_UP_DOWN", - "GRADIENT_LEFT_RIGHT", - "BREATHING", - "BAND_SAT", - "BAND_VAL", - "BAND_PINWHEEL_SAT", - "BAND_PINWHEEL_VAL", - "BAND_SPIRAL_SAT", - "BAND_SPIRAL_VAL", - "CYCLE_ALL", - "CYCLE_LEFT_RIGHT", - "CYCLE_UP_DOWN", - "CYCLE_OUT_IN", - "CYCLE_OUT_IN_DUAL", - "RAINBOW_MOVING_CHEVRON", - "CYCLE_PINWHEEL", - "CYCLE_SPIRAL", - "DUAL_BEACON", - "RAINBOW_BEACON", - "RAINBOW_PINWHEELS", - "RAINDROPS", - "JELLYBEAN_RAINDROPS", - "HUE_BREATHING", - "HUE_PENDULUM", - "HUE_WAVE", - "PIXEL_FRACTAL", - "PIXEL_FLOW", - "PIXEL_RAIN", - "TYPING_HEATMAP", - "DIGITAL_RAIN", - "SOLID_REACTIVE_SIMPLE", - "SOLID_REACTIVE", - "SOLID_REACTIVE_WIDE", - "SOLID_REACTIVE_MULTIWIDE", - "SOLID_REACTIVE_CROSS", - "SOLID_REACTIVE_MULTICROSS", - "SOLID_REACTIVE_NEXUS", - "SOLID_REACTIVE_MULTINEXUS", - "SPLASH", - "MULTISPLASH", - "SOLID_SPLASH", - "SOLID_MULTISPLASH", -] - def debug_print(*args): if DEBUG_PRINT: print(args) @@ -287,7 +196,7 @@ def main(): toggle_btn = ttk.Button(registry_frame, text="Disable Selective Suspend", command=lambda dev: selective_suspend_wrapper(dev, False), style="TButton", state=tk.DISABLED).pack(side="left", padx=5, pady=5) # Only in the pyinstaller bundle are the FW update binaries included - releases = find_releases() + releases = firmware_update.find_releases(resource_path(), r'framework_(.*)_default.*\.uf2') if not releases: tk.Label(tab_fw_update, text="Cannot find firmware updates").pack(side="top", padx=5, pady=5) else: @@ -309,7 +218,7 @@ def main(): flash_btn = ttk.Button(fw_update_frame, text="Update", command=lambda: tk_flash_firmware(devices, releases, fw_ver_combo.get(), fw_type_combo.get()), state=tk.DISABLED, style="TButton") flash_btn.pack(side="left", padx=5, pady=5) - program_ver_label = tk.Label(tab1, text="Program Version: 0.2.0") + program_ver_label = tk.Label(tab1, text=f"Program Version: {PROGRAM_VERSION}") program_ver_label.pack(side=tk.LEFT, padx=5, pady=5) root.mainloop() @@ -391,214 +300,6 @@ def backlight_watcher(window, devs): time.sleep(1) -# Example return value -# { -# '0.1.7': { -# 'ansi': 'framework_ansi_default_v0.1.7.uf2', -# 'gridpad': 'framework_gridpad_default_v0.1.7.uf2' -# }, -# '0.1.8': { -# 'ansi': 'framework_ansi_default.uf2', -# 'gridpad': 'framework_gridpad_default.uf2', -# } -# } -def find_releases(): - from os import listdir - from os.path import isfile, join - import re - - releases = {} - res_path = resource_path() - try: - versions = listdir(os.path.join(res_path, "releases")) - except FileNotFoundError: - return releases - - for version in versions: - path = join(res_path, "releases", version) - releases[version] = {} - for filename in listdir(path): - if not isfile(join(path, filename)): - continue - type_search = re.search(r'framework_(.*)_default.*\.uf2', filename) - if not type_search: - print(f"Filename '{filename}' not matching patten!") - sys.exit(1) - continue - fw_type = type_search.group(1) - releases[version][fw_type] = os.path.join(res_path, "releases", version, filename) - return releases - - -def find_devs(show, verbose): - if verbose: - show = True - - devices = [] - for device_dict in hid.enumerate(): - vid = device_dict["vendor_id"] - pid = device_dict["product_id"] - product = device_dict["product_string"] - manufacturer = device_dict["manufacturer_string"] - sn = device_dict['serial_number'] - interface = device_dict['interface_number'] - path = device_dict['path'] - - if vid != FWK_VID: - if verbose: - print("Vendor ID not matching") - continue - - if interface != QMK_INTERFACE: - if verbose: - print("Interface not matching") - continue - # For some reason on Linux it'll always show usage_page==0 - if os.name == 'nt' and device_dict['usage_page'] not in [RAW_USAGE_PAGE, CONSOLE_USAGE_PAGE]: - if verbose: - print("Usage Page not matching") - continue - # Lots of false positives, so at least skip Framework false positives - if vid == FWK_VID and pid not in [0x12, 0x13, 0x14, 0x18, 0x19]: - if verbose: - print("False positive, device is not allowed") - continue - - fw_ver = device_dict["release_number"] - - if (os.name == 'nt' and device_dict['usage_page'] == RAW_USAGE_PAGE) or verbose: - if show: - print(f"Manufacturer: {manufacturer}") - print(f"Product: {product}") - print("FW Version: {}".format(format_fw_ver(fw_ver))) - print(f"Serial No: {sn}") - - if verbose: - print(f"Path: {path}") - print(f"VID/PID: {vid:02X}:{pid:02X}") - print(f"Interface: {interface}") - # TODO: print Usage Page - print("") - - devices.append(device_dict) - - return devices - - -def send_message(dev, message_id, msg, out_len): - data = [0xFE] * RAW_HID_BUFFER_SIZE - data[0] = 0x00 # NULL report ID - data[1] = message_id - - if msg: - if len(msg) > RAW_HID_BUFFER_SIZE-2: - print("Message too big. BUG. Please report") - sys.exit(1) - for i, x in enumerate(msg): - data[2+i] = x - - try: - # TODO: Do this somewhere outside - h = hid.device() - h.open_path(dev['path']) - #h.set_nonblocking(0) - h.write(data) - - if out_len == 0: - return None - - out_data = h.read(out_len+3) - return out_data - except (IOError, OSError) as ex: - disable_devices([dev]) - debug_print("Error ({}): ".format(dev['path']), ex) - -def set_keyboard_value(dev, value, number): - msg = [value, number] - send_message(dev, SET_KEYBOARD_VALUE, msg, 0) - -def set_rgb_u8(dev, value, value_data): - msg = [CHANNEL_RGB_MATRIX, value, value_data] - send_message(dev, CUSTOM_SET_VALUE, msg, 0) - -# Returns brightness level: x/255 -def get_rgb_u8(dev, value): - msg = [CHANNEL_RGB_MATRIX, value] - output = send_message(dev, CUSTOM_GET_VALUE, msg, 1) - if output[0] == 255: # Not RGB - return None - return output[3] - -# Returns (hue, saturation) -def get_rgb_color(dev): - msg = [CHANNEL_RGB_MATRIX, RGB_MATRIX_VALUE_COLOR] - output = send_message(dev, CUSTOM_GET_VALUE, msg, 2) - return (output[3], output[4]) - -# Returns brightness level: x/255 -def get_backlight(dev, value): - msg = [CHANNEL_BACKLIGHT, value] - output = send_message(dev, CUSTOM_GET_VALUE, msg, 1) - return output[3] - -def set_backlight(dev, value, value_data): - msg = [CHANNEL_BACKLIGHT, value, value_data] - send_message(dev, CUSTOM_SET_VALUE, msg, 0) - -def save(dev): - save_rgb(dev) - save_backlight(dev) - -def save_rgb(dev): - msg = [CHANNEL_RGB_MATRIX] - send_message(dev, CUSTOM_SAVE, msg, 0) - -def save_backlight(dev): - msg = [CHANNEL_BACKLIGHT] - send_message(dev, CUSTOM_SAVE, msg, 0) - -def eeprom_reset(dev): - send_message(dev, EEPROM_RESET, None, 0) - - -def bootloader_jump(dev): - send_message(dev, BOOTLOADER_JUMP, None, 0) - - -def bios_mode(dev, enable): - param = 0x01 if enable else 0x00 - send_message(dev, BOOTLOADER_JUMP, [0x05, param], 0) - - -def factory_mode(dev, enable): - param = 0x01 if enable else 0x00 - send_message(dev, BOOTLOADER_JUMP, [0x06, param], 0) - - -def set_rgb_brightness(dev, brightness): - set_rgb_u8(dev, RGB_MATRIX_VALUE_BRIGHTNESS, brightness) - - -def set_brightness(dev, brightness): - set_backlight(dev, BACKLIGHT_VALUE_BRIGHTNESS, brightness) - -def set_white_effect(dev, breathing_on): - set_backlight(dev, BACKLIGHT_VALUE_EFFECT, breathing_on) - -# Set both -def set_white_rgb_brightness(dev, brightness): - set_brightness(dev, brightness) - set_rgb_brightness(dev, brightness) - - -def set_rgb_color(dev, hue, saturation): - (cur_hue, cur_sat) = get_rgb_color(dev) - if hue is None: - hue = cur_hue - msg = [CHANNEL_RGB_MATRIX, RGB_MATRIX_VALUE_COLOR, hue, saturation] - send_message(dev, CUSTOM_SET_VALUE, msg, 0) - - def restart_hint(): parent = tk.Tk() parent.title("Restart Application") @@ -609,7 +310,7 @@ def restart_hint(): def info_popup(msg): parent = tk.Tk() parent.title("Info") - message = tk.Message(parent, text="msg", width=800) + message = tk.Message(parent, text=msg, width=800) message.pack(padx=20, pady=20) parent.mainloop() @@ -622,40 +323,6 @@ def replug_hint(): parent.mainloop() -def flash_firmware(dev, fw_path): - print(f"Flashing {fw_path} onto {dev['path']}") - - # First jump to bootloader - drives = uf2conv.list_drives() - if not drives: - print("Jump to bootloader") - bootloader_jump(dev) - - timeout = 10 # 5s - while not drives: - if timeout == 0: - print("Failed to find device in bootloader") - # TODO: Handle return value - return False - # Wait for it to appear - time.sleep(0.5) - timeout -= 1 - drives = uf2conv.get_drives() - - - if len(drives) == 0: - print("No drive to deploy.") - return False - - # Firmware is pretty small, can just fit it all into memory - with open(fw_path, 'rb') as f: - fw_buf = f.read() - - for d in drives: - print("Flashing {} ({})".format(d, uf2conv.board_id(d))) - uf2conv.write_file(d + "/NEW.UF2", fw_buf) - - print("Flashing finished") def selective_suspend_wrapper(dev, enable): if enable: @@ -740,8 +407,8 @@ def disable_devices(devices): def perform_action(devices, action, value=None): if action == "bootloader": disable_devices(devices) - restart_hint() + if action == "off": brightness_scale.set(0) @@ -790,7 +457,7 @@ def tk_flash_firmware(devices, releases, version, fw_type): info_popup('To flash select exactly 1 device.') return dev = selected_devices[0] - flash_firmware(dev, releases[version][fw_type]) + firmware_update.flash_firmware(dev, releases[version][fw_type]) # Disable device that we just flashed disable_devices(devices) restart_hint() diff --git a/python/qmk_hid/protocol.py b/python/qmk_hid/protocol.py new file mode 100644 index 0000000..3aedb8b --- /dev/null +++ b/python/qmk_hid/protocol.py @@ -0,0 +1,265 @@ +import os +import sys + +import hid + +FWK_VID = 0x32AC + +QMK_INTERFACE = 0x01 +RAW_HID_BUFFER_SIZE = 32 + +RAW_USAGE_PAGE = 0xFF60 +CONSOLE_USAGE_PAGE = 0xFF31 +# Generic Desktop +G_DESK_USAGE_PAGE = 0x01 +CONSUMER_USAGE_PAGE = 0x0C + +GET_PROTOCOL_VERSION = 0x01 # always 0x01 +GET_KEYBOARD_VALUE = 0x02 +SET_KEYBOARD_VALUE = 0x03 +# DynamicKeymapGetKeycode = 0x04 +# DynamicKeymapSetKeycode = 0x05 +# DynamicKeymapReset = 0x06 +CUSTOM_SET_VALUE = 0x07 +CUSTOM_GET_VALUE = 0x08 +CUSTOM_SAVE = 0x09 +EEPROM_RESET = 0x0A +BOOTLOADER_JUMP = 0x0B + +CHANNEL_CUSTOM = 0 +CHANNEL_BACKLIGHT = 1 +CHANNEL_RGB_LIGHT = 2 +CHANNEL_RGB_MATRIX = 3 +CHANNEL_AUDIO = 4 + +BACKLIGHT_VALUE_BRIGHTNESS = 1 +BACKLIGHT_VALUE_EFFECT = 2 + +RGB_MATRIX_VALUE_BRIGHTNESS = 1 +RGB_MATRIX_VALUE_EFFECT = 2 +RGB_MATRIX_VALUE_EFFECT_SPEED = 3 +RGB_MATRIX_VALUE_COLOR = 4 + +RED_HUE = 0 +YELLOW_HUE = 43 +GREEN_HUE = 85 +CYAN_HUE = 125 +BLUE_HUE = 170 +PURPLE_HUE = 213 + +RGB_EFFECTS = [ + "Off", + "SOLID_COLOR", + "ALPHAS_MODS", + "GRADIENT_UP_DOWN", + "GRADIENT_LEFT_RIGHT", + "BREATHING", + "BAND_SAT", + "BAND_VAL", + "BAND_PINWHEEL_SAT", + "BAND_PINWHEEL_VAL", + "BAND_SPIRAL_SAT", + "BAND_SPIRAL_VAL", + "CYCLE_ALL", + "CYCLE_LEFT_RIGHT", + "CYCLE_UP_DOWN", + "CYCLE_OUT_IN", + "CYCLE_OUT_IN_DUAL", + "RAINBOW_MOVING_CHEVRON", + "CYCLE_PINWHEEL", + "CYCLE_SPIRAL", + "DUAL_BEACON", + "RAINBOW_BEACON", + "RAINBOW_PINWHEELS", + "RAINDROPS", + "JELLYBEAN_RAINDROPS", + "HUE_BREATHING", + "HUE_PENDULUM", + "HUE_WAVE", + "PIXEL_FRACTAL", + "PIXEL_FLOW", + "PIXEL_RAIN", + "TYPING_HEATMAP", + "DIGITAL_RAIN", + "SOLID_REACTIVE_SIMPLE", + "SOLID_REACTIVE", + "SOLID_REACTIVE_WIDE", + "SOLID_REACTIVE_MULTIWIDE", + "SOLID_REACTIVE_CROSS", + "SOLID_REACTIVE_MULTICROSS", + "SOLID_REACTIVE_NEXUS", + "SOLID_REACTIVE_MULTINEXUS", + "SPLASH", + "MULTISPLASH", + "SOLID_SPLASH", + "SOLID_MULTISPLASH", +] + +def find_devs(show, verbose): + if verbose: + show = True + + devices = [] + for device_dict in hid.enumerate(): + vid = device_dict["vendor_id"] + pid = device_dict["product_id"] + product = device_dict["product_string"] + manufacturer = device_dict["manufacturer_string"] + sn = device_dict['serial_number'] + interface = device_dict['interface_number'] + path = device_dict['path'] + + if vid != FWK_VID: + if verbose: + print("Vendor ID not matching") + continue + + if interface != QMK_INTERFACE: + if verbose: + print("Interface not matching") + continue + # For some reason on Linux it'll always show usage_page==0 + if os.name == 'nt' and device_dict['usage_page'] not in [RAW_USAGE_PAGE, CONSOLE_USAGE_PAGE]: + if verbose: + print("Usage Page not matching") + continue + # Lots of false positives, so at least skip Framework false positives + if vid == FWK_VID and pid not in [0x12, 0x13, 0x14, 0x18, 0x19]: + if verbose: + print("False positive, device is not allowed") + continue + + fw_ver = device_dict["release_number"] + + if (os.name == 'nt' and device_dict['usage_page'] == RAW_USAGE_PAGE) or verbose: + if show: + print(f"Manufacturer: {manufacturer}") + print(f"Product: {product}") + print("FW Version: {}".format(format_fw_ver(fw_ver))) + print(f"Serial No: {sn}") + + if verbose: + print(f"Path: {path}") + print(f"VID/PID: {vid:02X}:{pid:02X}") + print(f"Interface: {interface}") + # TODO: print Usage Page + print("") + + devices.append(device_dict) + + return devices + + +def send_message(dev, message_id, msg, out_len): + data = [0xFE] * RAW_HID_BUFFER_SIZE + data[0] = 0x00 # NULL report ID + data[1] = message_id + + if msg: + if len(msg) > RAW_HID_BUFFER_SIZE-2: + print("Message too big. BUG. Please report") + sys.exit(1) + for i, x in enumerate(msg): + data[2+i] = x + + try: + # TODO: Do this somewhere outside + h = hid.device() + h.open_path(dev['path']) + #h.set_nonblocking(0) + h.write(data) + + if out_len == 0: + return None + + out_data = h.read(out_len+3) + return out_data + except (IOError, OSError) as ex: + disable_devices([dev]) + debug_print("Error ({}): ".format(dev['path']), ex) + +def set_keyboard_value(dev, value, number): + msg = [value, number] + send_message(dev, SET_KEYBOARD_VALUE, msg, 0) + +def set_rgb_u8(dev, value, value_data): + msg = [CHANNEL_RGB_MATRIX, value, value_data] + send_message(dev, CUSTOM_SET_VALUE, msg, 0) + +# Returns brightness level: x/255 +def get_rgb_u8(dev, value): + msg = [CHANNEL_RGB_MATRIX, value] + output = send_message(dev, CUSTOM_GET_VALUE, msg, 1) + if output[0] == 255: # Not RGB + return None + return output[3] + +# Returns (hue, saturation) +def get_rgb_color(dev): + msg = [CHANNEL_RGB_MATRIX, RGB_MATRIX_VALUE_COLOR] + output = send_message(dev, CUSTOM_GET_VALUE, msg, 2) + return (output[3], output[4]) + +# Returns brightness level: x/255 +def get_backlight(dev, value): + msg = [CHANNEL_BACKLIGHT, value] + output = send_message(dev, CUSTOM_GET_VALUE, msg, 1) + return output[3] + +def set_backlight(dev, value, value_data): + msg = [CHANNEL_BACKLIGHT, value, value_data] + send_message(dev, CUSTOM_SET_VALUE, msg, 0) + +def save(dev): + save_rgb(dev) + save_backlight(dev) + +def save_rgb(dev): + msg = [CHANNEL_RGB_MATRIX] + send_message(dev, CUSTOM_SAVE, msg, 0) + +def save_backlight(dev): + msg = [CHANNEL_BACKLIGHT] + send_message(dev, CUSTOM_SAVE, msg, 0) + +def eeprom_reset(dev): + send_message(dev, EEPROM_RESET, None, 0) + + +def bootloader_jump(dev): + send_message(dev, BOOTLOADER_JUMP, None, 0) + + +def bios_mode(dev, enable): + param = 0x01 if enable else 0x00 + send_message(dev, BOOTLOADER_JUMP, [0x05, param], 0) + + +def factory_mode(dev, enable): + param = 0x01 if enable else 0x00 + send_message(dev, BOOTLOADER_JUMP, [0x06, param], 0) + + +def set_rgb_brightness(dev, brightness): + set_rgb_u8(dev, RGB_MATRIX_VALUE_BRIGHTNESS, brightness) + + +def set_brightness(dev, brightness): + set_backlight(dev, BACKLIGHT_VALUE_BRIGHTNESS, brightness) + +def set_white_effect(dev, breathing_on): + set_backlight(dev, BACKLIGHT_VALUE_EFFECT, breathing_on) + +# Set both +def set_white_rgb_brightness(dev, brightness): + set_brightness(dev, brightness) + set_rgb_brightness(dev, brightness) + + +def set_rgb_color(dev, hue, saturation): + (cur_hue, cur_sat) = get_rgb_color(dev) + if hue is None: + hue = cur_hue + msg = [CHANNEL_RGB_MATRIX, RGB_MATRIX_VALUE_COLOR, hue, saturation] + send_message(dev, CUSTOM_SET_VALUE, msg, 0) + From bbe5ccd74527d0e1cc177054a9d487112804163a Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Tue, 26 Nov 2024 14:31:58 +0800 Subject: [PATCH 4/4] python: Rename window name More friendly Signed-off-by: Daniel Schaefer --- python/qmk_hid/gui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/qmk_hid/gui.py b/python/qmk_hid/gui.py index 46aa1aa..b0a5cca 100644 --- a/python/qmk_hid/gui.py +++ b/python/qmk_hid/gui.py @@ -57,7 +57,7 @@ def main(): # print("Found {} devices".format(len(devices))) root = tk.Tk() - root.title("QMK GUI") + root.title("QMK Keyboard Control") ico = "logo_cropped_transparent_keyboard_48x48.ico" res_path = resource_path() if os.name == 'nt':