Merge branch 'tap-dance' into next

main
Ilya Zhuravlev 2021-07-03 22:28:57 -04:00
commit b777d861d7
9 changed files with 420 additions and 61 deletions

View File

@ -0,0 +1,61 @@
from PyQt5.QtCore import pyqtSignal
from any_keycode_dialog import AnyKeycodeDialog
from keyboard_widget import KeyboardWidget
from kle_serial import Key
from tabbed_keycodes import TabbedKeycodes
from util import KeycodeDisplay
class KeyWidget(KeyboardWidget):
changed = pyqtSignal()
def __init__(self):
super().__init__(None)
self.padding = 1
self.keycode = 0
key = Key()
key.row = key.col = 0
key.layout_index = key.layout_option = -1
self.set_keys([key], [])
self.anykey.connect(self.on_anykey)
def mousePressEvent(self, ev):
super().mousePressEvent(ev)
if self.active_key is not None:
TabbedKeycodes.open_tray(self)
else:
TabbedKeycodes.close_tray()
def mouseReleaseEvent(self, ev):
ev.accept()
def on_keycode_changed(self, keycode):
""" Unlike set_keycode, this handles setting masked keycode inside the mask """
if self.active_mask:
if keycode > 0xFF:
return
keycode = (self.keycode & 0xFF00) | keycode
self.set_keycode(keycode)
def on_anykey(self):
if self.active_key is None:
return
dlg = AnyKeycodeDialog(self.keycode)
if dlg.exec_() and dlg.value >= 0:
self.set_keycode(dlg.value)
def set_keycode(self, kc):
if kc == self.keycode:
return
self.keycode = kc
KeycodeDisplay.display_keycode(self.widgets[0], self.keycode)
self.update()
self.changed.emit()

View File

@ -57,6 +57,12 @@ CMD_VIAL_QMK_SETTINGS_GET = 0x0A
CMD_VIAL_QMK_SETTINGS_SET = 0x0B CMD_VIAL_QMK_SETTINGS_SET = 0x0B
CMD_VIAL_QMK_SETTINGS_RESET = 0x0C CMD_VIAL_QMK_SETTINGS_RESET = 0x0C
CMD_VIAL_DYNAMIC_ENTRY_OP = 0x0D
DYNAMIC_VIAL_GET_NUMBER_OF_ENTRIES = 0x00
DYNAMIC_VIAL_TAP_DANCE_GET = 0x01
DYNAMIC_VIAL_TAP_DANCE_SET = 0x02
# 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
@ -207,6 +213,7 @@ class Keyboard:
self.reload_keymap() self.reload_keymap()
self.reload_macros() self.reload_macros()
self.reload_rgb() self.reload_rgb()
self.reload_dynamic()
def reload_layers(self): def reload_layers(self):
""" Get how many layers the keyboard has """ """ Get how many layers the keyboard has """
@ -374,6 +381,22 @@ class Keyboard:
self.backlight_effect = self.usb_send( self.backlight_effect = self.usb_send(
self.dev, struct.pack(">BB", CMD_VIA_LIGHTING_GET_VALUE, QMK_BACKLIGHT_EFFECT), retries=20)[2] self.dev, struct.pack(">BB", CMD_VIA_LIGHTING_GET_VALUE, QMK_BACKLIGHT_EFFECT), retries=20)[2]
def reload_dynamic(self):
if self.vial_protocol < 4:
self.tap_dance_count = 0
self.tap_dance_entries = []
return
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)
self.tap_dance_count = data[0]
self.tap_dance_entries = []
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,
DYNAMIC_VIAL_TAP_DANCE_GET, x), retries=20)
if data[0] != 0:
raise RuntimeError("failed retrieving tapdance entry {} from the device".format(x))
self.tap_dance_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
@ -477,6 +500,7 @@ 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
data["tap_dance"] = self.tap_dance_entries
return json.dumps(data).encode("utf-8") return json.dumps(data).encode("utf-8")
@ -508,6 +532,10 @@ class Keyboard:
self.set_layout_options(data["layout_options"]) self.set_layout_options(data["layout_options"])
self.restore_macros(data.get("macro")) self.restore_macros(data.get("macro"))
for x, e in enumerate(data.get("tap_dance", [])):
if x < self.tap_dance_count:
self.tap_dance_set(x, e)
def restore_macros(self, macros): def restore_macros(self, macros):
if not isinstance(macros, list): if not isinstance(macros, list):
return return
@ -670,6 +698,17 @@ class Keyboard:
def qmk_settings_reset(self): def qmk_settings_reset(self):
self.usb_send(self.dev, struct.pack("BB", CMD_VIA_VIAL_PREFIX, CMD_VIAL_QMK_SETTINGS_RESET)) self.usb_send(self.dev, struct.pack("BB", CMD_VIA_VIAL_PREFIX, CMD_VIAL_QMK_SETTINGS_RESET))
def tap_dance_get(self, idx):
return self.tap_dance_entries[idx]
def tap_dance_set(self, idx, entry):
if self.tap_dance_entries[idx] == entry:
return
self.tap_dance_entries[idx] = entry
serialized = struct.pack("<HHHHH", *self.tap_dance_entries[idx])
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)
class DummyKeyboard(Keyboard): class DummyKeyboard(Keyboard):

View File

@ -4,7 +4,8 @@ from PyQt5.QtGui import QPainter, QColor, QPainterPath, QTransform, QBrush, QPol
from PyQt5.QtWidgets import QWidget, QToolTip, QApplication from PyQt5.QtWidgets import QWidget, QToolTip, QApplication
from PyQt5.QtCore import Qt, QSize, QRect, QPointF, pyqtSignal, QEvent, QRectF from PyQt5.QtCore import Qt, QSize, QRect, QPointF, pyqtSignal, QEvent, QRectF
from constants import KEY_SIZE_RATIO, KEY_SPACING_RATIO, KEYBOARD_WIDGET_PADDING, KEYBOARD_WIDGET_MASK_PADDING, KEYBOARD_WIDGET_MASK_HEIGHT, KEY_ROUNDNESS from constants import KEY_SIZE_RATIO, KEY_SPACING_RATIO, KEYBOARD_WIDGET_PADDING, KEYBOARD_WIDGET_MASK_PADDING,\
KEYBOARD_WIDGET_MASK_HEIGHT, KEY_ROUNDNESS
class KeyWidget: class KeyWidget:
@ -166,6 +167,7 @@ class KeyboardWidget(QWidget):
self.enabled = True self.enabled = True
self.scale = 1 self.scale = 1
self.padding = KEYBOARD_WIDGET_PADDING
self.setMouseTracking(True) self.setMouseTracking(True)
@ -215,7 +217,7 @@ class KeyboardWidget(QWidget):
# place common widgets, that is, ones which are always displayed and require no extra transforms # place common widgets, that is, ones which are always displayed and require no extra transforms
for widget in self.common_widgets: for widget in self.common_widgets:
widget.update_position(scale_factor, -top_x + KEYBOARD_WIDGET_PADDING, -top_y + KEYBOARD_WIDGET_PADDING) widget.update_position(scale_factor, -top_x + self.padding, -top_y + self.padding)
self.widgets.append(widget) self.widgets.append(widget)
# top-left position for specific layout # top-left position for specific layout
@ -236,7 +238,7 @@ class KeyboardWidget(QWidget):
if opt == self.layout_editor.get_choice(idx): if opt == self.layout_editor.get_choice(idx):
shift_x = layout_x[idx][opt] - layout_x[idx][0] shift_x = layout_x[idx][opt] - layout_x[idx][0]
shift_y = layout_y[idx][opt] - layout_y[idx][0] shift_y = layout_y[idx][opt] - layout_y[idx][0]
widget.update_position(scale_factor, -shift_x - top_x + KEYBOARD_WIDGET_PADDING, -shift_y - top_y + KEYBOARD_WIDGET_PADDING) widget.update_position(scale_factor, -shift_x - top_x + self.padding, -shift_y - top_y + self.padding)
self.widgets.append(widget) self.widgets.append(widget)
def update_layout(self): def update_layout(self):
@ -255,8 +257,8 @@ class KeyboardWidget(QWidget):
max_w = max(max_w, p.x() * self.scale) max_w = max(max_w, p.x() * self.scale)
max_h = max(max_h, p.y() * self.scale) max_h = max(max_h, p.y() * self.scale)
self.width = max_w + 2 * KEYBOARD_WIDGET_PADDING self.width = max_w + 2 * self.padding
self.height = max_h + 2 * KEYBOARD_WIDGET_PADDING self.height = max_h + 2 * self.padding
self.update() self.update()
self.updateGeometry() self.updateGeometry()

View File

@ -524,6 +524,8 @@ KEYCODES_MEDIA = [
K(132, "KC_LSCR", "Locking\nScroll", "Locking Scroll Lock", alias=["KC_LOCKING_SCROLL"]), K(132, "KC_LSCR", "Locking\nScroll", "Locking Scroll Lock", alias=["KC_LOCKING_SCROLL"]),
] ]
KEYCODES_TAP_DANCE = []
KEYCODES_USER = [] KEYCODES_USER = []
KEYCODES_MACRO = [] KEYCODES_MACRO = []
@ -544,8 +546,8 @@ def recreate_keycodes():
KEYCODES.clear() KEYCODES.clear()
KEYCODES.extend(KEYCODES_SPECIAL + KEYCODES_BASIC + KEYCODES_SHIFTED + KEYCODES_ISO + KEYCODES_LAYERS + KEYCODES.extend(KEYCODES_SPECIAL + KEYCODES_BASIC + KEYCODES_SHIFTED + KEYCODES_ISO + KEYCODES_LAYERS +
KEYCODES_QUANTUM + KEYCODES_BACKLIGHT + KEYCODES_MEDIA + KEYCODES_MACRO + KEYCODES_USER + KEYCODES_QUANTUM + KEYCODES_BACKLIGHT + KEYCODES_MEDIA + KEYCODES_TAP_DANCE + KEYCODES_MACRO +
KEYCODES_HIDDEN) KEYCODES_USER + KEYCODES_HIDDEN)
def create_user_keycodes(): def create_user_keycodes():
@ -608,6 +610,10 @@ def recreate_keyboard_keycodes(keyboard):
lbl = "M{}".format(x) lbl = "M{}".format(x)
KEYCODES_MACRO.append(Keycode(0x5F12 + x, lbl, lbl)) KEYCODES_MACRO.append(Keycode(0x5F12 + x, lbl, lbl))
for x in range(keyboard.tap_dance_count):
lbl = "TD({})".format(x)
KEYCODES_TAP_DANCE.append(Keycode(QK_TAP_DANCE | x, lbl, lbl))
# Check if custom keycodes are defined in keyboard, and if so add them to user keycodes # Check if custom keycodes are defined in keyboard, and if so add them to user keycodes
if keyboard.custom_keycodes is not None and len(keyboard.custom_keycodes) > 0: if keyboard.custom_keycodes is not None and len(keyboard.custom_keycodes) > 0:
create_custom_user_keycodes(keyboard.custom_keycodes) create_custom_user_keycodes(keyboard.custom_keycodes)

View File

@ -1,8 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-or-later # SPDX-License-Identifier: GPL-2.0-or-later
import json import json
from PyQt5.QtGui import QPalette from PyQt5.QtWidgets import QHBoxLayout, QLabel, QVBoxLayout, QMessageBox
from PyQt5.QtWidgets import QHBoxLayout, QLabel, QVBoxLayout, QMessageBox, QApplication
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from any_keycode_dialog import AnyKeycodeDialog from any_keycode_dialog import AnyKeycodeDialog
@ -12,7 +11,7 @@ from keycodes import recreate_keyboard_keycodes, Keycode
from keymaps import KEYMAPS from keymaps import KEYMAPS
from square_button import SquareButton from square_button import SquareButton
from tabbed_keycodes import TabbedKeycodes from tabbed_keycodes import TabbedKeycodes
from util import tr from util import tr, KeycodeDisplay
from vial_device import VialKeyboard from vial_device import VialKeyboard
@ -48,8 +47,6 @@ class KeymapEditor(BasicEditor):
layout_editor.changed.connect(self.on_layout_changed) layout_editor.changed.connect(self.on_layout_changed)
self.keymap_override = KEYMAPS[0][1]
self.container.anykey.connect(self.on_any_keycode) self.container.anykey.connect(self.on_any_keycode)
self.tabbed_keycodes = TabbedKeycodes() self.tabbed_keycodes = TabbedKeycodes()
@ -60,6 +57,7 @@ class KeymapEditor(BasicEditor):
self.addWidget(self.tabbed_keycodes) self.addWidget(self.tabbed_keycodes)
self.device = None self.device = None
KeycodeDisplay.notify_keymap_override(self)
def on_container_clicked(self): def on_container_clicked(self):
""" Called when a mouse click event is bubbled up to the editor's container """ """ Called when a mouse click event is bubbled up to the editor's container """
@ -115,6 +113,7 @@ class KeymapEditor(BasicEditor):
recreate_keyboard_keycodes(self.keyboard) recreate_keyboard_keycodes(self.keyboard)
self.tabbed_keycodes.recreate_keycode_buttons() self.tabbed_keycodes.recreate_keycode_buttons()
TabbedKeycodes.tray.recreate_keycode_buttons()
self.refresh_layer_display() self.refresh_layer_display()
self.container.setEnabled(self.valid()) self.container.setEnabled(self.valid())
@ -135,11 +134,6 @@ class KeymapEditor(BasicEditor):
self.keyboard.restore_layout(data) self.keyboard.restore_layout(data)
self.refresh_layer_display() self.refresh_layer_display()
def set_keymap_override(self, override):
self.keymap_override = override
self.refresh_layer_display()
self.tabbed_keycodes.set_keymap_override(override)
def on_any_keycode(self): def on_any_keycode(self):
if self.container.active_key is None: if self.container.active_key is None:
return return
@ -148,17 +142,6 @@ class KeymapEditor(BasicEditor):
if dlg.exec_() and dlg.value >= 0: if dlg.exec_() and dlg.value >= 0:
self.on_keycode_changed(dlg.value) self.on_keycode_changed(dlg.value)
def code_is_overriden(self, code):
""" Check whether a country-specific keymap overrides a code """
key = Keycode.find_outer_keycode(code)
return key is not None and key.qmk_id in self.keymap_override
def get_label(self, code):
""" Get label for a specific keycode """
if self.code_is_overriden(code):
return self.keymap_override[Keycode.find_outer_keycode(code).qmk_id]
return Keycode.label(code)
def code_for_widget(self, widget): def code_for_widget(self, widget):
if widget.desc.row is not None: if widget.desc.row is not None:
return self.keyboard.layout[(self.current_layer, widget.desc.row, widget.desc.col)] return self.keyboard.layout[(self.current_layer, widget.desc.row, widget.desc.col)]
@ -177,20 +160,7 @@ class KeymapEditor(BasicEditor):
for widget in self.container.widgets: for widget in self.container.widgets:
code = self.code_for_widget(widget) code = self.code_for_widget(widget)
text = self.get_label(code) KeycodeDisplay.display_keycode(widget, code)
tooltip = Keycode.tooltip(code)
mask = Keycode.is_mask(code)
mask_text = self.get_label(code & 0xFF)
if mask:
text = text.split("\n")[0]
widget.masked = mask
widget.setText(text)
widget.setMaskText(mask_text)
widget.setToolTip(tooltip)
if self.code_is_overriden(code):
widget.setColor(QApplication.palette().color(QPalette.Link))
else:
widget.setColor(None)
self.container.update() self.container.update()
self.container.updateGeometry() self.container.updateGeometry()
@ -245,3 +215,6 @@ class KeymapEditor(BasicEditor):
self.refresh_layer_display() self.refresh_layer_display()
self.keyboard.set_layout_options(self.layout_editor.pack()) self.keyboard.set_layout_options(self.layout_editor.pack())
def on_keymap_override(self):
self.refresh_layer_display()

View File

@ -18,8 +18,10 @@ from layout_editor import LayoutEditor
from macro_recorder import MacroRecorder from macro_recorder import MacroRecorder
from qmk_settings import QmkSettings from qmk_settings import QmkSettings
from rgb_configurator import RGBConfigurator from rgb_configurator import RGBConfigurator
from tabbed_keycodes import TabbedKeycodes
from tap_dance import TapDance
from unlocker import Unlocker from unlocker import Unlocker
from util import tr, find_vial_devices, EXAMPLE_KEYBOARDS from util import tr, find_vial_devices, EXAMPLE_KEYBOARDS, KeycodeDisplay
from vial_device import VialKeyboard from vial_device import VialKeyboard
from matrix_test import MatrixTest from matrix_test import MatrixTest
@ -58,12 +60,14 @@ class MainWindow(QMainWindow):
self.keymap_editor = KeymapEditor(self.layout_editor) self.keymap_editor = KeymapEditor(self.layout_editor)
self.firmware_flasher = FirmwareFlasher(self) self.firmware_flasher = FirmwareFlasher(self)
self.macro_recorder = MacroRecorder() self.macro_recorder = MacroRecorder()
self.tap_dance = TapDance()
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.qmk_settings, "QMK Settings"), (self.rgb_configurator, "Lighting"), (self.tap_dance, "Tap Dance"),
(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")]
Unlocker.global_layout_editor = self.layout_editor Unlocker.global_layout_editor = self.layout_editor
@ -88,6 +92,10 @@ class MainWindow(QMainWindow):
layout.addWidget(self.tabs) layout.addWidget(self.tabs)
layout.addWidget(self.lbl_no_devices) layout.addWidget(self.lbl_no_devices)
layout.setAlignment(self.lbl_no_devices, Qt.AlignHCenter) layout.setAlignment(self.lbl_no_devices, Qt.AlignHCenter)
self.tray_keycodes = TabbedKeycodes()
self.tray_keycodes.make_tray()
layout.addWidget(self.tray_keycodes)
self.tray_keycodes.hide()
w = QWidget() w = QWidget()
w.setLayout(layout) w.setLayout(layout)
self.setCentralWidget(w) self.setCentralWidget(w)
@ -257,7 +265,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.qmk_settings, self.matrix_tester, self.rgb_configurator]: self.tap_dance, 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):
@ -327,7 +335,7 @@ class MainWindow(QMainWindow):
def change_keyboard_layout(self, index): def change_keyboard_layout(self, index):
self.settings.setValue("keymap", KEYMAPS[index][0]) self.settings.setValue("keymap", KEYMAPS[index][0])
self.keymap_editor.set_keymap_override(KEYMAPS[index][1]) KeycodeDisplay.set_keymap_override(KEYMAPS[index][1])
def get_theme(self): def get_theme(self):
return self.settings.value("theme", "Dark") return self.settings.value("theme", "Dark")
@ -340,6 +348,7 @@ class MainWindow(QMainWindow):
msg.exec_() msg.exec_()
def on_tab_changed(self, index): def on_tab_changed(self, index):
TabbedKeycodes.close_tray()
old_tab = self.current_tab old_tab = self.current_tab
new_tab = None new_tab = None
if index >= 0: if index >= 0:

View File

@ -7,10 +7,9 @@ from PyQt5.QtGui import QPalette
from constants import KEYCODE_BTN_RATIO from constants import KEYCODE_BTN_RATIO
from flowlayout import FlowLayout from flowlayout import FlowLayout
from keycodes import KEYCODES_BASIC, KEYCODES_ISO, KEYCODES_MACRO, KEYCODES_LAYERS, KEYCODES_QUANTUM, \ from keycodes import KEYCODES_BASIC, KEYCODES_ISO, KEYCODES_MACRO, KEYCODES_LAYERS, KEYCODES_QUANTUM, \
KEYCODES_BACKLIGHT, KEYCODES_MEDIA, KEYCODES_SPECIAL, KEYCODES_SHIFTED, KEYCODES_USER, Keycode KEYCODES_BACKLIGHT, KEYCODES_MEDIA, KEYCODES_SPECIAL, KEYCODES_SHIFTED, KEYCODES_USER, Keycode, KEYCODES_TAP_DANCE
from keymaps import KEYMAPS
from square_button import SquareButton from square_button import SquareButton
from util import tr from util import tr, KeycodeDisplay
class TabbedKeycodes(QTabWidget): class TabbedKeycodes(QTabWidget):
@ -21,7 +20,8 @@ class TabbedKeycodes(QTabWidget):
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__(parent) super().__init__(parent)
self.keymap_override = None self.target = None
self.is_tray = False
self.tab_basic = QScrollArea() self.tab_basic = QScrollArea()
self.tab_iso = QScrollArea() self.tab_iso = QScrollArea()
@ -29,6 +29,7 @@ class TabbedKeycodes(QTabWidget):
self.tab_quantum = QScrollArea() self.tab_quantum = QScrollArea()
self.tab_backlight = QScrollArea() self.tab_backlight = QScrollArea()
self.tab_media = QScrollArea() self.tab_media = QScrollArea()
self.tab_tap_dance = QScrollArea()
self.tab_user = QScrollArea() self.tab_user = QScrollArea()
self.tab_macro = QScrollArea() self.tab_macro = QScrollArea()
@ -41,12 +42,15 @@ class TabbedKeycodes(QTabWidget):
(self.tab_quantum, "Quantum", KEYCODES_QUANTUM), (self.tab_quantum, "Quantum", KEYCODES_QUANTUM),
(self.tab_backlight, "Backlight", KEYCODES_BACKLIGHT), (self.tab_backlight, "Backlight", KEYCODES_BACKLIGHT),
(self.tab_media, "App, Media and Mouse", KEYCODES_MEDIA), (self.tab_media, "App, Media and Mouse", KEYCODES_MEDIA),
(self.tab_tap_dance, "Tap Dance", KEYCODES_TAP_DANCE),
(self.tab_user, "User", KEYCODES_USER), (self.tab_user, "User", KEYCODES_USER),
(self.tab_macro, "Macro", KEYCODES_MACRO), (self.tab_macro, "Macro", KEYCODES_MACRO),
]: ]:
layout = FlowLayout() layout = FlowLayout()
if tab == self.tab_layers: if tab == self.tab_layers:
self.layout_layers = layout self.layout_layers = layout
elif tab == self.tab_tap_dance:
self.layout_tap_dance = layout
elif tab == self.tab_macro: elif tab == self.tab_macro:
self.layout_macro = layout self.layout_macro = layout
elif tab == self.tab_user: elif tab == self.tab_user:
@ -71,16 +75,17 @@ class TabbedKeycodes(QTabWidget):
self.addTab(tab, tr("TabbedKeycodes", label)) self.addTab(tab, tr("TabbedKeycodes", label))
self.layer_keycode_buttons = [] self.layer_keycode_buttons = []
self.tap_dance_keycode_buttons = []
self.macro_keycode_buttons = [] self.macro_keycode_buttons = []
self.user_keycode_buttons = [] self.user_keycode_buttons = []
self.set_keymap_override(KEYMAPS[0][1]) KeycodeDisplay.notify_keymap_override(self)
def create_buttons(self, layout, keycodes, wordWrap = False): def create_buttons(self, layout, keycodes, word_wrap=False):
buttons = [] buttons = []
for keycode in keycodes: for keycode in keycodes:
btn = SquareButton() btn = SquareButton()
btn.setWordWrap(wordWrap) btn.setWordWrap(word_wrap)
btn.setRelSize(KEYCODE_BTN_RATIO) btn.setRelSize(KEYCODE_BTN_RATIO)
btn.setToolTip(Keycode.tooltip(keycode.code)) btn.setToolTip(Keycode.tooltip(keycode.code))
btn.clicked.connect(lambda st, k=keycode: self.keycode_changed.emit(k.code)) btn.clicked.connect(lambda st, k=keycode: self.keycode_changed.emit(k.code))
@ -91,28 +96,63 @@ class TabbedKeycodes(QTabWidget):
return buttons return buttons
def recreate_keycode_buttons(self): def recreate_keycode_buttons(self):
for btn in self.layer_keycode_buttons + self.macro_keycode_buttons + self.user_keycode_buttons: for btn in self.layer_keycode_buttons + self.tap_dance_keycode_buttons + self.macro_keycode_buttons \
+ self.user_keycode_buttons:
self.widgets.remove(btn) self.widgets.remove(btn)
btn.hide() btn.hide()
btn.deleteLater() btn.deleteLater()
self.layer_keycode_buttons = self.create_buttons(self.layout_layers, KEYCODES_LAYERS) self.layer_keycode_buttons = self.create_buttons(self.layout_layers, KEYCODES_LAYERS)
self.tap_dance_keycode_buttons = self.create_buttons(self.layout_tap_dance, KEYCODES_TAP_DANCE)
self.macro_keycode_buttons = self.create_buttons(self.layout_macro, KEYCODES_MACRO) self.macro_keycode_buttons = self.create_buttons(self.layout_macro, KEYCODES_MACRO)
self.user_keycode_buttons = self.create_buttons(self.layout_user, KEYCODES_USER, wordWrap=True) self.user_keycode_buttons = self.create_buttons(self.layout_user, KEYCODES_USER, word_wrap=True)
self.widgets += self.layer_keycode_buttons + self.macro_keycode_buttons + self.user_keycode_buttons self.widgets += self.layer_keycode_buttons + self.tap_dance_keycode_buttons + \
self.macro_keycode_buttons + self.user_keycode_buttons
self.relabel_buttons() self.relabel_buttons()
def set_keymap_override(self, override): def on_keymap_override(self):
self.keymap_override = override
self.relabel_buttons() self.relabel_buttons()
def relabel_buttons(self): def relabel_buttons(self):
for widget in self.widgets: for widget in self.widgets:
qmk_id = widget.keycode.qmk_id qmk_id = widget.keycode.qmk_id
if qmk_id in self.keymap_override: if qmk_id in KeycodeDisplay.keymap_override:
label = self.keymap_override[qmk_id] label = KeycodeDisplay.keymap_override[qmk_id]
highlight_color = QApplication.palette().color(QPalette.Link).getRgb() highlight_color = QApplication.palette().color(QPalette.Link).getRgb()
widget.setStyleSheet("QPushButton {color: rgb"+str(highlight_color)+";}") widget.setStyleSheet("QPushButton {color: rgb"+str(highlight_color)+";}")
else: else:
label = widget.keycode.label label = widget.keycode.label
widget.setStyleSheet("QPushButton {}") widget.setStyleSheet("QPushButton {}")
widget.setText(label.replace("&", "&&")) widget.setText(label.replace("&", "&&"))
@classmethod
def set_tray(cls, tray):
cls.tray = tray
@classmethod
def open_tray(cls, target):
cls.tray.show()
if cls.tray.target is not None and cls.tray.target != target:
cls.tray.target.deselect()
cls.tray.target = target
@classmethod
def close_tray(cls):
if cls.tray.target is not None:
cls.tray.target.deselect()
cls.tray.target = None
cls.tray.hide()
def make_tray(self):
self.is_tray = True
TabbedKeycodes.set_tray(self)
self.keycode_changed.connect(self.on_tray_keycode_changed)
self.anykey.connect(self.on_tray_anykey)
def on_tray_keycode_changed(self, kc):
if self.target is not None:
self.target.on_keycode_changed(kc)
def on_tray_anykey(self):
if self.target is not None:
self.target.on_anykey()

View File

@ -0,0 +1,179 @@
# 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, QLineEdit, QHBoxLayout, \
QPushButton, QSpinBox
from key_widget import KeyWidget
from tabbed_keycodes import TabbedKeycodes
from util import tr
from vial_device import VialKeyboard
from basic_editor import BasicEditor
class TapDanceEntryUI(QObject):
key_changed = pyqtSignal()
timing_changed = pyqtSignal()
def __init__(self, idx):
super().__init__()
self.idx = idx
self.container = QGridLayout()
self.populate_container()
w = QWidget()
w.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
w.setLayout(self.container)
l = QVBoxLayout()
l.addStretch()
l.addSpacing(10)
l.addWidget(w)
l.setAlignment(w, QtCore.Qt.AlignHCenter)
l.addSpacing(10)
lbl = QLabel("Use <code>TD({})</code> to set up this action in the keymap.".format(self.idx))
l.addWidget(lbl)
l.setAlignment(lbl, QtCore.Qt.AlignHCenter)
l.addStretch()
self.w2 = QWidget()
self.w2.setLayout(l)
def populate_container(self):
self.container.addWidget(QLabel("On tap"), 0, 0)
self.kc_on_tap = KeyWidget()
self.kc_on_tap.changed.connect(self.on_key_changed)
self.container.addWidget(self.kc_on_tap, 0, 1)
self.container.addWidget(QLabel("On hold"), 1, 0)
self.kc_on_hold = KeyWidget()
self.kc_on_hold.changed.connect(self.on_key_changed)
self.container.addWidget(self.kc_on_hold, 1, 1)
self.container.addWidget(QLabel("On double tap"), 2, 0)
self.kc_on_double_tap = KeyWidget()
self.kc_on_double_tap.changed.connect(self.on_key_changed)
self.container.addWidget(self.kc_on_double_tap, 2, 1)
self.container.addWidget(QLabel("On tap + hold"), 3, 0)
self.kc_on_tap_hold = KeyWidget()
self.kc_on_tap_hold.changed.connect(self.on_key_changed)
self.container.addWidget(self.kc_on_tap_hold, 3, 1)
self.container.addWidget(QLabel("Tapping term (ms)"), 4, 0)
self.txt_tapping_term = QSpinBox()
self.txt_tapping_term.valueChanged.connect(self.on_timing_changed)
self.txt_tapping_term.setMinimum(0)
self.txt_tapping_term.setMaximum(10000)
self.container.addWidget(self.txt_tapping_term, 4, 1)
def widget(self):
return self.w2
def load(self, data):
objs = [self.kc_on_tap, self.kc_on_hold, self.kc_on_double_tap, self.kc_on_tap_hold, self.txt_tapping_term]
for o in objs:
o.blockSignals(True)
self.kc_on_tap.set_keycode(data[0])
self.kc_on_hold.set_keycode(data[1])
self.kc_on_double_tap.set_keycode(data[2])
self.kc_on_tap_hold.set_keycode(data[3])
self.txt_tapping_term.setValue(data[4])
for o in objs:
o.blockSignals(False)
def save(self):
return (
self.kc_on_tap.keycode,
self.kc_on_hold.keycode,
self.kc_on_double_tap.keycode,
self.kc_on_tap_hold.keycode,
self.txt_tapping_term.value()
)
def on_key_changed(self):
self.key_changed.emit()
def on_timing_changed(self):
self.timing_changed.emit()
class CustomTabWidget(QTabWidget):
def mouseReleaseEvent(self, ev):
TabbedKeycodes.close_tray()
class TapDance(BasicEditor):
def __init__(self):
super().__init__()
self.keyboard = None
self.tap_dance_entries = []
self.tap_dance_entries_available = []
self.tabs = CustomTabWidget()
for x in range(128):
entry = TapDanceEntryUI(x)
entry.key_changed.connect(self.on_key_changed)
entry.timing_changed.connect(self.on_timing_changed)
self.tap_dance_entries_available.append(entry)
self.addWidget(self.tabs)
buttons = QHBoxLayout()
buttons.addStretch()
self.btn_save = QPushButton(tr("TapDance", "Save"))
self.btn_save.clicked.connect(self.on_save)
btn_revert = QPushButton(tr("TapDance", "Revert"))
btn_revert.clicked.connect(self.on_revert)
buttons.addWidget(self.btn_save)
buttons.addWidget(btn_revert)
self.addLayout(buttons)
def rebuild_ui(self):
while self.tabs.count() > 0:
self.tabs.removeTab(0)
self.tap_dance_entries = self.tap_dance_entries_available[:self.keyboard.tap_dance_count]
for x, e in enumerate(self.tap_dance_entries):
self.tabs.addTab(e.widget(), str(x))
self.reload_ui()
def reload_ui(self):
for x, e in enumerate(self.tap_dance_entries):
e.load(self.keyboard.tap_dance_get(x))
self.update_modified_state()
def on_save(self):
for x, e in enumerate(self.tap_dance_entries):
self.keyboard.tap_dance_set(x, self.tap_dance_entries[x].save())
self.update_modified_state()
def on_revert(self):
self.keyboard.reload_dynamic()
self.reload_ui()
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.tap_dance_count > 0)
def on_key_changed(self):
self.on_save()
def update_modified_state(self):
""" Update indication of which tabs are modified, and keep Save button enabled only if it's needed """
has_changes = False
for x, e in enumerate(self.tap_dance_entries):
if self.tap_dance_entries[x].save() != self.keyboard.tap_dance_get(x):
has_changes = True
self.tabs.setTabText(x, "{}*".format(x))
else:
self.tabs.setTabText(x, str(x))
self.btn_save.setEnabled(has_changes)
def on_timing_changed(self):
self.update_modified_state()

