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 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))
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue