From 3fe4dad968f7662cd25d56182bf6eb8996c93b0e Mon Sep 17 00:00:00 2001 From: Ilya Zhuravlev Date: Sun, 6 Dec 2020 19:24:34 -0500 Subject: [PATCH] encoders: initial support --- src/main/python/keyboard.py | 34 +++++++++++++-- src/main/python/keyboard_container.py | 62 +++++++++++++++++---------- src/main/python/keyboard_widget.py | 31 +++++++++++++- 3 files changed, 100 insertions(+), 27 deletions(-) diff --git a/src/main/python/keyboard.py b/src/main/python/keyboard.py index ffbdbdc..1ea3353 100644 --- a/src/main/python/keyboard.py +++ b/src/main/python/keyboard.py @@ -17,6 +17,8 @@ CMD_VIA_VIAL_PREFIX = 0xFE CMD_VIAL_GET_KEYBOARD_ID = 0x00 CMD_VIAL_GET_SIZE = 0x01 CMD_VIAL_GET_DEFINITION = 0x02 +CMD_VIAL_GET_ENCODER = 0x03 +CMD_VIAL_SET_ENCODER = 0x04 class Keyboard: @@ -28,9 +30,12 @@ class Keyboard: # n.b. using OrderedDict here to make order of layout requests consistent for tests self.rowcol = OrderedDict() + self.encoderpos = OrderedDict() self.layout = dict() + self.encoder_layout = dict() self.rows = self.cols = self.layers = 0 self.keys = [] + self.encoders = [] self.vial_protocol = self.keyboard_id = -1 @@ -38,7 +43,9 @@ class Keyboard: """ Load information about the keyboard: number of layers, physical key layout """ self.rowcol = OrderedDict() + self.encoderpos = OrderedDict() self.layout = dict() + self.encoder_layout = dict() self.reload_layout(sideload_json) self.reload_layers() @@ -82,16 +89,26 @@ class Keyboard: serial = KleSerial() kb = serial.deserialize(payload["layouts"]["keymap"]) - self.keys = kb.keys + self.keys = [] + self.encoders = [] - for key in self.keys: + for key in kb.keys: key.row = key.col = None - if key.labels[0] and "," in key.labels[0]: + key.encoder_idx = key.encoder_dir = None + if key.labels[4] == "e": + idx, direction = key.labels[0].split(",") + idx, direction = int(idx), int(direction) + key.encoder_idx = idx + key.encoder_dir = direction + self.encoderpos[idx] = True + self.encoders.append(key) + elif key.labels[0] and "," in key.labels[0]: row, col = key.labels[0].split(",") row, col = int(row), int(col) key.row = row key.col = col self.rowcol[(row, col)] = True + self.keys.append(key) def reload_keymap(self): """ Load current key mapping from the keyboard """ @@ -101,6 +118,10 @@ class Keyboard: data = self.usb_send(self.dev, struct.pack("BBBB", CMD_VIA_GET_KEYCODE, layer, row, col)) keycode = struct.unpack(">H", data[4:6])[0] self.layout[(layer, row, col)] = keycode + for idx in self.encoderpos: + data = self.usb_send(self.dev, struct.pack("BBBB", CMD_VIA_VIAL_PREFIX, CMD_VIAL_GET_ENCODER, layer, idx)) + 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] def set_key(self, layer, row, col, code): key = (layer, row, col) @@ -108,6 +129,13 @@ class Keyboard: self.usb_send(self.dev, struct.pack(">BBBBH", CMD_VIA_SET_KEYCODE, layer, row, col, code)) self.layout[key] = code + def set_encoder(self, layer, index, direction, code): + key = (layer, index, direction) + if self.encoder_layout[key] != code: + self.usb_send(self.dev, struct.pack(">BBBBBH", CMD_VIA_VIAL_PREFIX, CMD_VIAL_SET_ENCODER, + layer, index, direction, code)) + self.encoder_layout[key] = code + def save_layout(self): """ Serializes current layout to a binary """ diff --git a/src/main/python/keyboard_container.py b/src/main/python/keyboard_container.py index 193f438..2e8a7a1 100644 --- a/src/main/python/keyboard_container.py +++ b/src/main/python/keyboard_container.py @@ -6,7 +6,7 @@ from PyQt5.QtCore import pyqtSignal, Qt from PyQt5.QtWidgets import QWidget, QHBoxLayout, QLabel, QVBoxLayout from clickable_label import ClickableLabel -from keyboard_widget import KeyboardWidget +from keyboard_widget import KeyboardWidget, EncoderWidget from keycodes import keycode_label, keycode_tooltip, keycode_is_mask from constants import LAYER_BTN_STYLE, ACTIVE_LAYER_BTN_STYLE from util import tr @@ -39,9 +39,8 @@ class KeyboardContainer(QWidget): self.keys = [] self.layer_labels = [] - self.rowcol = defaultdict(list) - self.selected_row = -1 - self.selected_col = -1 + self.widgets = [] + self.selected_widget = None self.selected_mask = False self.keyboard = None self.current_layer = 0 @@ -74,12 +73,12 @@ class KeyboardContainer(QWidget): self.rebuild_layers() # prepare for fetching keymap - self.rowcol = defaultdict(list) + self.widgets = [] - self.container.set_keys(keyboard.keys) + self.container.set_keys(keyboard.keys, keyboard.encoders) for key in self.container.keys: - if key.desc.row is not None: - self.rowcol[(key.desc.row, key.desc.col)].append(key) + if key.desc.row is not None or key.desc.encoder_idx is not None: + self.widgets.append(key) self.current_layer = 0 self.refresh_layer_display() @@ -91,33 +90,54 @@ class KeyboardContainer(QWidget): label.setStyleSheet(LAYER_BTN_STYLE) self.layer_labels[self.current_layer].setStyleSheet(ACTIVE_LAYER_BTN_STYLE) - for (row, col), widgets in self.rowcol.items(): - code = self.keyboard.layout[(self.current_layer, row, col)] + for widget in self.widgets: + if widget.desc.row is not None: + code = self.keyboard.layout[(self.current_layer, widget.desc.row, widget.desc.col)] + else: + code = self.keyboard.encoder_layout[(self.current_layer, widget.desc.encoder_idx, + widget.desc.encoder_dir)] text = keycode_label(code) tooltip = keycode_tooltip(code) mask = keycode_is_mask(code) mask_text = keycode_label(code & 0xFF) if mask: text = text.split("\n")[0] - for widget in widgets: - widget.masked = mask - widget.setText(text) - widget.setMaskText(mask_text) - widget.setToolTip(tooltip) + widget.masked = mask + widget.setText(text) + widget.setMaskText(mask_text) + widget.setToolTip(tooltip) self.container.update() self.container.updateGeometry() def switch_layer(self, idx): self.container.deselect() self.current_layer = idx - self.selected_row = -1 - self.selected_col = -1 + self.selected_widget = None self.refresh_layer_display() def set_key(self, keycode): """ Change currently selected key to provided keycode """ - l, r, c = self.current_layer, self.selected_row, self.selected_col + if isinstance(self.selected_widget, EncoderWidget): + self.set_key_encoder(keycode) + else: + self.set_key_matrix(keycode) + + def set_key_encoder(self, keycode): + l, i, d = self.current_layer, self.selected_widget.desc.encoder_idx,\ + self.selected_widget.desc.encoder_dir + + # if masked, ensure that this is a byte-sized keycode + if self.selected_mask: + if keycode > 0xFF: + return + keycode = (self.keyboard.encoder_layout[(l, i, d)] & 0xFF00) | keycode + + self.keyboard.set_encoder(l, i, d, keycode) + self.refresh_layer_display() + + def set_key_matrix(self, keycode): + l, r, c = self.current_layer, self.selected_widget.desc.row, self.selected_widget.desc.col if r >= 0 and c >= 0: # if masked, ensure that this is a byte-sized keycode @@ -133,12 +153,8 @@ class KeyboardContainer(QWidget): """ Change which key is currently selected """ self.selected_mask = is_mask + self.selected_widget = widget - for (row, col), widgets in self.rowcol.items(): - if widget in widgets: - self.selected_row = row - self.selected_col = col - break self.refresh_layer_display() def save_layout(self): diff --git a/src/main/python/keyboard_widget.py b/src/main/python/keyboard_widget.py index 00d3d22..3918da0 100644 --- a/src/main/python/keyboard_widget.py +++ b/src/main/python/keyboard_widget.py @@ -34,6 +34,7 @@ class KeyWidget: self.bbox = self.calculate_bbox(QRectF(self.x, self.y, self.w, self.h)) self.polygon = QPolygonF(self.bbox + [self.bbox[0]]) self.draw_path = self.calculate_draw_path() + self.draw_path2 = self.calculate_draw_path2() # calculate areas where the inner keycode will be located # nonmask = outer (e.g. Rsft_T) @@ -73,6 +74,9 @@ class KeyWidget: return path + def calculate_draw_path2(self): + return QPainterPath() + def setText(self, text): self.text = text @@ -83,6 +87,28 @@ class KeyWidget: self.tooltip = tooltip +class EncoderWidget(KeyWidget): + + def calculate_draw_path(self): + path = QPainterPath() + path.addEllipse(int(self.x), int(self.y), int(self.w), int(self.h)) + return path + + def calculate_draw_path2(self): + path = QPainterPath() + if self.desc.encoder_dir == 0: + path.moveTo(int(self.x), int(self.y + self.h / 2)) + path.lineTo(int(self.x - self.w / 5), int(self.y + self.h / 3)) + path.moveTo(int(self.x), int(self.y + self.h / 2)) + path.lineTo(int(self.x + self.w / 5), int(self.y + self.h / 3)) + else: + path.moveTo(int(self.x), int(self.y + self.h / 2)) + path.lineTo(int(self.x - self.w / 5), int(self.y + self.h - self.h / 3)) + path.moveTo(int(self.x), int(self.y + self.h / 2)) + path.lineTo(int(self.x + self.w / 5), int(self.y + self.h - self.h / 3)) + return path + + class KeyboardWidget(QWidget): clicked = pyqtSignal(KeyWidget, bool) @@ -96,10 +122,12 @@ class KeyboardWidget(QWidget): self.active_key = None self.active_mask = False - def set_keys(self, keys): + def set_keys(self, keys, encoders): self.keys = [] for key in keys: self.keys.append(KeyWidget(key)) + for key in encoders: + self.keys.append(EncoderWidget(key)) self.calculate_size() self.update() @@ -155,6 +183,7 @@ class KeyboardWidget(QWidget): # draw the keycap qp.drawPath(key.draw_path) + qp.strokePath(key.draw_path2, regular_pen) # if this is a mask key, draw the inner key if key.masked: