add keyboard rendering

main
Ilya Zhuravlev 2020-10-14 12:56:25 -04:00
parent cbe3a2dd9e
commit 8f1123ee77
7 changed files with 685 additions and 18 deletions

130
g60.json 100644
View File

@ -0,0 +1,130 @@
{
"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"
]
]
}
}

301
plain60.json 100644
View File

@ -0,0 +1,301 @@
{
"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

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

View File

@ -0,0 +1,11 @@
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

@ -0,0 +1,202 @@
# Based on https://github.com/ijprest/kle-serial
import json
from copy import copy
class KeyDefaults:
def __init__(self):
self.textColor = "#000000"
self.textSize = 3
class Key:
def __init__(self):
self.color = "#cccccc"
self.labels = []
self.textColor = [None] * 12
self.textSize = []
self.default = KeyDefaults()
self.x = 0
self.y = 0
self.width = 1
self.height = 1
self.x2 = 0
self.y2 = 0
self.width2 = 1
self.height2 = 1
self.rotation_x = 0
self.rotation_y = 0
self.rotation_angle = 0
self.decal = False
self.ghost = False
self.stepped = False
self.nub = False
self.profile = ""
self.sm = ""
self.sb = ""
self.st = ""
class KeyboardMetadata:
def __init__(self):
self.author = ""
self.backcolor = "#eeeeee"
self.background = None
self.name = ""
self.notes = ""
self.radii = ""
self.switchBrand = ""
self.switchMount = ""
self.switchType = ""
class Keyboard:
def __init__(self):
self.meta = KeyboardMetadata()
self.keys = []
class Serial:
labelMap = [
# 0 1 2 3 4 5 6 7 8 9 10 11 # align flags
[ 0, 6, 2, 8, 9,11, 3, 5, 1, 4, 7,10], # 0 = no centering
[ 1, 7,-1,-1, 9,11, 4,-1,-1,-1,-1,10], # 1 = center x
[ 3,-1, 5,-1, 9,11,-1,-1, 4,-1,-1,10], # 2 = center y
[ 4,-1,-1,-1, 9,11,-1,-1,-1,-1,-1,10], # 3 = center x & y
[ 0, 6, 2, 8,10,-1, 3, 5, 1, 4, 7,-1], # 4 = center front (default)
[ 1, 7,-1,-1,10,-1, 4,-1,-1,-1,-1,-1], # 5 = center front & x
[ 3,-1, 5,-1,10,-1,-1,-1, 4,-1,-1,-1], # 6 = center front & y
[ 4,-1,-1,-1,10,-1,-1,-1,-1,-1,-1,-1], # 7 = center front & x & y
]
def reorderLabelsIn(self, labels, align):
ret = [None] * 12
for i in range(len(labels)):
if labels[i]:
ret[self.labelMap[align][i]] = labels[i]
return ret
def deserializeError(self, msg, data):
raise RuntimeError("Error: {} {}".format(msg, data))
def deserialize(self, rows):
current = Key()
kbd = Keyboard()
align = 4
for r in range(len(rows)):
if isinstance(rows[r], list):
for k in range(len(rows[r])):
item = rows[r][k]
if isinstance(item, str):
newKey = copy(current)
# Calculate some generated values
newKey.width2 = current.width if newKey.width2 == 0 else current.width2
newKey.height2 = current.height if newKey.height2 == 0 else current.height2
newKey.labels = self.reorderLabelsIn(item.split("\n"), align)
newKey.textSize = self.reorderLabelsIn(newKey.textSize, align)
# Clean up the data
for i in range(12):
if newKey.labels[i] is None:
newKey.textSize[i] = newKey.textColor[i] = None
if newKey.textSize[i] == newKey.default.textSize:
newKey.textSize[i] = None
if newKey.textColor[i] == newKey.default.textColor:
newKey.textColor[i] = None
# Add the key!
kbd.keys.append(newKey)
# Set up for the next key
current.x += current.width
current.width = current.height = 1
current.x2 = current.y2 = current.width2 = current.height2 = 0
current.nub = current.stepped = current.decal = False
else:
if k != 0 and ("r" in item or "rx" in item or "ry" in item):
self.deserializeError("rotation can only be specified on the first key in a row", item)
if "r" in item:
current.rotation_angle = item["r"]
if "rx" in item:
current.rotation_x = item["rx"]
if "ry" in item:
current.rotation_y = item["ry"]
if "a" in item:
align = item["a"]
if "f" in item:
current.default.textSize = item["f"]
current.textSize = []
if "f2" in item:
for i in range(1, 12):
current.textSize[i] = item["f2"]
if "fa" in item:
current.textSize = item["fa"]
if "p" in item:
current.profile = item["p"]
if "c" in item:
current.color = item["c"]
if "t" in item:
split = item["t"].split("\n")
if split[0] != "":
current.default.textColor = split[0]
current.textColor = self.reorderLabelsIn(split, align)
if "x" in item:
current.x += item["x"]
if "y" in item:
current.y += item["y"]
if "w" in item:
current.width = current.width2 = item["w"]
if "h" in item:
current.height = current.height2 = item["h"]
if "x2" in item:
current.x2 = item["x2"]
if "y2" in item:
current.y2 = item["y2"]
if "w2" in item:
current.width2 = item["w2"]
if "h2" in item:
current.height2 = item["h2"]
if "n" in item:
current.nub = item["n"]
if "l" in item:
current.stepped = item["l"]
if "d" in item:
current.decal = item["d"]
if "g" in item and item["g"]:
current.ghost = item.g
if "sm" in item:
current.sm = item["sm"]
if "sb" in item:
current.sb = item["sb"]
if "st" in item:
current.st = item["st"]
# End of the row
current.y += 1
current.x = current.rotation_x
elif isinstance(item, dict):
if r != 0:
self.deserializeError("keyboard metadata must the be first element", rows[r])
# TODO: parse prop
else:
self.deserializeError("unexpected", rows[r])
return kbd
# TODO: add tests
# serial = Serial()
# data = open("plain60.json", "r").read()
# data = json.loads(data)
# kb = serial.deserialize(data["layouts"]["keymap"])
# for key in kb.keys:
# print("{} {}x{}+{}x{}".format(key.labels, key.x, key.y, key.width, key.height))

View File

@ -3,8 +3,11 @@ from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QWidget, QTabWidget, QVBoxLayout, QPushButton, QLabel
import sys
import json
from flowlayout import FlowLayout
from util import tr
from kle_serial import Serial as KleSerial
class TabbedKeycodes(QTabWidget):
@ -13,7 +16,7 @@ class TabbedKeycodes(QTabWidget):
self.tab_basic = QWidget()
layout = FlowLayout()
for lbl in ["", "TODO", "Esc", "A", "B", "C", "D", "E", "F"]:
for lbl in ["", "hello", "Esc", "A", "B", "C", "D", "E", "F"]:
btn = QPushButton(lbl)
btn.setFixedSize(50, 50)
layout.addWidget(btn)
@ -22,14 +25,14 @@ class TabbedKeycodes(QTabWidget):
self.tab_media = QWidget()
self.tab_macro = QWidget()
self.addTab(self.tab_basic, "Basic")
self.addTab(self.tab_media, "Media")
self.addTab(self.tab_macro, "Macro")
self.addTab(self.tab_basic, tr("TabbedKeycodes", "Basic"))
self.addTab(self.tab_media, tr("TabbedKeycodes", "Media"))
self.addTab(self.tab_macro, tr("TabbedKeycodes", "Macro"))
KEY_WIDTH = 40
KEY_HEIGHT = KEY_WIDTH
KEY_SPACING = 10
KEY_SPACING = 4
class KeyboardContainer(QWidget):
@ -37,23 +40,32 @@ class KeyboardContainer(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setFixedSize(300, 300)
serial = KleSerial()
data = open("g60.json", "r").read()
data = json.loads(data)
kb = serial.deserialize(data["layouts"]["keymap"])
for x, btn in enumerate(["Q", "W", "E", "R"]):
widget = QLabel(btn)
max_w = max_h = 0
for key in kb.keys:
widget = QLabel(str(key.labels[0]))
widget.setParent(self)
widget.setStyleSheet('background-color:red;')
widget.setStyleSheet('background-color:white; border: 1px solid black')
widget.setAlignment(Qt.AlignCenter)
widget.setFixedSize(KEY_WIDTH, KEY_HEIGHT)
widget.move((KEY_WIDTH + KEY_SPACING) * x, 0)
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)
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)
for x, btn in enumerate(["A", "S", "D", "F"]):
widget = QLabel(btn)
widget.setParent(self)
widget.setStyleSheet('background-color:red;')
widget.setAlignment(Qt.AlignCenter)
widget.setFixedSize(KEY_WIDTH, KEY_HEIGHT)
widget.move((KEY_WIDTH + KEY_SPACING) * (x + 0.25), KEY_HEIGHT + KEY_SPACING)
self.setFixedSize(max_w, max_h)
class MainWindow(QWidget):
@ -67,6 +79,7 @@ class MainWindow(QWidget):
layout = QVBoxLayout()
layout.addWidget(self.keyboard_container)
layout.setAlignment(self.keyboard_container, Qt.AlignHCenter)
layout.addWidget(self.tabbed_keycodes)
self.setLayout(layout)

View File

@ -0,0 +1,3 @@
from PyQt5.QtCore import QCoreApplication
tr = QCoreApplication.translate