Initial communication with keyboard

main
Ilya Zhuravlev 2020-10-14 15:16:14 -04:00
parent 8f1123ee77
commit ed4673dfb8
6 changed files with 109 additions and 455 deletions

130
g60.json
View File

@ -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"
]
]
}
}

View File

@ -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"
]
]
}
}

View File

@ -1,7 +0,0 @@
import hid
VIAL_SERIAL_NUMBER_MAGIC = "vial:f64c2b3c"
def find_vial_keyboards():
for dev in hid.enumerate():
print(dev)

View File

@ -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)

View File

@ -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("<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))
self.keyboard_container.rebuild(payload)
if __name__ == '__main__':
appctxt = ApplicationContext() # 1. Instantiate ApplicationContext

View File

@ -1,3 +1,42 @@
from PyQt5.QtCore import QCoreApplication
import sys
if sys.platform.startswith("linux"):
import hidraw as hid
else:
import hid
tr = QCoreApplication.translate
VIAL_SERIAL_NUMBER_MAGIC = "vial:f64c2b3c"
MSG_LEN = 32
def hid_send(dev, msg):
if len(msg) > 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