vial/src/main/python/main.py

302 lines
10 KiB
Python
Raw Normal View History

2020-10-14 22:21:33 -04:00
# SPDX-License-Identifier: GPL-2.0-or-later
2020-10-14 02:39:59 -04:00
from fbs_runtime.application_context.PyQt5 import ApplicationContext
from PyQt5.QtCore import Qt, pyqtSignal
2020-10-14 16:15:32 -04:00
from PyQt5.QtWidgets import QWidget, QTabWidget, QVBoxLayout, QPushButton, QLabel, QComboBox, QToolButton, QHBoxLayout, QSizePolicy
2020-10-14 02:39:59 -04:00
import sys
2020-10-14 12:56:25 -04:00
import json
2020-10-14 15:16:14 -04:00
import struct
import lzma
2020-10-14 20:27:24 -04:00
from collections import defaultdict
2020-10-14 02:39:59 -04:00
from flowlayout import FlowLayout
2020-10-14 15:16:14 -04:00
from util import tr, find_vial_keyboards, open_device, hid_send, MSG_LEN
2020-10-14 12:56:25 -04:00
from kle_serial import Serial as KleSerial
2020-10-14 20:27:24 -04:00
from clickable_label import ClickableLabel
from keycodes import keycode_label, keycode_tooltip, recreate_layer_keycodes, KEYCODES_BASIC, KEYCODES_ISO, KEYCODES_MACRO, KEYCODES_LAYERS, KEYCODES_SPECIAL
2020-10-14 20:27:24 -04:00
2020-10-14 02:39:59 -04:00
class TabbedKeycodes(QTabWidget):
2020-10-14 21:12:53 -04:00
def __init__(self, kb, parent=None):
2020-10-14 02:39:59 -04:00
super().__init__(parent)
2020-10-16 05:06:41 -04:00
2020-10-14 02:39:59 -04:00
self.tab_basic = QWidget()
2020-10-16 05:06:41 -04:00
self.tab_iso = QWidget()
self.tab_macro = QWidget()
self.tab_layers = QWidget()
self.tab_special = QWidget()
for (tab, label, keycodes) in [
(self.tab_basic, "Basic", KEYCODES_BASIC),
(self.tab_iso, "ISO/JIS", KEYCODES_ISO),
(self.tab_macro, "Macro", KEYCODES_MACRO),
(self.tab_layers, "Layers", KEYCODES_LAYERS),
(self.tab_special, "Special", KEYCODES_SPECIAL),
]:
layout = FlowLayout()
buttons = self.create_buttons(layout, keycodes)
tab.setLayout(layout)
2020-10-16 05:06:41 -04:00
self.addTab(tab, tr("TabbedKeycodes", label))
self.layer_keycode_buttons = []
def create_buttons(self, layout, keycodes):
buttons = []
2020-10-14 02:39:59 -04:00
2020-10-16 05:06:41 -04:00
for keycode in keycodes:
2020-10-14 21:12:53 -04:00
btn = QPushButton(keycode.label)
2020-10-14 02:39:59 -04:00
btn.setFixedSize(50, 50)
2020-10-16 05:06:41 -04:00
btn.setToolTip(keycode_tooltip(keycode.code))
btn.clicked.connect(lambda st, k=keycode: kb.set_key(k.code))
2020-10-14 02:39:59 -04:00
layout.addWidget(btn)
buttons.append(btn)
return buttons
2020-10-14 02:39:59 -04:00
def recreate_layer_keycode_buttons(self):
for btn in self.layer_keycode_buttons:
btn.deleteLater()
self.layer_keycode_buttons = self.create_buttons(self.tab_layers.layout(), KEYCODES_LAYERS)
2020-10-14 02:39:59 -04:00
KEY_WIDTH = 40
KEY_HEIGHT = KEY_WIDTH
2020-10-14 12:56:25 -04:00
KEY_SPACING = 4
2020-10-14 02:39:59 -04:00
class KeyboardContainer(QWidget):
number_layers_changed = pyqtSignal()
2020-10-14 02:39:59 -04:00
def __init__(self, parent=None):
super().__init__(parent)
2020-10-14 16:15:32 -04:00
self.layout_layers = QHBoxLayout()
layer_label = QLabel(tr("KeyboardContainer", "Layer"))
layout_labels_container = QHBoxLayout()
layout_labels_container.addWidget(layer_label)
layout_labels_container.addLayout(self.layout_layers)
layout_labels_container.addStretch()
# contains the actual keyboard
self.container = QWidget()
layout = QVBoxLayout()
layout.addLayout(layout_labels_container)
layout.addWidget(self.container)
layout.setAlignment(self.container, Qt.AlignHCenter)
self.setLayout(layout)
2020-10-14 15:16:14 -04:00
2020-10-14 16:15:32 -04:00
self.keys = []
self.layer_labels = []
2020-10-14 20:27:24 -04:00
self.rowcol = defaultdict(list)
self.layout = dict()
2020-10-14 21:12:53 -04:00
self.selected_key = None
self.selected_row = -1
self.selected_col = -1
2020-10-14 20:27:24 -04:00
def rebuild_layers(self, dev):
self.layers = hid_send(dev, b"\x11")[1]
self.number_layers_changed.emit()
2020-10-14 16:15:32 -04:00
for label in self.layer_labels:
label.deleteLater()
self.layer_labels = []
# create new layer labels
for x in range(self.layers):
2020-10-14 20:27:24 -04:00
label = ClickableLabel(str(x))
2020-10-14 16:15:32 -04:00
label.setAlignment(Qt.AlignCenter)
2020-10-14 20:27:24 -04:00
label.clicked.connect(lambda idx=x: self.switch_layer(idx))
2020-10-14 16:15:32 -04:00
self.layout_layers.addWidget(label)
self.layer_labels.append(label)
2020-10-14 20:27:24 -04:00
def rebuild_layout(self, dev):
""" Load current key mapping from the keyboard """
for layer in range(self.layers):
for row, col in self.rowcol.keys():
data = hid_send(dev, b"\x04" + struct.pack("<BBB", layer, row, col))
keycode = struct.unpack(">H", data[4:6])[0]
self.layout[(layer, row, col)] = keycode
2020-10-14 16:15:32 -04:00
def rebuild(self, dev, data):
# delete current layout
2020-10-14 15:16:14 -04:00
for key in self.keys:
key.deleteLater()
self.keys = []
2020-10-14 16:15:32 -04:00
# get number of layers
2020-10-14 20:27:24 -04:00
self.rebuild_layers(dev)
# prepare for fetching keymap
self.rowcol = defaultdict(list)
2020-10-14 16:15:32 -04:00
2020-10-14 12:56:25 -04:00
serial = KleSerial()
kb = serial.deserialize(data["layouts"]["keymap"])
2020-10-14 02:39:59 -04:00
2020-10-14 12:56:25 -04:00
max_w = max_h = 0
for key in kb.keys:
2020-10-14 21:12:53 -04:00
widget = ClickableLabel()
widget.clicked.connect(lambda w=widget: self.select_key(w))
2020-10-14 16:15:32 -04:00
if key.labels[0] and "," in key.labels[0]:
row, col = key.labels[0].split(",")
row, col = int(row), int(col)
2020-10-14 20:27:24 -04:00
self.rowcol[(row, col)].append(widget)
2020-10-14 16:15:32 -04:00
widget.setParent(self.container)
2020-10-14 02:39:59 -04:00
widget.setAlignment(Qt.AlignCenter)
2020-10-14 12:56:25 -04:00
x = (KEY_WIDTH + KEY_SPACING) * key.x
y = (KEY_HEIGHT + KEY_SPACING) * key.y
w = (KEY_WIDTH + KEY_SPACING) * key.width - KEY_SPACING
h = (KEY_HEIGHT + KEY_SPACING) * key.height - KEY_SPACING
widget.setFixedSize(w, h)
widget.move(x, y)
2020-10-14 15:16:14 -04:00
widget.show()
2020-10-14 16:15:32 -04:00
2020-10-14 12:56:25 -04:00
max_w = max(max_w, x + w)
max_h = max(max_h, y + h)
2020-10-14 15:16:14 -04:00
self.keys.append(widget)
2020-10-14 16:15:32 -04:00
self.container.setFixedSize(max_w, max_h)
2020-10-14 20:27:24 -04:00
self.rebuild_layout(dev)
self.current_layer = 0
self.refresh_layer_display()
def refresh_layer_display(self):
""" Refresh text on key widgets to display data corresponding to current layer """
for label in self.layer_labels:
label.setStyleSheet("border: 1px solid black; padding: 5px")
self.layer_labels[self.current_layer].setStyleSheet("border: 1px solid black; padding: 5px; background-color: black; color: white")
for (row, col), widgets in self.rowcol.items():
2020-10-16 05:06:41 -04:00
code = self.layout[(self.current_layer, row, col)]
text = keycode_label(code)
tooltip = keycode_tooltip(code)
2020-10-14 20:27:24 -04:00
for widget in widgets:
2020-10-14 21:12:53 -04:00
widget.setStyleSheet('background-color:white; border: 1px solid black')
if widget == self.selected_key:
widget.setStyleSheet('background-color:black; color: white; border: 1px solid black')
2020-10-14 20:27:24 -04:00
widget.setText(text)
2020-10-16 05:06:41 -04:00
widget.setToolTip(tooltip)
2020-10-14 20:27:24 -04:00
def switch_layer(self, idx):
self.current_layer = idx
2020-10-14 21:12:53 -04:00
self.selected_key = None
self.selected_row = -1
self.selected_col = -1
self.refresh_layer_display()
def set_key(self, keycode):
""" Change currently selected key to provided keycode """
if self.selected_row >= 0 and self.selected_col >= 0:
hid_send(self.dev, struct.pack(">BBBBH", 5, self.current_layer, self.selected_row, self.selected_col, keycode))
self.layout[(self.current_layer, self.selected_row, self.selected_col)] = keycode
self.refresh_layer_display()
def select_key(self, widget):
""" Change which key is currently selected """
self.selected_key = widget
for (row, col), widgets in self.rowcol.items():
if widget in widgets:
self.selected_row = row
self.selected_col = col
break
2020-10-14 20:27:24 -04:00
self.refresh_layer_display()
2020-10-14 02:39:59 -04:00
class MainWindow(QWidget):
def __init__(self):
super().__init__()
2020-10-14 15:16:14 -04:00
self.device = None
self.devices = []
2020-10-14 02:39:59 -04:00
self.keyboard_container = KeyboardContainer()
self.keyboard_container.number_layers_changed.connect(self.on_number_layers_changed)
2020-10-14 02:39:59 -04:00
2020-10-14 21:12:53 -04:00
self.tabbed_keycodes = TabbedKeycodes(self.keyboard_container)
2020-10-14 02:39:59 -04:00
2020-10-14 15:16:14 -04:00
self.combobox_devices = QComboBox()
self.combobox_devices.currentIndexChanged.connect(self.on_device_selected)
btn_refresh_devices = QToolButton()
btn_refresh_devices.setToolButtonStyle(Qt.ToolButtonTextOnly)
btn_refresh_devices.setText(tr("MainWindow", "Refresh"))
btn_refresh_devices.clicked.connect(self.on_click_refresh)
layout_combobox = QHBoxLayout()
layout_combobox.addWidget(self.combobox_devices)
layout_combobox.addWidget(btn_refresh_devices)
2020-10-14 02:39:59 -04:00
layout = QVBoxLayout()
2020-10-14 15:16:14 -04:00
layout.addLayout(layout_combobox)
2020-10-14 02:39:59 -04:00
layout.addWidget(self.keyboard_container)
layout.addWidget(self.tabbed_keycodes)
self.setLayout(layout)
2020-10-14 15:16:14 -04:00
# make sure initial state is valid
self.on_click_refresh()
def on_click_refresh(self):
self.devices = find_vial_keyboards()
self.combobox_devices.clear()
for dev in self.devices:
self.combobox_devices.addItem("{} {}".format(dev["manufacturer_string"], dev["product_string"]))
def on_device_selected(self):
self.device = None
2020-10-14 21:12:53 -04:00
self.keyboard_container.dev = None
2020-10-14 15:16:14 -04:00
idx = self.combobox_devices.currentIndex()
if idx >= 0:
self.device = open_device(self.devices[idx])
2020-10-14 21:12:53 -04:00
self.keyboard_container.dev = self.device
2020-10-14 15:16:14 -04:00
self.reload_layout()
def reload_layout(self):
""" Requests layout data from the current device """
# get the size
data = hid_send(self.device, b"\xFE\x01")
sz = struct.unpack("<I", data[0:4])[0]
# get the payload
payload = b""
block = 0
while sz > 0:
data = hid_send(self.device, b"\xFE\x02" + struct.pack("<I", block))
if sz < MSG_LEN:
data = data[:sz]
payload += data
block += 1
sz -= MSG_LEN
payload = json.loads(lzma.decompress(payload))
2020-10-14 16:15:32 -04:00
self.keyboard_container.rebuild(self.device, payload)
2020-10-14 15:16:14 -04:00
def on_number_layers_changed(self):
recreate_layer_keycodes(self.keyboard_container.layers)
self.tabbed_keycodes.recreate_layer_keycode_buttons()
2020-10-14 02:39:59 -04:00
if __name__ == '__main__':
appctxt = ApplicationContext() # 1. Instantiate ApplicationContext
window = MainWindow()
window.resize(1024, 768)
window.show()
exit_code = appctxt.app.exec_() # 2. Invoke appctxt.app.exec_()
sys.exit(exit_code)