From ed4673dfb896e379785c652a65a2d03d9db1efab Mon Sep 17 00:00:00 2001 From: Ilya Zhuravlev Date: Wed, 14 Oct 2020 15:16:14 -0400 Subject: [PATCH] Initial communication with keyboard --- g60.json | 130 --------------- plain60.json | 301 ----------------------------------- src/main/python/enumerate.py | 7 - src/main/python/hid.py | 11 -- src/main/python/main.py | 76 ++++++++- src/main/python/util.py | 39 +++++ 6 files changed, 109 insertions(+), 455 deletions(-) delete mode 100644 g60.json delete mode 100644 plain60.json delete mode 100644 src/main/python/enumerate.py delete mode 100644 src/main/python/hid.py diff --git a/g60.json b/g60.json deleted file mode 100644 index b6c4cb9..0000000 --- a/g60.json +++ /dev/null @@ -1,130 +0,0 @@ -{ - "name": "g60", - "vendorId": "0xFEED", - "productId": "0x6464", - "lighting": "none", - "matrix": { - "rows": 5, - "cols": 15 - }, - "layouts": { - "keymap": [ - [ - "0,0", - "0,1", - "0,2", - "0,3", - "0,4", - "0,5", - "0,6", - "0,7", - "0,8", - "0,9", - "0,10", - "0,11", - "0,12", - "0,13", - "0,14" - ], - [ - { - "w": 1.5 - }, - "1,0", - "1,2", - "1,3", - "1,4", - "1,5", - "1,6", - "1,7", - "1,8", - "1,9", - "1,10", - "1,11", - "1,12", - "1,13", - { - "w": 1.5 - }, - "1,14" - ], - [ - { - "w": 1.75 - }, - "2,0", - "2,2", - "2,3", - "2,4", - "2,5", - "2,6", - "2,7", - "2,8", - "2,9", - "2,10", - "2,11", - "2,12", - { - "w": 2.25 - }, - "2,13" - ], - [ - { - "w": 1.25 - }, - "3,0", - "3,1", - "3,2", - "3,3", - "3,4", - "3,5", - "3,6", - "3,7", - "3,8", - "3,9", - "3,10", - "3,11", - { - "w": 1.75 - }, - "3,13", - "3,14" - ], - [ - { - "w": 1.25 - }, - "4,0", - { - "w": 1.25 - }, - "4,1", - { - "w": 1.25 - }, - "4,3", - { - "w": 6.25 - }, - "4,6", - { - "w": 1.25 - }, - "4,10", - { - "w": 1.25 - }, - "4,11", - { - "w": 1.25 - }, - "4,13", - { - "w": 1.25 - }, - "4,14" - ] - ] - } -} \ No newline at end of file diff --git a/plain60.json b/plain60.json deleted file mode 100644 index ba3db32..0000000 --- a/plain60.json +++ /dev/null @@ -1,301 +0,0 @@ -{ - "name": "plain60", - "vendorId": "0x4705", - "productId": "0x0160", - "lighting": "none", - "matrix": { - "rows": 5, - "cols": 15 - }, - "layouts": { - "labels": [ - [ - "Enter Key", - "ANSI", - "ISO" - ], - "Split Left Shift", - "Split Right Shift", - "Split Backspace", - [ - "Bottom row", - "Default", - "Tsangan", - "WKL", - "HHKB" - ] - ], - "keymap": [ - [ - { - "x": 2.75, - "c": "#777777" - }, - "0,0", - { - "c": "#cccccc" - }, - "0,1", - "0,2", - "0,3", - "0,4", - "0,5", - "0,6", - "0,7", - "0,8", - "0,9", - "0,10", - "0,11", - "0,12", - { - "c": "#aaaaaa", - "w": 2 - }, - "0,14\n\n\n3,0", - { - "x": 0.5, - "c": "#cccccc" - }, - "0,13\n\n\n3,1", - "0,14\n\n\n3,1" - ], - [ - { - "x": 2.75, - "c": "#aaaaaa", - "w": 1.5 - }, - "1,0", - { - "c": "#cccccc" - }, - "1,1", - "1,2", - "1,3", - "1,4", - "1,5", - "1,6", - "1,7", - "1,8", - "1,9", - "1,10", - "1,11", - "1,12", - { - "w": 1.5 - }, - "1,13\n\n\n0,0", - { - "x": 1.5, - "c": "#777777", - "w": 1.25, - "h": 2, - "w2": 1.5, - "h2": 1, - "x2": -0.25 - }, - "2,13\n\n\n0,1" - ], - [ - { - "x": 2.75, - "c": "#aaaaaa", - "w": 1.75 - }, - "2,0", - { - "c": "#cccccc" - }, - "2,1", - "2,2", - "2,3", - "2,4", - "2,5", - "2,6", - "2,7", - "2,8", - "2,09", - "2,10", - "2,11", - { - "c": "#777777", - "w": 2.25 - }, - "2,13\n\n\n0,0", - { - "x": 0.5, - "c": "#cccccc" - }, - "2,12\n\n\n0,1" - ], - [ - { - "c": "#aaaaaa", - "w": 1.25 - }, - "3,0\n\n\n1,1", - { - "c": "#cccccc" - }, - "3,1\n\n\n1,1", - { - "x": 0.5, - "c": "#aaaaaa", - "w": 2.25 - }, - "3,0\n\n\n1,0", - { - "c": "#cccccc" - }, - "3,2", - "3,3", - "3,4", - "3,5", - "3,6", - "3,7", - "3,8", - "3,9", - "3,10", - "3,11", - { - "c": "#aaaaaa", - "w": 2.75 - }, - "3,12\n\n\n2,0", - { - "x": 0.5, - "w": 1.75 - }, - "3,12\n\n\n2,1", - "3,13\n\n\n2,1" - ], - [ - { - "x": 2.75, - "w": 1.25 - }, - "4,0\n\n\n4,0", - { - "w": 1.25 - }, - "4,1\n\n\n4,0", - { - "w": 1.25 - }, - "4,2\n\n\n4,0", - { - "c": "#cccccc", - "w": 6.25 - }, - "4,6\n\n\n4,0", - { - "c": "#aaaaaa", - "w": 1.25 - }, - "4,10\n\n\n4,0", - { - "w": 1.25 - }, - "4,11\n\n\n4,0", - { - "w": 1.25 - }, - "4,12\n\n\n4,0", - { - "w": 1.25 - }, - "4,13\n\n\n4,0" - ], - [ - { - "y": 0.25, - "x": 2.75, - "w": 1.5 - }, - "4,0\n\n\n4,1", - "4,1\n\n\n4,1", - { - "w": 1.5 - }, - "4,2\n\n\n4,1", - { - "c": "#cccccc", - "w": 7 - }, - "4,6\n\n\n4,1", - { - "c": "#aaaaaa", - "w": 1.5 - }, - "4,11\n\n\n4,1", - "4,12\n\n\n4,1", - { - "w": 1.5 - }, - "4,13\n\n\n4,1" - ], - [ - { - "x": 2.75, - "w": 1.5 - }, - "4,0\n\n\n4,2", - { - "d": true - }, - "\n\n\n4,2", - { - "w": 1.5 - }, - "4,2\n\n\n4,2", - { - "c": "#cccccc", - "w": 7 - }, - "4,6\n\n\n4,2", - { - "c": "#aaaaaa", - "w": 1.5 - }, - "4,11\n\n\n4,2", - { - "d": true - }, - "\n\n\n4,2", - { - "w": 1.5 - }, - "4,13\n\n\n4,2" - ], - [ - { - "x": 2.75, - "w": 1.5, - "d": true - }, - "\n\n\n4,3", - "4,1\n\n\n4,3", - { - "w": 1.5 - }, - "4,2\n\n\n4,3", - { - "c": "#cccccc", - "w": 7 - }, - "4,6\n\n\n4,3", - { - "c": "#aaaaaa", - "w": 1.5 - }, - "4,11\n\n\n4,3", - "4,12\n\n\n4,3", - { - "w": 1.5, - "d": true - }, - "\n\n\n4,3" - ] - ] - } -} \ No newline at end of file diff --git a/src/main/python/enumerate.py b/src/main/python/enumerate.py deleted file mode 100644 index 2a98738..0000000 --- a/src/main/python/enumerate.py +++ /dev/null @@ -1,7 +0,0 @@ -import hid - -VIAL_SERIAL_NUMBER_MAGIC = "vial:f64c2b3c" - -def find_vial_keyboards(): - for dev in hid.enumerate(): - print(dev) diff --git a/src/main/python/hid.py b/src/main/python/hid.py deleted file mode 100644 index a1c2ca4..0000000 --- a/src/main/python/hid.py +++ /dev/null @@ -1,11 +0,0 @@ -REPORT_LEN = 32 - -def hid_send(dev, msg): - if len(report) > REPORT_LEN: - raise RuntimeError("report must be less than 64 bytes") - msg += b"\x00" * (REPORT_LEN - len(msg)) - - # add 00 at start for hidapi report id - dev.write(b"\x00" + report) - - return dev.read(REPORT_LEN) diff --git a/src/main/python/main.py b/src/main/python/main.py index f07b6d0..4cd8813 100644 --- a/src/main/python/main.py +++ b/src/main/python/main.py @@ -1,12 +1,14 @@ from fbs_runtime.application_context.PyQt5 import ApplicationContext from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QWidget, QTabWidget, QVBoxLayout, QPushButton, QLabel +from PyQt5.QtWidgets import QWidget, QTabWidget, QVBoxLayout, QPushButton, QLabel, QComboBox, QToolButton, QHBoxLayout import sys import json +import struct +import lzma from flowlayout import FlowLayout -from util import tr +from util import tr, find_vial_keyboards, open_device, hid_send, MSG_LEN from kle_serial import Serial as KleSerial class TabbedKeycodes(QTabWidget): @@ -40,9 +42,14 @@ class KeyboardContainer(QWidget): def __init__(self, parent=None): super().__init__(parent) + self.keys = [] + + def rebuild(self, data): + for key in self.keys: + key.deleteLater() + self.keys = [] + serial = KleSerial() - data = open("g60.json", "r").read() - data = json.loads(data) kb = serial.deserialize(data["layouts"]["keymap"]) max_w = max_h = 0 @@ -60,11 +67,14 @@ class KeyboardContainer(QWidget): widget.setFixedSize(w, h) widget.move(x, y) - print("{} {}x{}+{}x{}".format(key.labels, key.x, key.y, key.width, key.height)) + widget.show() + # print("{} {}x{}+{}x{}".format(key.labels, key.x, key.y, key.width, key.height)) max_w = max(max_w, x + w) max_h = max(max_h, y + h) - + + self.keys.append(widget) + self.setFixedSize(max_w, max_h) @@ -72,17 +82,71 @@ class MainWindow(QWidget): def __init__(self): super().__init__() + self.device = None + self.devices = [] self.keyboard_container = KeyboardContainer() self.tabbed_keycodes = TabbedKeycodes() + 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) + layout = QVBoxLayout() + layout.addLayout(layout_combobox) layout.addWidget(self.keyboard_container) layout.setAlignment(self.keyboard_container, Qt.AlignHCenter) layout.addWidget(self.tabbed_keycodes) self.setLayout(layout) + # make sure initial state is valid + self.on_click_refresh() + self.on_device_selected() + + 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 + idx = self.combobox_devices.currentIndex() + if idx >= 0: + self.device = open_device(self.devices[idx]) + 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(" 0: + data = hid_send(self.device, b"\xFE\x02" + struct.pack(" MSG_LEN: + raise RuntimeError("message must be less than 32 bytes") + msg += b"\x00" * (MSG_LEN - len(msg)) + + # add 00 at start for hidapi report id + dev.write(b"\x00" + msg) + + return bytes(dev.read(MSG_LEN)) + +def is_rawhid(dev): + # TODO: this is only broken on linux, other platforms should be able to check usage_page + return dev["interface_number"] == 1 + +def find_vial_keyboards(): + filtered = [] + for dev in hid.enumerate(): + if VIAL_SERIAL_NUMBER_MAGIC in dev["serial_number"] and is_rawhid(dev): + filtered.append(dev) + return filtered + +def open_device(desc): + # TODO: error handling here + dev = hid.device() + dev.open_path(desc["path"]) + return dev