Support delays in VIA macros

main
Ilya Zhuravlev 2021-03-06 14:05:57 -05:00
parent bc6dbfb7a2
commit 3461bc436b
4 changed files with 123 additions and 19 deletions

View File

@ -10,6 +10,7 @@ from kle_serial import Serial as KleSerial
from unlocker import Unlocker
from util import MSG_LEN, hid_send, chunks
CMD_VIA_GET_PROTOCOL_VERSION = 0x01
CMD_VIA_GET_KEYBOARD_VALUE = 0x02
CMD_VIA_SET_KEYBOARD_VALUE = 0x03
CMD_VIA_GET_KEYCODE = 0x04
@ -61,7 +62,7 @@ class Keyboard:
self.macro = b""
self.vibl = False
self.vial_protocol = self.keyboard_id = -1
self.via_protocol = self.vial_protocol = self.keyboard_id = -1
def reload(self, sideload_json=None):
""" Load information about the keyboard: number of layers, physical key layout """
@ -84,6 +85,9 @@ class Keyboard:
def reload_layout(self, sideload_json=None):
""" Requests layout data from the current device """
data = self.usb_send(self.dev, struct.pack("B", CMD_VIA_GET_PROTOCOL_VERSION), retries=20)
self.via_protocol = struct.unpack(">H", data[1:3])[0]
if sideload_json is not None:
payload = sideload_json
else:
@ -276,6 +280,8 @@ class Keyboard:
# TODO: macros should be serialized in a portable format instead of base64 string
# i.e. use a custom structure (as keycodes numbers can change, etc)
data["macro"] = base64.b64encode(self.macro).decode("utf-8")
data["vial_protocol"] = self.vial_protocol
data["via_protocol"] = self.via_protocol
return json.dumps(data).encode("utf-8")
@ -299,13 +305,15 @@ class Keyboard:
self.set_layout_options(data["layout_options"])
# we need to unlock the keyboard before we can restore the macros, lock it afterwards
# only do that if it's different from current macros
macro = base64.b64decode(data["macro"])
if macro != self.macro:
Unlocker.unlock(self)
self.set_macro(macro)
self.lock()
json_vial_protocol = data.get("vial_protocol", 1)
if json_vial_protocol == self.vial_protocol:
# we need to unlock the keyboard before we can restore the macros, lock it afterwards
# only do that if it's different from current macros
macro = base64.b64decode(data["macro"])
if macro != self.macro:
Unlocker.unlock(self)
self.set_macro(macro)
self.lock()
def reset(self):
self.usb_send(self.dev, struct.pack("B", 0xB))

View File

@ -12,9 +12,12 @@ from util import tr
MACRO_SEQUENCE_KEYCODES = KEYCODES_BASIC + KEYCODES_ISO + KEYCODES_MEDIA
KC_A = MACRO_SEQUENCE_KEYCODES[0]
SS_QMK_PREFIX = 1
SS_TAP_CODE = 1
SS_DOWN_CODE = 2
SS_UP_CODE = 3
SS_DELAY_CODE = 4
class BasicAction(QObject):
@ -43,7 +46,7 @@ class ActionText(BasicAction):
def delete(self):
self.text.deleteLater()
def serialize(self):
def serialize(self, vial_protocol):
return self.text.text().encode("utf-8")
def on_change(self):
@ -123,8 +126,11 @@ class ActionSequence(BasicAction):
def serialize_prefix(self):
raise NotImplementedError
def serialize(self):
out = b""
def serialize(self, vial_protocol):
if vial_protocol >= 2:
out = b"\x01"
else:
out = b""
for k in self.sequence:
out += self.serialize_prefix()
out += struct.pack("B", k.code)
@ -147,3 +153,30 @@ class ActionTap(ActionSequence):
def serialize_prefix(self):
return b"\x01"
class ActionDelay(BasicAction):
def __init__(self, container, delay=0):
super().__init__(container)
self.text = QLineEdit()
self.text.setText(str(delay))
self.text.textChanged.connect(self.on_change)
def insert(self, row):
self.container.addWidget(self.text, row, 2)
def remove(self):
self.container.removeWidget(self.text)
def delete(self):
self.text.deleteLater()
def on_change(self):
self.changed.emit()
def serialize(self, vial_protocol):
if vial_protocol < 2:
raise RuntimeError("ActionDelay can only be used with vial_protocol>=2")
delay = int(self.text.text())
return struct.pack("BBBB", SS_QMK_PREFIX, SS_DELAY_CODE, (delay % 255) + 1, (delay // 255) + 1)

View File

@ -3,7 +3,7 @@
from PyQt5.QtCore import QObject, pyqtSignal, Qt
from PyQt5.QtWidgets import QHBoxLayout, QToolButton, QComboBox
from macro_action import ActionText, ActionDown, ActionUp, ActionTap
from macro_action import ActionText, ActionDown, ActionUp, ActionTap, ActionDelay
class MacroLine(QObject):
@ -19,6 +19,10 @@ class MacroLine(QObject):
self.parent = parent
self.container = parent.container
if self.parent.parent.keyboard.vial_protocol >= 2:
self.types = self.types[:] + ["Delay"]
self.type_to_cls = self.type_to_cls[:] + [ActionDelay]
self.arrows = QHBoxLayout()
self.btn_up = QToolButton()
self.btn_up.setText("")
@ -87,5 +91,5 @@ class MacroLine(QObject):
def on_change(self):
self.changed.emit()
def serialize(self):
return self.action.serialize()
def serialize(self, vial_protocol):
return self.action.serialize(vial_protocol)

View File

@ -7,7 +7,7 @@ from PyQt5.QtWidgets import QPushButton, QGridLayout, QHBoxLayout, QToolButton,
from basic_editor import BasicEditor
from keycodes import Keycode
from macro_action import ActionText, ActionTap, ActionDown, ActionUp, SS_TAP_CODE, SS_DOWN_CODE, SS_UP_CODE
from macro_action import ActionText, ActionTap, ActionDown, ActionUp, ActionDelay, SS_TAP_CODE, SS_DOWN_CODE, SS_UP_CODE, SS_DELAY_CODE
from macro_key import KeyString, KeyDown, KeyUp, KeyTap
from macro_line import MacroLine
from macro_optimizer import macro_optimize
@ -22,9 +22,11 @@ class MacroTab(QVBoxLayout):
record = pyqtSignal(object, bool)
record_stop = pyqtSignal()
def __init__(self, enable_recorder):
def __init__(self, parent, enable_recorder):
super().__init__()
self.parent = parent
self.lines = []
self.container = QGridLayout()
@ -121,10 +123,10 @@ class MacroTab(QVBoxLayout):
def serialize(self):
out = b""
for line in self.lines:
out += line.serialize()
out += line.serialize(self.parent.keyboard.vial_protocol)
return out
def deserialize(self, data):
def deserialize_v1(self, data):
self.clear()
sequence = []
@ -160,6 +162,63 @@ class MacroTab(QVBoxLayout):
cls = {SS_TAP_CODE: ActionTap, SS_DOWN_CODE: ActionDown, SS_UP_CODE: ActionUp}[s[0]]
self.add_action(cls(self.container, keycodes))
def deserialize_v2(self, data):
self.clear()
sequence = []
data = bytearray(data)
while len(data) > 0:
if data[0] == SS_QMK_PREFIX:
if data[1] in [SS_TAP_CODE, SS_DOWN_CODE, SS_UP_CODE]:
# append to previous *_CODE if it's the same type, otherwise create a new entry
if len(sequence) > 0 and isinstance(sequence[-1], list) and sequence[-1][0] == data[1]:
sequence[-1][1].append(data[2])
else:
sequence.append([data[1], [data[2]]])
for x in range(3):
data.pop(0)
elif data[1] == SS_DELAY_CODE:
# decode the delay
delay = (data[2] - 1) + (data[3] - 1) * 255
sequence.append([SS_DELAY_CODE, delay])
for x in range(4):
data.pop(0)
else:
# append to previous string if it is a string, otherwise create a new entry
ch = chr(data[0])
if len(sequence) > 0 and isinstance(sequence[-1], str):
sequence[-1] += ch
else:
sequence.append(ch)
data.pop(0)
for s in sequence:
if isinstance(s, str):
self.add_action(ActionText(self.container, s))
else:
args = None
if s[0] in [SS_TAP_CODE, SS_DOWN_CODE, SS_UP_CODE]:
# map integer values to qmk keycodes
args = []
for code in s[1]:
keycode = Keycode.find_outer_keycode(code)
if keycode:
args.append(keycode)
elif s[0] == SS_DELAY_CODE:
args = s[1]
if args is not None:
cls = {SS_TAP_CODE: ActionTap, SS_DOWN_CODE: ActionDown, SS_UP_CODE: ActionUp,
SS_DELAY_CODE: ActionDelay}[s[0]]
self.add_action(cls(self.container, args))
def deserialize(self, data):
if self.parent.keyboard.vial_protocol >= 2:
return self.deserialize_v2(data)
else:
return self.deserialize_v1(data)
def on_change(self):
self.changed.emit()
@ -211,7 +270,7 @@ class MacroRecorder(BasicEditor):
self.tabs = QTabWidget()
for x in range(32):
tab = MacroTab(self.recorder is not None)
tab = MacroTab(self, self.recorder is not None)
tab.changed.connect(self.on_change)
tab.record.connect(self.on_record)
tab.record_stop.connect(self.on_tab_stop)