From 49948a7e767454663ea64f61d4103b43f5a20d6f Mon Sep 17 00:00:00 2001 From: Ilya Zhuravlev Date: Tue, 29 Jun 2021 19:43:21 -0400 Subject: [PATCH 01/10] qmk_settings: initial prototype --- src/main/python/keyboard_comm.py | 21 +++++++++++++ src/main/python/main_window.py | 8 +++-- src/main/python/qmk_settings.py | 52 ++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 src/main/python/qmk_settings.py diff --git a/src/main/python/keyboard_comm.py b/src/main/python/keyboard_comm.py index 59d7c4a..f14afa4 100644 --- a/src/main/python/keyboard_comm.py +++ b/src/main/python/keyboard_comm.py @@ -52,6 +52,10 @@ CMD_VIAL_UNLOCK_START = 0x06 CMD_VIAL_UNLOCK_POLL = 0x07 CMD_VIAL_LOCK = 0x08 +CMD_VIAL_QMK_SETTINGS_QUERY = 0x09 +CMD_VIAL_QMK_SETTINGS_GET = 0x0A +CMD_VIAL_QMK_SETTINGS_SET = 0x0B + # how much of a macro/keymap buffer we can read/write per packet BUFFER_FETCH_CHUNK = 28 @@ -637,6 +641,23 @@ class Keyboard: macros = macros[:self.macro_count] return [self.macro_deserialize(x) for x in macros] + def qmk_settings_query(self): + raise NotImplementedError + + def qmk_settings_get(self, qsid): + data = self.usb_send(self.dev, struct.pack("= 3) # TODO(xyz): protocol bump From 2fd8a0a7d03e5b13d1234288f43e0a4310dfcd01 Mon Sep 17 00:00:00 2001 From: Ilya Zhuravlev Date: Wed, 30 Jun 2021 23:20:18 -0400 Subject: [PATCH 02/10] qmk_settings: store settings as json --- src/main/python/main.py | 2 +- src/main/python/main_window.py | 5 +- src/main/python/qmk_settings.py | 108 +++++++++++++++++----- src/main/resources/base/qmk_settings.json | 48 ++++++++++ 4 files changed, 138 insertions(+), 25 deletions(-) create mode 100644 src/main/resources/base/qmk_settings.json diff --git a/src/main/python/main.py b/src/main/python/main.py index 9d2ae19..a189160 100644 --- a/src/main/python/main.py +++ b/src/main/python/main.py @@ -64,7 +64,7 @@ if __name__ == '__main__': appctxt = ApplicationContext() # 1. Instantiate ApplicationContext init_logger() qt_exception_hook = UncaughtHook() - window = MainWindow() + window = MainWindow(appctxt) window.resize(WINDOW_WIDTH, WINDOW_HEIGHT) window.show() exit_code = appctxt.app.exec_() # 2. Invoke appctxt.app.exec_() diff --git a/src/main/python/main_window.py b/src/main/python/main_window.py index 7443428..8c32fe8 100644 --- a/src/main/python/main_window.py +++ b/src/main/python/main_window.py @@ -28,8 +28,9 @@ import themes class MainWindow(QMainWindow): - def __init__(self): + def __init__(self, appctx): super().__init__() + self.appctx = appctx self.settings = QSettings("Vial", "Vial") themes.set_theme(self.get_theme()) @@ -57,7 +58,7 @@ class MainWindow(QMainWindow): self.keymap_editor = KeymapEditor(self.layout_editor) self.firmware_flasher = FirmwareFlasher(self) self.macro_recorder = MacroRecorder() - self.qmk_settings = QmkSettings() + self.qmk_settings = QmkSettings(self.appctx) self.matrix_tester = MatrixTest(self.layout_editor) self.rgb_configurator = RGBConfigurator() diff --git a/src/main/python/qmk_settings.py b/src/main/python/qmk_settings.py index c9e6720..3b0f823 100644 --- a/src/main/python/qmk_settings.py +++ b/src/main/python/qmk_settings.py @@ -1,41 +1,105 @@ # SPDX-License-Identifier: GPL-2.0-or-later +import json import struct from PyQt5 import QtCore -from PyQt5.QtWidgets import QVBoxLayout, QCheckBox, QGridLayout, QLabel, QWidget, QSizePolicy +from PyQt5.QtWidgets import QVBoxLayout, QCheckBox, QGridLayout, QLabel, QWidget, QSizePolicy, QTabWidget, QSpinBox, \ + QHBoxLayout, QPushButton from basic_editor import BasicEditor from vial_device import VialKeyboard +class GenericOption: + + def __init__(self, option, container): + self.row = container.rowCount() + self.option = option + self.container = container + + self.container.addWidget(QLabel(option["title"]), self.row, 0) + + +class BooleanOption(GenericOption): + + def __init__(self, option, container): + super().__init__(option, container) + + self.checkbox = QCheckBox() + self.container.addWidget(self.checkbox, self.row, 1) + + +class IntegerOption(GenericOption): + + def __init__(self, option ,container): + super().__init__(option, container) + + self.spinbox = QSpinBox() + self.container.addWidget(self.spinbox, self.row, 1) + + class QmkSettings(BasicEditor): - def __init__(self): + def __init__(self, appctx): super().__init__() - - w = QWidget() - w.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) - self.container = QGridLayout() - w.setLayout(self.container) - self.addWidget(w) - self.setAlignment(w, QtCore.Qt.AlignHCenter) - - self.container.addWidget(QLabel("Always send Escape if Alt is pressed"), 0, 0) - self.container.addWidget(QCheckBox(), 0, 1) - self.container.addWidget(QLabel("Always send Escape if Control is pressed"), 1, 0) - self.chk_ctrl = QCheckBox() - self.chk_ctrl.stateChanged.connect(self.on_checked) - self.container.addWidget(self.chk_ctrl, 1, 1) - self.container.addWidget(QLabel("Always send Escape if GUI is pressed"), 2, 0) - self.container.addWidget(QCheckBox(), 2, 1) - self.container.addWidget(QLabel("Always send Escape if Shift is pressed"), 3, 0) - self.container.addWidget(QCheckBox(), 3, 1) - + self.appctx = appctx self.keyboard = None + self.tabs_widget = QTabWidget() + self.addWidget(self.tabs_widget) + buttons = QHBoxLayout() + buttons.addStretch() + buttons.addWidget(QPushButton("Save")) + buttons.addWidget(QPushButton("Undo")) + buttons.addWidget(QPushButton("Reset")) + self.addLayout(buttons) + + self.tabs = [] + self.create_gui() + + @staticmethod + def populate_tab(tab, container): + options = [] + for field in tab["fields"]: + if field["type"] == "boolean": + options.append(BooleanOption(field, container)) + elif field["type"] == "integer": + options.append(IntegerOption(field, container)) + else: + raise RuntimeError("unsupported field type: {}".format(field)) + return options + + def create_gui(self): + with open(self.appctx.get_resource("qmk_settings.json"), "r") as inf: + settings = json.load(inf) + + for tab in settings["tabs"]: + w = QWidget() + w.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) + container = QGridLayout() + w.setLayout(container) + l = QVBoxLayout() + l.addWidget(w) + l.setAlignment(w, QtCore.Qt.AlignHCenter) + w2 = QWidget() + w2.setLayout(l) + self.tabs_widget.addTab(w2, tab["name"]) + self.tabs.append(self.populate_tab(tab, container)) + + # self.container.addWidget(QLabel("Always send Escape if Alt is pressed"), 0, 0) + # self.container.addWidget(QCheckBox(), 0, 1) + # self.container.addWidget(QLabel("Always send Escape if Control is pressed"), 1, 0) + # self.chk_ctrl = QCheckBox() + # self.chk_ctrl.stateChanged.connect(self.on_checked) + # self.container.addWidget(self.chk_ctrl, 1, 1) + # self.container.addWidget(QLabel("Always send Escape if GUI is pressed"), 2, 0) + # self.container.addWidget(QCheckBox(), 2, 1) + # self.container.addWidget(QLabel("Always send Escape if Shift is pressed"), 3, 0) + # self.container.addWidget(QCheckBox(), 3, 1) + def reload_settings(self): gresc = self.keyboard.qmk_settings_get(1)[0] - self.chk_ctrl.setChecked(gresc & 2) + # self.chk_ctrl.setChecked(gresc & 2) def on_checked(self, state): data = struct.pack("B", int(self.chk_ctrl.isChecked()) * 2) diff --git a/src/main/resources/base/qmk_settings.json b/src/main/resources/base/qmk_settings.json new file mode 100644 index 0000000..6244779 --- /dev/null +++ b/src/main/resources/base/qmk_settings.json @@ -0,0 +1,48 @@ +{ + "tabs": [ + { + "name": "Grave Escape", + "fields": [ + { "type": "boolean", "title": "Always send Escape if Alt is pressed", "qsid": 1, "bit": 0 }, + { "type": "boolean", "title": "Always send Escape if Control is pressed", "qsid": 1, "bit": 1 }, + { "type": "boolean", "title": "Always send Escape if GUI is pressed", "qsid": 1, "bit": 2 }, + { "type": "boolean", "title": "Always send Escape if Shift is pressed", "qsid": 1, "bit": 3 } + ] + }, + { + "name": "Debounce", + "fields": [ + { "type": "integer", "title": "Debounce time (ms)", "qsid": 2, "min": 0, "max": 1000 } + ] + }, + { + "name": "Auto Shift", + "fields": [ + { "type": "boolean", "title": "Enable", "qsid": 3, "bit": 0 }, + { "type": "boolean", "title": "Enable for modifiers", "qsid": 3, "bit": 1 }, + { "type": "integer", "title": "Timeout", "qsid": 4, "min": 0, "max": 1000 }, + { "type": "boolean", "title": "Do not Auto Shift special keys", "qsid": 3, "bit": 2 }, + { "type": "boolean", "title": "Do not Auto Shift numeric keys", "qsid": 3, "bit": 3 }, + { "type": "boolean", "title": "Do not Auto Shift alpha characters", "qsid": 3, "bit": 4 }, + { "type": "boolean", "title": "Enable keyrepeat", "qsid": 3, "bit": 5 }, + { "type": "boolean", "title": "Disable automatically keyrepeating when AUTO_SHIFT_TIMEOUT is exceeded", "qsid": 3, "bit": 6 } + ] + }, + { + "name": "One Shot Keys", + "fields": [ + { "type": "integer", "title": "Tapping this number of times holds the key until tapped once again", "qsid": 5, "min": 0, "max": 50 }, + { "type": "integer", "title": "Time (in ms) before the one shot key is released", "qsid": 6, "min": 0, "max": 100000 } + ] + }, + { + "name": "Tap-Hold", + "fields": [ + { "type": "integer", "title": "TAPPING_TERM", "qsid": 7, "min": 0, "max": 10000 }, + { "type": "boolean", "title": "PERMISSIVE_HOLD", "qsid": 8, "bit": 0 }, + { "type": "boolean", "title": "IGNORE_MOD_TAP_INTERRUPT", "qsid": 8, "bit": 1 }, + { "type": "boolean", "title": "TAPPING_FORCE_HOLD", "qsid": 8, "bit": 2 } + ] + } + ] +} From e8d448f084acc3fddcd3873ee94c41da6105c708 Mon Sep 17 00:00:00 2001 From: Ilya Zhuravlev Date: Wed, 30 Jun 2021 23:42:56 -0400 Subject: [PATCH 03/10] qmk_settings: load data from keyboard --- src/main/python/qmk_settings.py | 58 +++++++++++++++-------- src/main/resources/base/qmk_settings.json | 10 ++-- 2 files changed, 42 insertions(+), 26 deletions(-) diff --git a/src/main/python/qmk_settings.py b/src/main/python/qmk_settings.py index 3b0f823..6429079 100644 --- a/src/main/python/qmk_settings.py +++ b/src/main/python/qmk_settings.py @@ -7,6 +7,7 @@ from PyQt5.QtWidgets import QVBoxLayout, QCheckBox, QGridLayout, QLabel, QWidget QHBoxLayout, QPushButton from basic_editor import BasicEditor +from util import tr from vial_device import VialKeyboard @@ -15,28 +16,55 @@ class GenericOption: def __init__(self, option, container): self.row = container.rowCount() self.option = option + self.qsid = self.option["qsid"] self.container = container self.container.addWidget(QLabel(option["title"]), self.row, 0) + def reload(self, keyboard): + data = keyboard.qmk_settings_get(self.qsid) + if not data: + raise RuntimeError("failed to retrieve setting {} from keyboard".format(self.option)) + return data + class BooleanOption(GenericOption): def __init__(self, option, container): super().__init__(option, container) + self.qsid_bit = self.option["bit"] + self.checkbox = QCheckBox() + self.checkbox.stateChanged.connect(self.on_change) self.container.addWidget(self.checkbox, self.row, 1) + def on_change(self): + print(self.option, self.checkbox.isChecked()) + + def reload(self, keyboard): + data = super().reload(keyboard) + checked = data[0] & (1 << self.qsid_bit) + + self.checkbox.blockSignals(True) + self.checkbox.setChecked(checked != 0) + self.checkbox.blockSignals(False) + class IntegerOption(GenericOption): - def __init__(self, option ,container): + def __init__(self, option, container): super().__init__(option, container) self.spinbox = QSpinBox() + self.spinbox.setMinimum(option["min"]) + self.spinbox.setMaximum(option["max"]) self.container.addWidget(self.spinbox, self.row, 1) + def reload(self, keyboard): + data = super().reload(keyboard)[0:self.option["width"]] + self.spinbox.setValue(int.from_bytes(data, byteorder="little")) + class QmkSettings(BasicEditor): @@ -49,9 +77,11 @@ class QmkSettings(BasicEditor): self.addWidget(self.tabs_widget) buttons = QHBoxLayout() buttons.addStretch() - buttons.addWidget(QPushButton("Save")) - buttons.addWidget(QPushButton("Undo")) - buttons.addWidget(QPushButton("Reset")) + buttons.addWidget(QPushButton(tr("QmkSettings", "Save"))) + btn_undo = QPushButton(tr("QmkSettings", "Undo")) + btn_undo.clicked.connect(self.reload_settings) + buttons.addWidget(btn_undo) + buttons.addWidget(QPushButton(tr("QmkSettings", "Reset"))) self.addLayout(buttons) self.tabs = [] @@ -86,24 +116,10 @@ class QmkSettings(BasicEditor): self.tabs_widget.addTab(w2, tab["name"]) self.tabs.append(self.populate_tab(tab, container)) - # self.container.addWidget(QLabel("Always send Escape if Alt is pressed"), 0, 0) - # self.container.addWidget(QCheckBox(), 0, 1) - # self.container.addWidget(QLabel("Always send Escape if Control is pressed"), 1, 0) - # self.chk_ctrl = QCheckBox() - # self.chk_ctrl.stateChanged.connect(self.on_checked) - # self.container.addWidget(self.chk_ctrl, 1, 1) - # self.container.addWidget(QLabel("Always send Escape if GUI is pressed"), 2, 0) - # self.container.addWidget(QCheckBox(), 2, 1) - # self.container.addWidget(QLabel("Always send Escape if Shift is pressed"), 3, 0) - # self.container.addWidget(QCheckBox(), 3, 1) - def reload_settings(self): - gresc = self.keyboard.qmk_settings_get(1)[0] - # self.chk_ctrl.setChecked(gresc & 2) - - def on_checked(self, state): - data = struct.pack("B", int(self.chk_ctrl.isChecked()) * 2) - self.keyboard.qmk_settings_set(1, data) + for tab in self.tabs: + for field in tab: + field.reload(self.keyboard) def rebuild(self, device): super().rebuild(device) diff --git a/src/main/resources/base/qmk_settings.json b/src/main/resources/base/qmk_settings.json index 6244779..e39d5c5 100644 --- a/src/main/resources/base/qmk_settings.json +++ b/src/main/resources/base/qmk_settings.json @@ -12,7 +12,7 @@ { "name": "Debounce", "fields": [ - { "type": "integer", "title": "Debounce time (ms)", "qsid": 2, "min": 0, "max": 1000 } + { "type": "integer", "title": "Debounce time (ms)", "qsid": 2, "min": 0, "max": 1000, "width": 2 } ] }, { @@ -20,7 +20,7 @@ "fields": [ { "type": "boolean", "title": "Enable", "qsid": 3, "bit": 0 }, { "type": "boolean", "title": "Enable for modifiers", "qsid": 3, "bit": 1 }, - { "type": "integer", "title": "Timeout", "qsid": 4, "min": 0, "max": 1000 }, + { "type": "integer", "title": "Timeout", "qsid": 4, "min": 0, "max": 1000, "width": 2 }, { "type": "boolean", "title": "Do not Auto Shift special keys", "qsid": 3, "bit": 2 }, { "type": "boolean", "title": "Do not Auto Shift numeric keys", "qsid": 3, "bit": 3 }, { "type": "boolean", "title": "Do not Auto Shift alpha characters", "qsid": 3, "bit": 4 }, @@ -31,14 +31,14 @@ { "name": "One Shot Keys", "fields": [ - { "type": "integer", "title": "Tapping this number of times holds the key until tapped once again", "qsid": 5, "min": 0, "max": 50 }, - { "type": "integer", "title": "Time (in ms) before the one shot key is released", "qsid": 6, "min": 0, "max": 100000 } + { "type": "integer", "title": "Tapping this number of times holds the key until tapped once again", "qsid": 5, "min": 0, "max": 50, "width": 1 }, + { "type": "integer", "title": "Time (in ms) before the one shot key is released", "qsid": 6, "min": 0, "max": 60000, "width": 2 } ] }, { "name": "Tap-Hold", "fields": [ - { "type": "integer", "title": "TAPPING_TERM", "qsid": 7, "min": 0, "max": 10000 }, + { "type": "integer", "title": "TAPPING_TERM", "qsid": 7, "min": 0, "max": 10000, "width": 2 }, { "type": "boolean", "title": "PERMISSIVE_HOLD", "qsid": 8, "bit": 0 }, { "type": "boolean", "title": "IGNORE_MOD_TAP_INTERRUPT", "qsid": 8, "bit": 1 }, { "type": "boolean", "title": "TAPPING_FORCE_HOLD", "qsid": 8, "bit": 2 } From c8c85c22709fb1832079e44f8f259c0f3fdd51b1 Mon Sep 17 00:00:00 2001 From: Ilya Zhuravlev Date: Wed, 30 Jun 2021 23:53:40 -0400 Subject: [PATCH 04/10] qmk_settings: send store command to keyboard --- src/main/python/qmk_settings.py | 40 +++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/src/main/python/qmk_settings.py b/src/main/python/qmk_settings.py index 6429079..5341c93 100644 --- a/src/main/python/qmk_settings.py +++ b/src/main/python/qmk_settings.py @@ -1,6 +1,6 @@ # SPDX-License-Identifier: GPL-2.0-or-later import json -import struct +from collections import defaultdict from PyQt5 import QtCore from PyQt5.QtWidgets import QVBoxLayout, QCheckBox, QGridLayout, QLabel, QWidget, QSizePolicy, QTabWidget, QSpinBox, \ @@ -36,12 +36,8 @@ class BooleanOption(GenericOption): self.qsid_bit = self.option["bit"] self.checkbox = QCheckBox() - self.checkbox.stateChanged.connect(self.on_change) self.container.addWidget(self.checkbox, self.row, 1) - def on_change(self): - print(self.option, self.checkbox.isChecked()) - def reload(self, keyboard): data = super().reload(keyboard) checked = data[0] & (1 << self.qsid_bit) @@ -50,6 +46,10 @@ class BooleanOption(GenericOption): self.checkbox.setChecked(checked != 0) self.checkbox.blockSignals(False) + def value(self): + checked = int(self.checkbox.isChecked()) + return checked << self.qsid_bit + class IntegerOption(GenericOption): @@ -65,6 +65,9 @@ class IntegerOption(GenericOption): data = super().reload(keyboard)[0:self.option["width"]] self.spinbox.setValue(int.from_bytes(data, byteorder="little")) + def value(self): + return self.spinbox.value().to_bytes(self.option["width"], byteorder="little") + class QmkSettings(BasicEditor): @@ -77,11 +80,15 @@ class QmkSettings(BasicEditor): self.addWidget(self.tabs_widget) buttons = QHBoxLayout() buttons.addStretch() - buttons.addWidget(QPushButton(tr("QmkSettings", "Save"))) + btn_save = QPushButton(tr("QmkSettings", "Save")) + btn_save.clicked.connect(self.save_settings) + buttons.addWidget(btn_save) btn_undo = QPushButton(tr("QmkSettings", "Undo")) btn_undo.clicked.connect(self.reload_settings) buttons.addWidget(btn_undo) - buttons.addWidget(QPushButton(tr("QmkSettings", "Reset"))) + btn_reset = QPushButton(tr("QmkSettings", "Reset")) + btn_reset.clicked.connect(self.reset_settings) + buttons.addWidget(btn_reset) self.addLayout(buttons) self.tabs = [] @@ -127,6 +134,25 @@ class QmkSettings(BasicEditor): self.keyboard = device.keyboard self.reload_settings() + def save_settings(self): + qsid_values = defaultdict(int) + for tab in self.tabs: + for field in tab: + # hack for boolean options - we pack several booleans into a single byte + if isinstance(field, BooleanOption): + qsid_values[field.qsid] |= field.value() + else: + qsid_values[field.qsid] = field.value() + + for qsid, value in qsid_values.items(): + if isinstance(value, int): + value = value.to_bytes(1, byteorder="little") + self.keyboard.qmk_settings_set(qsid, value) + + def reset_settings(self): + # TODO: implement this + raise NotImplementedError + def valid(self): return isinstance(self.device, VialKeyboard) and \ (self.device.keyboard and self.device.keyboard.vial_protocol >= 3) # TODO(xyz): protocol bump From 7129fca01f51f5c476757d5e20e7e3c3613d428e Mon Sep 17 00:00:00 2001 From: Ilya Zhuravlev Date: Thu, 1 Jul 2021 00:08:08 -0400 Subject: [PATCH 05/10] qmk_settings: reword autoshift --- src/main/resources/base/qmk_settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/base/qmk_settings.json b/src/main/resources/base/qmk_settings.json index e39d5c5..fe439ff 100644 --- a/src/main/resources/base/qmk_settings.json +++ b/src/main/resources/base/qmk_settings.json @@ -25,7 +25,7 @@ { "type": "boolean", "title": "Do not Auto Shift numeric keys", "qsid": 3, "bit": 3 }, { "type": "boolean", "title": "Do not Auto Shift alpha characters", "qsid": 3, "bit": 4 }, { "type": "boolean", "title": "Enable keyrepeat", "qsid": 3, "bit": 5 }, - { "type": "boolean", "title": "Disable automatically keyrepeating when AUTO_SHIFT_TIMEOUT is exceeded", "qsid": 3, "bit": 6 } + { "type": "boolean", "title": "Disable keyrepeat when timeout is exceeded", "qsid": 3, "bit": 6 } ] }, { From 6c90d164c047e43444316d2c0a658a91e7739077 Mon Sep 17 00:00:00 2001 From: Ilya Zhuravlev Date: Thu, 1 Jul 2021 15:00:45 -0400 Subject: [PATCH 06/10] qmk_settings: implement reset command --- src/main/python/keyboard_comm.py | 4 ++++ src/main/python/qmk_settings.py | 9 ++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/main/python/keyboard_comm.py b/src/main/python/keyboard_comm.py index f14afa4..8bf6391 100644 --- a/src/main/python/keyboard_comm.py +++ b/src/main/python/keyboard_comm.py @@ -55,6 +55,7 @@ CMD_VIAL_LOCK = 0x08 CMD_VIAL_QMK_SETTINGS_QUERY = 0x09 CMD_VIAL_QMK_SETTINGS_GET = 0x0A CMD_VIAL_QMK_SETTINGS_SET = 0x0B +CMD_VIAL_QMK_SETTINGS_RESET = 0x0C # how much of a macro/keymap buffer we can read/write per packet BUFFER_FETCH_CHUNK = 28 @@ -658,6 +659,9 @@ class Keyboard: print("resp", data.hex()) return data[0] + def qmk_settings_reset(self): + self.usb_send(self.dev, struct.pack("BB", CMD_VIA_VIAL_PREFIX, CMD_VIAL_QMK_SETTINGS_RESET)) + class DummyKeyboard(Keyboard): diff --git a/src/main/python/qmk_settings.py b/src/main/python/qmk_settings.py index 5341c93..039661a 100644 --- a/src/main/python/qmk_settings.py +++ b/src/main/python/qmk_settings.py @@ -4,7 +4,7 @@ from collections import defaultdict from PyQt5 import QtCore from PyQt5.QtWidgets import QVBoxLayout, QCheckBox, QGridLayout, QLabel, QWidget, QSizePolicy, QTabWidget, QSpinBox, \ - QHBoxLayout, QPushButton + QHBoxLayout, QPushButton, QMessageBox from basic_editor import BasicEditor from util import tr @@ -150,8 +150,11 @@ class QmkSettings(BasicEditor): self.keyboard.qmk_settings_set(qsid, value) def reset_settings(self): - # TODO: implement this - raise NotImplementedError + if QMessageBox.question(self.widget(), "", + tr("QmkSettings", "Reset all settings to default values?"), + QMessageBox.Yes | QMessageBox.No) == QMessageBox.Yes: + self.keyboard.qmk_settings_reset() + self.reload_settings() def valid(self): return isinstance(self.device, VialKeyboard) and \ From 3318a2c167ce64fe557ed7e9d5b62b35cd760824 Mon Sep 17 00:00:00 2001 From: Ilya Zhuravlev Date: Thu, 1 Jul 2021 15:10:32 -0400 Subject: [PATCH 07/10] qmk_settings: retrieve supported settings --- src/main/python/keyboard_comm.py | 12 +++++++++++- src/main/python/qmk_settings.py | 3 +++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/main/python/keyboard_comm.py b/src/main/python/keyboard_comm.py index 8bf6391..6086b17 100644 --- a/src/main/python/keyboard_comm.py +++ b/src/main/python/keyboard_comm.py @@ -643,7 +643,17 @@ class Keyboard: return [self.macro_deserialize(x) for x in macros] def qmk_settings_query(self): - raise NotImplementedError + cur = 0 + supported_settings = [] + while cur != 0xFFFF: + data = self.usb_send(self.dev, struct.pack(" Date: Thu, 1 Jul 2021 15:34:35 -0400 Subject: [PATCH 08/10] qmk_settings: don't display unsupported settings --- src/main/python/qmk_settings.py | 54 ++++++++++++++++++++++++++++----- 1 file changed, 47 insertions(+), 7 deletions(-) diff --git a/src/main/python/qmk_settings.py b/src/main/python/qmk_settings.py index c18e466..75b39cc 100644 --- a/src/main/python/qmk_settings.py +++ b/src/main/python/qmk_settings.py @@ -19,7 +19,8 @@ class GenericOption: self.qsid = self.option["qsid"] self.container = container - self.container.addWidget(QLabel(option["title"]), self.row, 0) + self.lbl = QLabel(option["title"]) + self.container.addWidget(self.lbl, self.row, 0) def reload(self, keyboard): data = keyboard.qmk_settings_get(self.qsid) @@ -27,6 +28,10 @@ class GenericOption: raise RuntimeError("failed to retrieve setting {} from keyboard".format(self.option)) return data + def delete(self): + self.lbl.hide() + self.lbl.deleteLater() + class BooleanOption(GenericOption): @@ -50,6 +55,11 @@ class BooleanOption(GenericOption): checked = int(self.checkbox.isChecked()) return checked << self.qsid_bit + def delete(self): + super().delete() + self.checkbox.hide() + self.checkbox.deleteLater() + class IntegerOption(GenericOption): @@ -68,6 +78,11 @@ class IntegerOption(GenericOption): def value(self): return self.spinbox.value().to_bytes(self.option["width"], byteorder="little") + def delete(self): + super().delete() + self.spinbox.hide() + self.spinbox.deleteLater() + class QmkSettings(BasicEditor): @@ -91,13 +106,15 @@ class QmkSettings(BasicEditor): buttons.addWidget(btn_reset) self.addLayout(buttons) + self.supported_settings = set() self.tabs = [] - self.create_gui() + self.misc_widgets = [] - @staticmethod - def populate_tab(tab, container): + def populate_tab(self, tab, container): options = [] for field in tab["fields"]: + if field["qsid"] not in self.supported_settings: + continue if field["type"] == "boolean": options.append(BooleanOption(field, container)) elif field["type"] == "integer": @@ -106,11 +123,33 @@ class QmkSettings(BasicEditor): raise RuntimeError("unsupported field type: {}".format(field)) return options - def create_gui(self): + def recreate_gui(self): + # delete old GUI + for tab in self.tabs: + for field in tab: + field.delete() + self.tabs.clear() + for w in self.misc_widgets: + w.hide() + w.deleteLater() + self.misc_widgets.clear() + while self.tabs_widget.count() > 0: + self.tabs_widget.removeTab(0) + with open(self.appctx.get_resource("qmk_settings.json"), "r") as inf: settings = json.load(inf) + # create new GUI for tab in settings["tabs"]: + # don't bother creating tabs that would be empty - i.e. at least one qsid in a tab should be supported + use_tab = False + for field in tab["fields"]: + if field["qsid"] in self.supported_settings: + use_tab = True + break + if not use_tab: + continue + w = QWidget() w.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) container = QGridLayout() @@ -120,12 +159,13 @@ class QmkSettings(BasicEditor): l.setAlignment(w, QtCore.Qt.AlignHCenter) w2 = QWidget() w2.setLayout(l) + self.misc_widgets += [w, w2] self.tabs_widget.addTab(w2, tab["name"]) self.tabs.append(self.populate_tab(tab, container)) def reload_settings(self): - settings = self.keyboard.qmk_settings_query() - print(settings) + self.supported_settings = set(self.keyboard.qmk_settings_query()) + self.recreate_gui() for tab in self.tabs: for field in tab: From eef81070361ec1ab56066e064c9174da68f1e117 Mon Sep 17 00:00:00 2001 From: Ilya Zhuravlev Date: Thu, 1 Jul 2021 20:43:58 -0400 Subject: [PATCH 09/10] qmk_settings: support mouse keys --- src/main/resources/base/qmk_settings.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/main/resources/base/qmk_settings.json b/src/main/resources/base/qmk_settings.json index fe439ff..bcabb3c 100644 --- a/src/main/resources/base/qmk_settings.json +++ b/src/main/resources/base/qmk_settings.json @@ -35,6 +35,20 @@ { "type": "integer", "title": "Time (in ms) before the one shot key is released", "qsid": 6, "min": 0, "max": 60000, "width": 2 } ] }, + { + "name": "Mouse keys", + "fields": [ + { "type": "integer", "title": "Delay between pressing a movement key and cursor movement", "qsid": 9, "min": 0, "max": 10000, "width": 2 }, + { "type": "integer", "title": "Time between cursor movements in milliseconds", "qsid": 10, "min": 0, "max": 10000, "width": 2 }, + { "type": "integer", "title": "Step size", "qsid": 11, "min": 0, "max": 1000, "width": 2 }, + { "type": "integer", "title": "Maximum cursor speed at which acceleration stops", "qsid": 12, "min": 0, "max": 1000, "width": 2 }, + { "type": "integer", "title": "Time until maximum cursor speed is reached", "qsid": 13, "min": 0, "max": 1000, "width": 2 }, + { "type": "integer", "title": "Delay between pressing a wheel key and wheel movement", "qsid": 14, "min": 0, "max": 10000, "width": 2 }, + { "type": "integer", "title": "Time between wheel movements", "qsid": 15, "min": 0, "max": 10000, "width": 2 }, + { "type": "integer", "title": "Maximum number of scroll steps per scroll action", "qsid": 16, "min": 0, "max": 1000, "width": 2 }, + { "type": "integer", "title": "Time until maximum scroll speed is reached", "qsid": 17, "min": 0, "max": 1000, "width": 2 } + ] + }, { "name": "Tap-Hold", "fields": [ From aa3ed9aeeeeeb874219c43337b36a840118a9c7b Mon Sep 17 00:00:00 2001 From: Ilya Zhuravlev Date: Thu, 1 Jul 2021 20:44:52 -0400 Subject: [PATCH 10/10] qmk_settings: remove debounce, tap-hold -- too much effort to implement --- src/main/resources/base/qmk_settings.json | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/main/resources/base/qmk_settings.json b/src/main/resources/base/qmk_settings.json index bcabb3c..3fd4885 100644 --- a/src/main/resources/base/qmk_settings.json +++ b/src/main/resources/base/qmk_settings.json @@ -9,12 +9,6 @@ { "type": "boolean", "title": "Always send Escape if Shift is pressed", "qsid": 1, "bit": 3 } ] }, - { - "name": "Debounce", - "fields": [ - { "type": "integer", "title": "Debounce time (ms)", "qsid": 2, "min": 0, "max": 1000, "width": 2 } - ] - }, { "name": "Auto Shift", "fields": [ @@ -48,15 +42,6 @@ { "type": "integer", "title": "Maximum number of scroll steps per scroll action", "qsid": 16, "min": 0, "max": 1000, "width": 2 }, { "type": "integer", "title": "Time until maximum scroll speed is reached", "qsid": 17, "min": 0, "max": 1000, "width": 2 } ] - }, - { - "name": "Tap-Hold", - "fields": [ - { "type": "integer", "title": "TAPPING_TERM", "qsid": 7, "min": 0, "max": 10000, "width": 2 }, - { "type": "boolean", "title": "PERMISSIVE_HOLD", "qsid": 8, "bit": 0 }, - { "type": "boolean", "title": "IGNORE_MOD_TAP_INTERRUPT", "qsid": 8, "bit": 1 }, - { "type": "boolean", "title": "TAPPING_FORCE_HOLD", "qsid": 8, "bit": 2 } - ] } ] }