From 6add0a8d70c07a150147eea63099147c34a863d4 Mon Sep 17 00:00:00 2001 From: Ilya Zhuravlev Date: Sun, 20 Dec 2020 21:04:26 -0500 Subject: [PATCH] layout_editor: implement loading and parsing of layout options --- src/main/python/keyboard.py | 17 ++++++ src/main/python/layout_editor.py | 98 +++++++++++++++++++++++++++++++- 2 files changed, 114 insertions(+), 1 deletion(-) diff --git a/src/main/python/keyboard.py b/src/main/python/keyboard.py index b0cb349..54809b5 100644 --- a/src/main/python/keyboard.py +++ b/src/main/python/keyboard.py @@ -9,11 +9,15 @@ from kle_serial import Serial as KleSerial from util import MSG_LEN, hid_send +CMD_VIA_GET_KEYBOARD_VALUE = 0x02 +CMD_VIA_SET_KEYBOARD_VALUE = 0x03 CMD_VIA_GET_KEYCODE = 0x04 CMD_VIA_SET_KEYCODE = 0x05 CMD_VIA_GET_LAYER_COUNT = 0x11 CMD_VIA_VIAL_PREFIX = 0xFE +VIA_LAYOUT_OPTIONS = 0x02 + CMD_VIAL_GET_KEYBOARD_ID = 0x00 CMD_VIAL_GET_SIZE = 0x01 CMD_VIAL_GET_DEFINITION = 0x02 @@ -34,6 +38,8 @@ class Keyboard: self.layout = dict() self.encoder_layout = dict() self.rows = self.cols = self.layers = 0 + self.layouts = None + self.layout_options = 0 self.keys = [] self.encoders = [] @@ -83,6 +89,8 @@ class Keyboard: payload = json.loads(lzma.decompress(payload)) + self.layouts = payload.get("layouts") + self.rows = payload["matrix"]["rows"] self.cols = payload["matrix"]["cols"] @@ -125,6 +133,10 @@ class Keyboard: self.encoder_layout[(layer, idx, 0)] = struct.unpack(">H", data[0:2])[0] self.encoder_layout[(layer, idx, 1)] = struct.unpack(">H", data[2:4])[0] + if self.layouts: + data = self.usb_send(self.dev, struct.pack("BB", CMD_VIA_GET_KEYBOARD_VALUE, VIA_LAYOUT_OPTIONS)) + self.layout_options = struct.unpack(">I", data[2:6])[0] + def set_key(self, layer, row, col, code): key = (layer, row, col) if self.layout[key] != code: @@ -138,6 +150,11 @@ class Keyboard: layer, index, direction, code)) self.encoder_layout[key] = code + def set_layout_options(self, options): + if self.layout_options != options: + self.layout_options = options + self.usb_send(self.dev, struct.pack(">BBI", CMD_VIA_SET_KEYBOARD_VALUE, VIA_LAYOUT_OPTIONS, options)) + def save_layout(self): """ Serializes current layout to a binary """ diff --git a/src/main/python/layout_editor.py b/src/main/python/layout_editor.py index 80ff8a4..dc54ed4 100644 --- a/src/main/python/layout_editor.py +++ b/src/main/python/layout_editor.py @@ -1,17 +1,113 @@ # SPDX-License-Identifier: GPL-2.0-or-later +from PyQt5.QtWidgets import QLabel, QCheckBox, QComboBox, QGridLayout from basic_editor import BasicEditor from vial_device import VialKeyboard +class BooleanChoice: + + def __init__(self, container, label): + self.choice = 0 + + self.widget_label = QLabel(label) + self.widget_checkbox = QCheckBox() + + row = container.rowCount() + container.addWidget(self.widget_label, row, 0) + container.addWidget(self.widget_checkbox, row, 1) + + def delete(self): + self.widget_label.deleteLater() + self.widget_checkbox.deleteLater() + + def pack(self): + return str(self.choice) + + def unpack(self, value): + self.change(int(value)) + + def change(self, value): + self.widget_checkbox.setChecked(bool(value)) + + +class SelectChoice: + + def __init__(self, container, label, options): + self.choice = 0 + self.options = options + + self.widget_label = QLabel(label) + self.widget_options = QComboBox() + self.widget_options.addItems(options) + + row = container.rowCount() + container.addWidget(self.widget_label, row, 0) + container.addWidget(self.widget_options, row, 1) + + def delete(self): + self.widget_label.deleteLater() + self.widget_options.deleteLater() + + def pack(self): + val = bin(self.choice)[2:] + val = "0" * ((len(self.options) - 1).bit_length() - len(val)) + val + return val + + def unpack(self, value): + self.change(int(value, 2)) + + def change(self, value): + self.choice = value + self.widget_options.setCurrentIndex(self.choice) + + class LayoutEditor(BasicEditor): def __init__(self, parent=None): super().__init__(parent) self.device = None + self.choices = [] + + self.widgets = [] + + self.container = QGridLayout() + self.addLayout(self.container) + def rebuild(self, device): super().rebuild(device) + if not self.valid: + return + + for choice in self.choices: + choice.delete() + self.choices = [] + + for item in device.keyboard.layouts["labels"]: + if isinstance(item, str): + self.choices.append(BooleanChoice(self.container, item)) + else: + self.choices.append(SelectChoice(self.container, item[0], item[1:])) + + self.unpack(self.device.keyboard.layout_options) + def valid(self): - return isinstance(self.device, VialKeyboard) + return isinstance(self.device, VialKeyboard) and self.device.keyboard.layouts + + def pack(self): + val = "" + for choice in self.choices[::-1]: + val += choice.pack() + return int(val, 2) + + def unpack(self, value): + # we operate on bit strings + value = "0" * 100 + bin(value)[2:] + + # VIA stores option choices backwards, we need to parse the input in reverse + for choice in self.choices[::-1]: + sz = len(choice.pack()) + choice.unpack(value[-sz:]) + value = value[:-sz]