Support delays in VIA macros
parent
bc6dbfb7a2
commit
3461bc436b
|
|
@ -10,6 +10,7 @@ from kle_serial import Serial as KleSerial
|
||||||
from unlocker import Unlocker
|
from unlocker import Unlocker
|
||||||
from util import MSG_LEN, hid_send, chunks
|
from util import MSG_LEN, hid_send, chunks
|
||||||
|
|
||||||
|
CMD_VIA_GET_PROTOCOL_VERSION = 0x01
|
||||||
CMD_VIA_GET_KEYBOARD_VALUE = 0x02
|
CMD_VIA_GET_KEYBOARD_VALUE = 0x02
|
||||||
CMD_VIA_SET_KEYBOARD_VALUE = 0x03
|
CMD_VIA_SET_KEYBOARD_VALUE = 0x03
|
||||||
CMD_VIA_GET_KEYCODE = 0x04
|
CMD_VIA_GET_KEYCODE = 0x04
|
||||||
|
|
@ -61,7 +62,7 @@ class Keyboard:
|
||||||
self.macro = b""
|
self.macro = b""
|
||||||
self.vibl = False
|
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):
|
def reload(self, sideload_json=None):
|
||||||
""" Load information about the keyboard: number of layers, physical key layout """
|
""" Load information about the keyboard: number of layers, physical key layout """
|
||||||
|
|
@ -84,6 +85,9 @@ class Keyboard:
|
||||||
def reload_layout(self, sideload_json=None):
|
def reload_layout(self, sideload_json=None):
|
||||||
""" Requests layout data from the current device """
|
""" 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:
|
if sideload_json is not None:
|
||||||
payload = sideload_json
|
payload = sideload_json
|
||||||
else:
|
else:
|
||||||
|
|
@ -276,6 +280,8 @@ class Keyboard:
|
||||||
# TODO: macros should be serialized in a portable format instead of base64 string
|
# 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)
|
# i.e. use a custom structure (as keycodes numbers can change, etc)
|
||||||
data["macro"] = base64.b64encode(self.macro).decode("utf-8")
|
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")
|
return json.dumps(data).encode("utf-8")
|
||||||
|
|
||||||
|
|
@ -299,13 +305,15 @@ class Keyboard:
|
||||||
|
|
||||||
self.set_layout_options(data["layout_options"])
|
self.set_layout_options(data["layout_options"])
|
||||||
|
|
||||||
# we need to unlock the keyboard before we can restore the macros, lock it afterwards
|
json_vial_protocol = data.get("vial_protocol", 1)
|
||||||
# only do that if it's different from current macros
|
if json_vial_protocol == self.vial_protocol:
|
||||||
macro = base64.b64decode(data["macro"])
|
# we need to unlock the keyboard before we can restore the macros, lock it afterwards
|
||||||
if macro != self.macro:
|
# only do that if it's different from current macros
|
||||||
Unlocker.unlock(self)
|
macro = base64.b64decode(data["macro"])
|
||||||
self.set_macro(macro)
|
if macro != self.macro:
|
||||||
self.lock()
|
Unlocker.unlock(self)
|
||||||
|
self.set_macro(macro)
|
||||||
|
self.lock()
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
self.usb_send(self.dev, struct.pack("B", 0xB))
|
self.usb_send(self.dev, struct.pack("B", 0xB))
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,12 @@ from util import tr
|
||||||
MACRO_SEQUENCE_KEYCODES = KEYCODES_BASIC + KEYCODES_ISO + KEYCODES_MEDIA
|
MACRO_SEQUENCE_KEYCODES = KEYCODES_BASIC + KEYCODES_ISO + KEYCODES_MEDIA
|
||||||
KC_A = MACRO_SEQUENCE_KEYCODES[0]
|
KC_A = MACRO_SEQUENCE_KEYCODES[0]
|
||||||
|
|
||||||
|
SS_QMK_PREFIX = 1
|
||||||
|
|
||||||
SS_TAP_CODE = 1
|
SS_TAP_CODE = 1
|
||||||
SS_DOWN_CODE = 2
|
SS_DOWN_CODE = 2
|
||||||
SS_UP_CODE = 3
|
SS_UP_CODE = 3
|
||||||
|
SS_DELAY_CODE = 4
|
||||||
|
|
||||||
|
|
||||||
class BasicAction(QObject):
|
class BasicAction(QObject):
|
||||||
|
|
@ -43,7 +46,7 @@ class ActionText(BasicAction):
|
||||||
def delete(self):
|
def delete(self):
|
||||||
self.text.deleteLater()
|
self.text.deleteLater()
|
||||||
|
|
||||||
def serialize(self):
|
def serialize(self, vial_protocol):
|
||||||
return self.text.text().encode("utf-8")
|
return self.text.text().encode("utf-8")
|
||||||
|
|
||||||
def on_change(self):
|
def on_change(self):
|
||||||
|
|
@ -123,8 +126,11 @@ class ActionSequence(BasicAction):
|
||||||
def serialize_prefix(self):
|
def serialize_prefix(self):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def serialize(self):
|
def serialize(self, vial_protocol):
|
||||||
out = b""
|
if vial_protocol >= 2:
|
||||||
|
out = b"\x01"
|
||||||
|
else:
|
||||||
|
out = b""
|
||||||
for k in self.sequence:
|
for k in self.sequence:
|
||||||
out += self.serialize_prefix()
|
out += self.serialize_prefix()
|
||||||
out += struct.pack("B", k.code)
|
out += struct.pack("B", k.code)
|
||||||
|
|
@ -147,3 +153,30 @@ class ActionTap(ActionSequence):
|
||||||
|
|
||||||
def serialize_prefix(self):
|
def serialize_prefix(self):
|
||||||
return b"\x01"
|
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)
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
from PyQt5.QtCore import QObject, pyqtSignal, Qt
|
from PyQt5.QtCore import QObject, pyqtSignal, Qt
|
||||||
from PyQt5.QtWidgets import QHBoxLayout, QToolButton, QComboBox
|
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):
|
class MacroLine(QObject):
|
||||||
|
|
@ -19,6 +19,10 @@ class MacroLine(QObject):
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.container = parent.container
|
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.arrows = QHBoxLayout()
|
||||||
self.btn_up = QToolButton()
|
self.btn_up = QToolButton()
|
||||||
self.btn_up.setText("▲")
|
self.btn_up.setText("▲")
|
||||||
|
|
@ -87,5 +91,5 @@ class MacroLine(QObject):
|
||||||
def on_change(self):
|
def on_change(self):
|
||||||
self.changed.emit()
|
self.changed.emit()
|
||||||
|
|
||||||
def serialize(self):
|
def serialize(self, vial_protocol):
|
||||||
return self.action.serialize()
|
return self.action.serialize(vial_protocol)
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ from PyQt5.QtWidgets import QPushButton, QGridLayout, QHBoxLayout, QToolButton,
|
||||||
|
|
||||||
from basic_editor import BasicEditor
|
from basic_editor import BasicEditor
|
||||||
from keycodes import Keycode
|
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_key import KeyString, KeyDown, KeyUp, KeyTap
|
||||||
from macro_line import MacroLine
|
from macro_line import MacroLine
|
||||||
from macro_optimizer import macro_optimize
|
from macro_optimizer import macro_optimize
|
||||||
|
|
@ -22,9 +22,11 @@ class MacroTab(QVBoxLayout):
|
||||||
record = pyqtSignal(object, bool)
|
record = pyqtSignal(object, bool)
|
||||||
record_stop = pyqtSignal()
|
record_stop = pyqtSignal()
|
||||||
|
|
||||||
def __init__(self, enable_recorder):
|
def __init__(self, parent, enable_recorder):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
|
self.parent = parent
|
||||||
|
|
||||||
self.lines = []
|
self.lines = []
|
||||||
|
|
||||||
self.container = QGridLayout()
|
self.container = QGridLayout()
|
||||||
|
|
@ -121,10 +123,10 @@ class MacroTab(QVBoxLayout):
|
||||||
def serialize(self):
|
def serialize(self):
|
||||||
out = b""
|
out = b""
|
||||||
for line in self.lines:
|
for line in self.lines:
|
||||||
out += line.serialize()
|
out += line.serialize(self.parent.keyboard.vial_protocol)
|
||||||
return out
|
return out
|
||||||
|
|
||||||
def deserialize(self, data):
|
def deserialize_v1(self, data):
|
||||||
self.clear()
|
self.clear()
|
||||||
|
|
||||||
sequence = []
|
sequence = []
|
||||||
|
|
@ -160,6 +162,63 @@ class MacroTab(QVBoxLayout):
|
||||||
cls = {SS_TAP_CODE: ActionTap, SS_DOWN_CODE: ActionDown, SS_UP_CODE: ActionUp}[s[0]]
|
cls = {SS_TAP_CODE: ActionTap, SS_DOWN_CODE: ActionDown, SS_UP_CODE: ActionUp}[s[0]]
|
||||||
self.add_action(cls(self.container, keycodes))
|
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):
|
def on_change(self):
|
||||||
self.changed.emit()
|
self.changed.emit()
|
||||||
|
|
||||||
|
|
@ -211,7 +270,7 @@ class MacroRecorder(BasicEditor):
|
||||||
|
|
||||||
self.tabs = QTabWidget()
|
self.tabs = QTabWidget()
|
||||||
for x in range(32):
|
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.changed.connect(self.on_change)
|
||||||
tab.record.connect(self.on_record)
|
tab.record.connect(self.on_record)
|
||||||
tab.record_stop.connect(self.on_tab_stop)
|
tab.record_stop.connect(self.on_tab_stop)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue