Merge branch 'combos' into next
commit
b8acb0a00f
|
|
@ -0,0 +1,118 @@
|
||||||
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
from PyQt5 import QtCore
|
||||||
|
from PyQt5.QtCore import pyqtSignal, QObject
|
||||||
|
from PyQt5.QtWidgets import QTabWidget, QWidget, QSizePolicy, QGridLayout, QVBoxLayout, QLabel
|
||||||
|
|
||||||
|
from key_widget import KeyWidget
|
||||||
|
from tabbed_keycodes import TabbedKeycodes
|
||||||
|
from vial_device import VialKeyboard
|
||||||
|
from basic_editor import BasicEditor
|
||||||
|
|
||||||
|
|
||||||
|
class ComboEntryUI(QObject):
|
||||||
|
|
||||||
|
key_changed = pyqtSignal()
|
||||||
|
|
||||||
|
def __init__(self, idx):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self.idx = idx
|
||||||
|
self.container = QGridLayout()
|
||||||
|
self.kc_inputs = []
|
||||||
|
self.populate_container()
|
||||||
|
|
||||||
|
w = QWidget()
|
||||||
|
w.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
|
||||||
|
w.setLayout(self.container)
|
||||||
|
l = QVBoxLayout()
|
||||||
|
l.addWidget(w)
|
||||||
|
l.setAlignment(w, QtCore.Qt.AlignHCenter)
|
||||||
|
self.w2 = QWidget()
|
||||||
|
self.w2.setLayout(l)
|
||||||
|
|
||||||
|
def populate_container(self):
|
||||||
|
for x in range(4):
|
||||||
|
kc_widget = KeyWidget()
|
||||||
|
kc_widget.changed.connect(self.on_key_changed)
|
||||||
|
self.container.addWidget(QLabel("Key {}".format(x + 1)), x, 0)
|
||||||
|
self.container.addWidget(kc_widget, x, 1)
|
||||||
|
self.kc_inputs.append(kc_widget)
|
||||||
|
|
||||||
|
self.kc_output = KeyWidget()
|
||||||
|
self.kc_output.changed.connect(self.on_key_changed)
|
||||||
|
self.container.addWidget(QLabel("Output key"), 4, 0)
|
||||||
|
self.container.addWidget(self.kc_output, 4, 1)
|
||||||
|
|
||||||
|
def widget(self):
|
||||||
|
return self.w2
|
||||||
|
|
||||||
|
def load(self, data):
|
||||||
|
objs = self.kc_inputs + [self.kc_output]
|
||||||
|
for o in objs:
|
||||||
|
o.blockSignals(True)
|
||||||
|
|
||||||
|
for x in range(4):
|
||||||
|
self.kc_inputs[x].set_keycode(data[x])
|
||||||
|
self.kc_output.set_keycode(data[4])
|
||||||
|
|
||||||
|
for o in objs:
|
||||||
|
o.blockSignals(False)
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
return (
|
||||||
|
self.kc_inputs[0].keycode,
|
||||||
|
self.kc_inputs[1].keycode,
|
||||||
|
self.kc_inputs[2].keycode,
|
||||||
|
self.kc_inputs[3].keycode,
|
||||||
|
self.kc_output.keycode
|
||||||
|
)
|
||||||
|
|
||||||
|
def on_key_changed(self):
|
||||||
|
self.key_changed.emit()
|
||||||
|
|
||||||
|
|
||||||
|
class CustomTabWidget(QTabWidget):
|
||||||
|
|
||||||
|
def mouseReleaseEvent(self, ev):
|
||||||
|
TabbedKeycodes.close_tray()
|
||||||
|
|
||||||
|
|
||||||
|
class Combos(BasicEditor):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.keyboard = None
|
||||||
|
|
||||||
|
self.combo_entries = []
|
||||||
|
self.combo_entries_available = []
|
||||||
|
self.tabs = CustomTabWidget()
|
||||||
|
for x in range(128):
|
||||||
|
entry = ComboEntryUI(x)
|
||||||
|
entry.key_changed.connect(self.on_key_changed)
|
||||||
|
self.combo_entries_available.append(entry)
|
||||||
|
|
||||||
|
self.addWidget(self.tabs)
|
||||||
|
|
||||||
|
def rebuild_ui(self):
|
||||||
|
while self.tabs.count() > 0:
|
||||||
|
self.tabs.removeTab(0)
|
||||||
|
self.combo_entries = self.combo_entries_available[:self.keyboard.combo_count]
|
||||||
|
for x, e in enumerate(self.combo_entries):
|
||||||
|
self.tabs.addTab(e.widget(), str(x + 1))
|
||||||
|
for x, e in enumerate(self.combo_entries):
|
||||||
|
e.load(self.keyboard.combo_get(x))
|
||||||
|
|
||||||
|
def rebuild(self, device):
|
||||||
|
super().rebuild(device)
|
||||||
|
if self.valid():
|
||||||
|
self.keyboard = device.keyboard
|
||||||
|
self.rebuild_ui()
|
||||||
|
|
||||||
|
def valid(self):
|
||||||
|
return isinstance(self.device, VialKeyboard) and \
|
||||||
|
(self.device.keyboard and self.device.keyboard.vial_protocol >= 4
|
||||||
|
and self.device.keyboard.combo_count > 0)
|
||||||
|
|
||||||
|
def on_key_changed(self):
|
||||||
|
for x, e in enumerate(self.combo_entries):
|
||||||
|
self.keyboard.combo_set(x, self.combo_entries[x].save())
|
||||||
|
|
@ -62,6 +62,8 @@ CMD_VIAL_DYNAMIC_ENTRY_OP = 0x0D
|
||||||
DYNAMIC_VIAL_GET_NUMBER_OF_ENTRIES = 0x00
|
DYNAMIC_VIAL_GET_NUMBER_OF_ENTRIES = 0x00
|
||||||
DYNAMIC_VIAL_TAP_DANCE_GET = 0x01
|
DYNAMIC_VIAL_TAP_DANCE_GET = 0x01
|
||||||
DYNAMIC_VIAL_TAP_DANCE_SET = 0x02
|
DYNAMIC_VIAL_TAP_DANCE_SET = 0x02
|
||||||
|
DYNAMIC_VIAL_COMBO_GET = 0x03
|
||||||
|
DYNAMIC_VIAL_COMBO_SET = 0x04
|
||||||
|
|
||||||
# how much of a macro/keymap buffer we can read/write per packet
|
# how much of a macro/keymap buffer we can read/write per packet
|
||||||
BUFFER_FETCH_CHUNK = 28
|
BUFFER_FETCH_CHUNK = 28
|
||||||
|
|
@ -385,10 +387,14 @@ class Keyboard:
|
||||||
if self.vial_protocol < 4:
|
if self.vial_protocol < 4:
|
||||||
self.tap_dance_count = 0
|
self.tap_dance_count = 0
|
||||||
self.tap_dance_entries = []
|
self.tap_dance_entries = []
|
||||||
|
self.combo_count = 0
|
||||||
|
self.combo_entries = []
|
||||||
return
|
return
|
||||||
data = self.usb_send(self.dev, struct.pack("BBB", CMD_VIA_VIAL_PREFIX, CMD_VIAL_DYNAMIC_ENTRY_OP,
|
data = self.usb_send(self.dev, struct.pack("BBB", CMD_VIA_VIAL_PREFIX, CMD_VIAL_DYNAMIC_ENTRY_OP,
|
||||||
DYNAMIC_VIAL_GET_NUMBER_OF_ENTRIES), retries=20)
|
DYNAMIC_VIAL_GET_NUMBER_OF_ENTRIES), retries=20)
|
||||||
self.tap_dance_count = data[0]
|
self.tap_dance_count = data[0]
|
||||||
|
self.combo_count = data[1]
|
||||||
|
|
||||||
self.tap_dance_entries = []
|
self.tap_dance_entries = []
|
||||||
for x in range(self.tap_dance_count):
|
for x in range(self.tap_dance_count):
|
||||||
data = self.usb_send(self.dev, struct.pack("BBBB", CMD_VIA_VIAL_PREFIX, CMD_VIAL_DYNAMIC_ENTRY_OP,
|
data = self.usb_send(self.dev, struct.pack("BBBB", CMD_VIA_VIAL_PREFIX, CMD_VIAL_DYNAMIC_ENTRY_OP,
|
||||||
|
|
@ -397,6 +403,15 @@ class Keyboard:
|
||||||
raise RuntimeError("failed retrieving tapdance entry {} from the device".format(x))
|
raise RuntimeError("failed retrieving tapdance entry {} from the device".format(x))
|
||||||
self.tap_dance_entries.append(struct.unpack("<HHHHH", data[1:11]))
|
self.tap_dance_entries.append(struct.unpack("<HHHHH", data[1:11]))
|
||||||
|
|
||||||
|
self.combo_entries = []
|
||||||
|
for x in range(self.combo_count):
|
||||||
|
data = self.usb_send(self.dev, struct.pack("BBBB", CMD_VIA_VIAL_PREFIX, CMD_VIAL_DYNAMIC_ENTRY_OP,
|
||||||
|
DYNAMIC_VIAL_COMBO_GET, x), retries=20)
|
||||||
|
|
||||||
|
if data[0] != 0:
|
||||||
|
raise RuntimeError("failed retrieving combo entry {} from the device".format(x))
|
||||||
|
self.combo_entries.append(struct.unpack("<HHHHH", data[1:11]))
|
||||||
|
|
||||||
def set_key(self, layer, row, col, code):
|
def set_key(self, layer, row, col, code):
|
||||||
if code < 0:
|
if code < 0:
|
||||||
return
|
return
|
||||||
|
|
@ -500,7 +515,9 @@ class Keyboard:
|
||||||
data["macro"] = self.save_macro()
|
data["macro"] = self.save_macro()
|
||||||
data["vial_protocol"] = self.vial_protocol
|
data["vial_protocol"] = self.vial_protocol
|
||||||
data["via_protocol"] = self.via_protocol
|
data["via_protocol"] = self.via_protocol
|
||||||
|
# TODO: should store/restore serialized keycodes for these two
|
||||||
data["tap_dance"] = self.tap_dance_entries
|
data["tap_dance"] = self.tap_dance_entries
|
||||||
|
data["combo"] = self.combo_entries
|
||||||
|
|
||||||
return json.dumps(data).encode("utf-8")
|
return json.dumps(data).encode("utf-8")
|
||||||
|
|
||||||
|
|
@ -535,6 +552,9 @@ class Keyboard:
|
||||||
for x, e in enumerate(data.get("tap_dance", [])):
|
for x, e in enumerate(data.get("tap_dance", [])):
|
||||||
if x < self.tap_dance_count:
|
if x < self.tap_dance_count:
|
||||||
self.tap_dance_set(x, e)
|
self.tap_dance_set(x, e)
|
||||||
|
for x, e in enumerate(data.get("combo", [])):
|
||||||
|
if x < self.combo_count:
|
||||||
|
self.combo_set(x, e)
|
||||||
|
|
||||||
def restore_macros(self, macros):
|
def restore_macros(self, macros):
|
||||||
if not isinstance(macros, list):
|
if not isinstance(macros, list):
|
||||||
|
|
@ -709,6 +729,17 @@ class Keyboard:
|
||||||
self.usb_send(self.dev, struct.pack("BBBB", CMD_VIA_VIAL_PREFIX, CMD_VIAL_DYNAMIC_ENTRY_OP,
|
self.usb_send(self.dev, struct.pack("BBBB", CMD_VIA_VIAL_PREFIX, CMD_VIAL_DYNAMIC_ENTRY_OP,
|
||||||
DYNAMIC_VIAL_TAP_DANCE_SET, idx) + serialized, retries=20)
|
DYNAMIC_VIAL_TAP_DANCE_SET, idx) + serialized, retries=20)
|
||||||
|
|
||||||
|
def combo_get(self, idx):
|
||||||
|
return self.combo_entries[idx]
|
||||||
|
|
||||||
|
def combo_set(self, idx, entry):
|
||||||
|
if self.combo_entries[idx] == entry:
|
||||||
|
return
|
||||||
|
self.combo_entries[idx] = entry
|
||||||
|
serialized = struct.pack("<HHHHH", *self.combo_entries[idx])
|
||||||
|
self.usb_send(self.dev, struct.pack("BBBB", CMD_VIA_VIAL_PREFIX, CMD_VIAL_DYNAMIC_ENTRY_OP,
|
||||||
|
DYNAMIC_VIAL_COMBO_SET, idx) + serialized, retries=20)
|
||||||
|
|
||||||
|
|
||||||
class DummyKeyboard(Keyboard):
|
class DummyKeyboard(Keyboard):
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import os
|
||||||
import sys
|
import sys
|
||||||
from urllib.request import urlopen
|
from urllib.request import urlopen
|
||||||
|
|
||||||
|
from combos import Combos
|
||||||
from editor_container import EditorContainer
|
from editor_container import EditorContainer
|
||||||
from firmware_flasher import FirmwareFlasher
|
from firmware_flasher import FirmwareFlasher
|
||||||
from keyboard_comm import ProtocolError
|
from keyboard_comm import ProtocolError
|
||||||
|
|
@ -61,12 +62,13 @@ class MainWindow(QMainWindow):
|
||||||
self.firmware_flasher = FirmwareFlasher(self)
|
self.firmware_flasher = FirmwareFlasher(self)
|
||||||
self.macro_recorder = MacroRecorder()
|
self.macro_recorder = MacroRecorder()
|
||||||
self.tap_dance = TapDance()
|
self.tap_dance = TapDance()
|
||||||
|
self.combos = Combos()
|
||||||
self.qmk_settings = QmkSettings(self.appctx)
|
self.qmk_settings = QmkSettings(self.appctx)
|
||||||
self.matrix_tester = MatrixTest(self.layout_editor)
|
self.matrix_tester = MatrixTest(self.layout_editor)
|
||||||
self.rgb_configurator = RGBConfigurator()
|
self.rgb_configurator = RGBConfigurator()
|
||||||
|
|
||||||
self.editors = [(self.keymap_editor, "Keymap"), (self.layout_editor, "Layout"), (self.macro_recorder, "Macros"),
|
self.editors = [(self.keymap_editor, "Keymap"), (self.layout_editor, "Layout"), (self.macro_recorder, "Macros"),
|
||||||
(self.rgb_configurator, "Lighting"), (self.tap_dance, "Tap Dance"),
|
(self.rgb_configurator, "Lighting"), (self.tap_dance, "Tap Dance"), (self.combos, "Combos"),
|
||||||
(self.qmk_settings, "QMK Settings"),
|
(self.qmk_settings, "QMK Settings"),
|
||||||
(self.matrix_tester, "Matrix tester"), (self.firmware_flasher, "Firmware updater")]
|
(self.matrix_tester, "Matrix tester"), (self.firmware_flasher, "Firmware updater")]
|
||||||
|
|
||||||
|
|
@ -265,7 +267,7 @@ class MainWindow(QMainWindow):
|
||||||
self.current_device.keyboard.reload()
|
self.current_device.keyboard.reload()
|
||||||
|
|
||||||
for e in [self.layout_editor, self.keymap_editor, self.firmware_flasher, self.macro_recorder,
|
for e in [self.layout_editor, self.keymap_editor, self.firmware_flasher, self.macro_recorder,
|
||||||
self.tap_dance, self.qmk_settings, self.matrix_tester, self.rgb_configurator]:
|
self.tap_dance, self.combos, self.qmk_settings, self.matrix_tester, self.rgb_configurator]:
|
||||||
e.rebuild(self.current_device)
|
e.rebuild(self.current_device)
|
||||||
|
|
||||||
def refresh_tabs(self):
|
def refresh_tabs(self):
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,12 @@
|
||||||
{ "type": "boolean", "title": "Disable keyrepeat when timeout is exceeded", "qsid": 3, "bit": 6 }
|
{ "type": "boolean", "title": "Disable keyrepeat when timeout is exceeded", "qsid": 3, "bit": 6 }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Combo",
|
||||||
|
"fields": [
|
||||||
|
{ "type": "integer", "title": "Time out period for combos", "qsid": 2, "min": 0, "max": 10000, "width": 2 }
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "One Shot Keys",
|
"name": "One Shot Keys",
|
||||||
"fields": [
|
"fields": [
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue