diff --git a/src/main/python/keyboard_comm.py b/src/main/python/keyboard_comm.py index fd1d758..a0ffb53 100644 --- a/src/main/python/keyboard_comm.py +++ b/src/main/python/keyboard_comm.py @@ -52,6 +52,11 @@ 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 +CMD_VIAL_QMK_SETTINGS_RESET = 0x0C + # how much of a macro/keymap buffer we can read/write per packet BUFFER_FETCH_CHUNK = 28 @@ -637,6 +642,36 @@ class Keyboard: macros = macros[:self.macro_count] return [self.macro_deserialize(x) for x in macros] + def qmk_settings_query(self): + cur = 0 + supported_settings = [] + while cur != 0xFFFF: + data = self.usb_send(self.dev, struct.pack(" 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() + w.setLayout(container) + l = QVBoxLayout() + l.addWidget(w) + 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): + self.supported_settings = set(self.keyboard.qmk_settings_query()) + self.recreate_gui() + + for tab in self.tabs: + for field in tab: + field.reload(self.keyboard) + + def rebuild(self, device): + super().rebuild(device) + if self.valid(): + 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): + 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 \ + (self.device.keyboard and self.device.keyboard.vial_protocol >= 3) # TODO(xyz): protocol bump diff --git a/src/main/resources/base/qmk_settings.json b/src/main/resources/base/qmk_settings.json new file mode 100644 index 0000000..3fd4885 --- /dev/null +++ b/src/main/resources/base/qmk_settings.json @@ -0,0 +1,47 @@ +{ + "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": "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, "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 }, + { "type": "boolean", "title": "Enable keyrepeat", "qsid": 3, "bit": 5 }, + { "type": "boolean", "title": "Disable keyrepeat when 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, "width": 1 }, + { "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 } + ] + } + ] +}