View File

@ -5,9 +5,12 @@ import time
from logging.handlers import RotatingFileHandler from logging.handlers import RotatingFileHandler
from PyQt5.QtCore import QCoreApplication, QStandardPaths from PyQt5.QtCore import QCoreApplication, QStandardPaths
from PyQt5.QtGui import QPalette
from PyQt5.QtWidgets import QApplication
from hidproxy import hid from hidproxy import hid
from keycodes import Keycode
from keymaps import KEYMAPS
tr = QCoreApplication.translate tr = QCoreApplication.translate
@ -147,3 +150,50 @@ def init_logger():
handler = RotatingFileHandler(path, maxBytes=5 * 1024 * 1024, backupCount=5) handler = RotatingFileHandler(path, maxBytes=5 * 1024 * 1024, backupCount=5)
handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(module)s:%(lineno)d - %(message)s")) handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(module)s:%(lineno)d - %(message)s"))
logging.getLogger().addHandler(handler) logging.getLogger().addHandler(handler)
class KeycodeDisplay:
keymap_override = KEYMAPS[0][1]
clients = []
@classmethod
def get_label(cls, code):
""" Get label for a specific keycode """
if cls.code_is_overriden(code):
return cls.keymap_override[Keycode.find_outer_keycode(code).qmk_id]
return Keycode.label(code)
@classmethod
def code_is_overriden(cls, code):
""" Check whether a country-specific keymap overrides a code """
key = Keycode.find_outer_keycode(code)
return key is not None and key.qmk_id in cls.keymap_override
@classmethod
def display_keycode(cls, widget, code):
text = cls.get_label(code)
tooltip = Keycode.tooltip(code)
mask = Keycode.is_mask(code)
mask_text = cls.get_label(code & 0xFF)
if mask:
text = text.split("\n")[0]
widget.masked = mask
widget.setText(text)
widget.setMaskText(mask_text)
widget.setToolTip(tooltip)
if cls.code_is_overriden(code):
widget.setColor(QApplication.palette().color(QPalette.Link))
else:
widget.setColor(None)
@classmethod
def set_keymap_override(cls, override):
cls.keymap_override = override
for client in cls.clients:
client.on_keymap_override()
@classmethod
def notify_keymap_override(cls, client):
cls.clients.append(client)
client.on_keymap_override()