macro_recorder: basic linux macro recording
parent
cb9e0c90cd
commit
abf62439e4
|
|
@ -1,15 +1,20 @@
|
||||||
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import keyboard
|
import keyboard
|
||||||
|
|
||||||
|
|
||||||
def key_cb(key):
|
def key_cb(key):
|
||||||
print(key)
|
names = keyboard._nixkeyboard.to_name[(key.scan_code, ())] or ["unknown"]
|
||||||
|
name = names[0]
|
||||||
|
name = keyboard.normalize_name(name)
|
||||||
|
|
||||||
|
sys.stdout.write("{}:{}\n".format(key.event_type, name))
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
|
||||||
def linux_keystroke_recorder():
|
def linux_keystroke_recorder():
|
||||||
keyboard.hook(key_cb)
|
keyboard.hook(key_cb)
|
||||||
print("Recording")
|
|
||||||
while True:
|
while True:
|
||||||
ch = sys.stdin.read(1)
|
ch = sys.stdin.read(1)
|
||||||
if ch == "q":
|
if ch == "q":
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
from keycodes import keycode_label
|
||||||
|
|
||||||
|
|
||||||
|
class KeyDown:
|
||||||
|
|
||||||
|
def __init__(self, keycode):
|
||||||
|
self.keycode = keycode
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "Down({})".format(keycode_label(self.keycode.code))
|
||||||
|
|
||||||
|
|
||||||
|
class KeyUp:
|
||||||
|
|
||||||
|
def __init__(self, keycode):
|
||||||
|
self.keycode = keycode
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "Up({})".format(keycode_label(self.keycode.code))
|
||||||
|
|
||||||
|
|
||||||
|
class KeyTap:
|
||||||
|
|
||||||
|
def __init__(self, keycode):
|
||||||
|
self.keycode = keycode
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "Tap({})".format(keycode_label(self.keycode.code))
|
||||||
|
|
||||||
|
|
||||||
|
class KeyString:
|
||||||
|
|
||||||
|
def __init__(self, string):
|
||||||
|
self.string = string
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "SendString({})".format(self.string)
|
||||||
|
|
@ -1,21 +1,116 @@
|
||||||
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from PyQt5 import QtCore
|
from PyQt5 import QtCore
|
||||||
from PyQt5.QtCore import QProcess
|
from PyQt5.QtCore import QProcess, pyqtSignal
|
||||||
from PyQt5.QtWidgets import QPushButton, QWidget, QApplication, QVBoxLayout
|
from PyQt5.QtWidgets import QPushButton, QWidget, QApplication, QVBoxLayout
|
||||||
from fbs_runtime.application_context import is_frozen
|
from fbs_runtime.application_context import is_frozen
|
||||||
|
|
||||||
from basic_editor import BasicEditor
|
from basic_editor import BasicEditor
|
||||||
|
from keycodes import KEYCODES_BASIC
|
||||||
|
from macro_key import KeyDown, KeyUp
|
||||||
from util import tr
|
from util import tr
|
||||||
from vial_device import VialKeyboard
|
from vial_device import VialKeyboard
|
||||||
|
|
||||||
|
|
||||||
class LinuxRecorder(QWidget):
|
class LinuxRecorder(QWidget):
|
||||||
|
|
||||||
|
keystroke = pyqtSignal(object)
|
||||||
|
stopped = pyqtSignal()
|
||||||
|
|
||||||
|
mapping_qmk_id = {
|
||||||
|
"esc": "KC_ESCAPE",
|
||||||
|
"1": "KC_1",
|
||||||
|
"2": "KC_2",
|
||||||
|
"3": "KC_3",
|
||||||
|
"4": "KC_4",
|
||||||
|
"5": "KC_5",
|
||||||
|
"6": "KC_6",
|
||||||
|
"7": "KC_7",
|
||||||
|
"8": "KC_8",
|
||||||
|
"9": "KC_9",
|
||||||
|
"0": "KC_0",
|
||||||
|
"-": "KC_MINUS",
|
||||||
|
"=": "KC_EQUAL",
|
||||||
|
"backspace": "KC_BSPACE",
|
||||||
|
"tab": "KC_TAB",
|
||||||
|
"q": "KC_Q",
|
||||||
|
"w": "KC_W",
|
||||||
|
"e": "KC_E",
|
||||||
|
"r": "KC_R",
|
||||||
|
"t": "KC_T",
|
||||||
|
"y": "KC_Y",
|
||||||
|
"u": "KC_U",
|
||||||
|
"i": "KC_I",
|
||||||
|
"o": "KC_O",
|
||||||
|
"p": "KC_P",
|
||||||
|
"[": "KC_LBRACKET",
|
||||||
|
"]": "KC_RBRACKET",
|
||||||
|
"enter": "KC_ENTER",
|
||||||
|
"ctrl": "KC_LCTRL",
|
||||||
|
"a": "KC_A",
|
||||||
|
"s": "KC_S",
|
||||||
|
"d": "KC_D",
|
||||||
|
"f": "KC_F",
|
||||||
|
"g": "KC_G",
|
||||||
|
"h": "KC_H",
|
||||||
|
"j": "KC_J",
|
||||||
|
"k": "KC_K",
|
||||||
|
"l": "KC_L",
|
||||||
|
";": "KC_SCOLON",
|
||||||
|
"'": "KC_QUOTE",
|
||||||
|
"`": "KC_GRAVE",
|
||||||
|
"shift": "KC_LSHIFT",
|
||||||
|
"\\": "KC_BSLASH",
|
||||||
|
"z": "KC_Z",
|
||||||
|
"x": "KC_X",
|
||||||
|
"c": "KC_C",
|
||||||
|
"v": "KC_V",
|
||||||
|
"b": "KC_B",
|
||||||
|
"n": "KC_N",
|
||||||
|
"m": "KC_M",
|
||||||
|
",": "KC_COMMA",
|
||||||
|
".": "KC_DOT",
|
||||||
|
"/": "KC_SLASH",
|
||||||
|
"alt": "KC_LALT",
|
||||||
|
"space": "KC_SPACE",
|
||||||
|
"caps lock": "KC_CAPSLOCK",
|
||||||
|
"f1": "KC_F1",
|
||||||
|
"f2": "KC_F2",
|
||||||
|
"f3": "KC_F3",
|
||||||
|
"f4": "KC_F4",
|
||||||
|
"f5": "KC_F5",
|
||||||
|
"f6": "KC_F6",
|
||||||
|
"f7": "KC_F7",
|
||||||
|
"f8": "KC_F8",
|
||||||
|
"f9": "KC_F9",
|
||||||
|
"f10": "KC_F10",
|
||||||
|
"f11": "KC_F11",
|
||||||
|
"f12": "KC_F12",
|
||||||
|
"num lock": "KC_NUMLOCK",
|
||||||
|
"scroll lock": "KC_SCROLLLOCK",
|
||||||
|
"break": "KC_PAUSE",
|
||||||
|
"home": "KC_HOME",
|
||||||
|
"up": "KC_UP",
|
||||||
|
"page up": "KC_PGUP",
|
||||||
|
"left": "KC_LEFT",
|
||||||
|
"right": "KC_RIGHT",
|
||||||
|
"end": "KC_END",
|
||||||
|
"down": "KC_DOWN",
|
||||||
|
"page down": "KC_PGDOWN",
|
||||||
|
"insert": "KC_INSERT",
|
||||||
|
"delete": "KC_DELETE",
|
||||||
|
"pause": "KC_PAUSE",
|
||||||
|
"windows": "KC_LGUI",
|
||||||
|
"menu": "KC_APPLICATION",
|
||||||
|
}
|
||||||
|
|
||||||
|
mapping = dict()
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
self.process = None
|
self.process = QProcess()
|
||||||
|
|
||||||
self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint | QtCore.Qt.X11BypassWindowManagerHint)
|
self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint | QtCore.Qt.X11BypassWindowManagerHint)
|
||||||
|
|
||||||
|
|
@ -32,7 +127,6 @@ class LinuxRecorder(QWidget):
|
||||||
center = QApplication.desktop().availableGeometry(self).center()
|
center = QApplication.desktop().availableGeometry(self).center()
|
||||||
self.move(center.x() - self.width() * 0.5, 0)
|
self.move(center.x() - self.width() * 0.5, 0)
|
||||||
|
|
||||||
self.process = QProcess()
|
|
||||||
args = [sys.executable]
|
args = [sys.executable]
|
||||||
if is_frozen():
|
if is_frozen():
|
||||||
args += sys.argv[1:]
|
args += sys.argv[1:]
|
||||||
|
|
@ -40,11 +134,43 @@ class LinuxRecorder(QWidget):
|
||||||
args += sys.argv
|
args += sys.argv
|
||||||
args += ["--linux-recorder"]
|
args += ["--linux-recorder"]
|
||||||
|
|
||||||
|
self.process.readyReadStandardOutput.connect(self.on_output)
|
||||||
self.process.start("pkexec", args, QProcess.Unbuffered | QProcess.ReadWrite)
|
self.process.start("pkexec", args, QProcess.Unbuffered | QProcess.ReadWrite)
|
||||||
|
|
||||||
def on_stop(self):
|
def on_stop(self):
|
||||||
self.process.write(b"q")
|
self.process.write(b"q")
|
||||||
|
self.process.waitForFinished()
|
||||||
|
self.process.close()
|
||||||
self.hide()
|
self.hide()
|
||||||
|
self.stopped.emit()
|
||||||
|
|
||||||
|
def action2cls(self, action):
|
||||||
|
if action == "up":
|
||||||
|
return KeyUp
|
||||||
|
elif action == "down":
|
||||||
|
return KeyDown
|
||||||
|
else:
|
||||||
|
raise RuntimeError("unexpected action={}".format(action))
|
||||||
|
|
||||||
|
def key2code(self, key):
|
||||||
|
return self.mapping.get(key, None)
|
||||||
|
|
||||||
|
def on_output(self):
|
||||||
|
if self.process.canReadLine():
|
||||||
|
line = bytes(self.process.readLine()).decode("utf-8")
|
||||||
|
action, key = line.strip().split(":")
|
||||||
|
code = self.key2code(key)
|
||||||
|
if code is not None:
|
||||||
|
self.keystroke.emit(self.action2cls(action)(code))
|
||||||
|
|
||||||
|
|
||||||
|
for linux, qmk in LinuxRecorder.mapping_qmk_id.items():
|
||||||
|
for k in KEYCODES_BASIC:
|
||||||
|
if k.qmk_id == qmk:
|
||||||
|
LinuxRecorder.mapping[linux] = k
|
||||||
|
break
|
||||||
|
if linux not in LinuxRecorder.mapping:
|
||||||
|
raise RuntimeError("Misconfigured - cannot determine QMK keycode value for {}:{} pair".format(linux, qmk))
|
||||||
|
|
||||||
|
|
||||||
class MacroRecorder(BasicEditor):
|
class MacroRecorder(BasicEditor):
|
||||||
|
|
@ -52,7 +178,11 @@ class MacroRecorder(BasicEditor):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
|
self.keystrokes = []
|
||||||
|
|
||||||
self.recorder = LinuxRecorder()
|
self.recorder = LinuxRecorder()
|
||||||
|
self.recorder.keystroke.connect(self.on_keystroke)
|
||||||
|
self.recorder.stopped.connect(self.on_stop)
|
||||||
self.recording = False
|
self.recording = False
|
||||||
|
|
||||||
btn = QPushButton("Record")
|
btn = QPushButton("Record")
|
||||||
|
|
@ -68,4 +198,11 @@ class MacroRecorder(BasicEditor):
|
||||||
return
|
return
|
||||||
|
|
||||||
def on_record_clicked(self):
|
def on_record_clicked(self):
|
||||||
|
self.keystrokes = []
|
||||||
self.recorder.start()
|
self.recorder.start()
|
||||||
|
|
||||||
|
def on_stop(self):
|
||||||
|
print(self.keystrokes)
|
||||||
|
|
||||||
|
def on_keystroke(self, keystroke):
|
||||||
|
self.keystrokes.append(keystroke)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue