vial/src/main/python/util.py

200 lines
6.5 KiB
Python
Raw Normal View History

2020-10-14 22:21:33 -04:00
# SPDX-License-Identifier: GPL-2.0-or-later
2021-03-27 05:03:24 -04:00
import logging
import os
import time
2021-03-27 05:03:24 -04:00
from logging.handlers import RotatingFileHandler
2020-10-14 22:21:33 -04:00
2021-03-27 05:03:24 -04:00
from PyQt5.QtCore import QCoreApplication, QStandardPaths
from PyQt5.QtGui import QPalette
from PyQt5.QtWidgets import QApplication
2020-10-14 12:56:25 -04:00
2020-12-02 10:10:59 -05:00
from hidproxy import hid
from keycodes import Keycode
from keymaps import KEYMAPS
2020-10-14 15:16:14 -04:00
2020-10-14 12:56:25 -04:00
tr = QCoreApplication.translate
2020-10-14 15:16:14 -04:00
2020-12-02 10:10:59 -05:00
# For Vial keyboard
2020-10-14 15:16:14 -04:00
VIAL_SERIAL_NUMBER_MAGIC = "vial:f64c2b3c"
2020-12-02 10:10:59 -05:00
# For bootloader
VIBL_SERIAL_NUMBER_MAGIC = "vibl:d4f8159c"
2020-10-14 15:16:14 -04:00
MSG_LEN = 32
# these should match what we have in vial-qmk/keyboards/vial_example
# so that people don't accidentally reuse a sample keyboard UID
EXAMPLE_KEYBOARDS = [
0xD4A36200603E3007, # vial_stm32f103_vibl
0x32F62BC2EEF2237B, # vial_atmega32u4
0x38CEA320F23046A5, # vial_stm32f072
]
2020-10-16 15:26:11 -04:00
def hid_send(dev, msg, retries=1):
2020-10-14 15:16:14 -04:00
if len(msg) > MSG_LEN:
raise RuntimeError("message must be less than 32 bytes")
msg += b"\x00" * (MSG_LEN - len(msg))
data = b""
first = True
while retries > 0:
retries -= 1
if not first:
time.sleep(0.5)
first = False
try:
# add 00 at start for hidapi report id
if dev.write(b"\x00" + msg) != MSG_LEN + 1:
continue
data = bytes(dev.read(MSG_LEN, timeout_ms=500))
if not data:
continue
except OSError:
continue
break
if not data:
raise RuntimeError("failed to communicate with the device")
return data
2020-10-14 15:16:14 -04:00
2020-10-16 15:26:11 -04:00
def is_rawhid(desc):
if desc["usage_page"] != 0xFF60 or desc["usage"] != 0x61:
2021-03-27 05:03:24 -04:00
logging.warning("is_rawhid: {} does not match - usage_page={:04X} usage={:02X}".format(
desc["path"], desc["usage_page"], desc["usage"]))
return False
dev = hid.device()
try:
dev.open_path(desc["path"])
2021-03-27 05:03:24 -04:00
except OSError as e:
logging.warning("is_rawhid: {} does not match - open_path error {}".format(desc["path"], e))
return False
# probe VIA version and ensure it is supported
data = b""
try:
data = hid_send(dev, b"\x01", retries=3)
2021-03-27 05:03:24 -04:00
except RuntimeError as e:
logging.warning("is_rawhid: {} does not match - hid_send error {}".format(desc["path"], e))
pass
dev.close()
# must have VIA protocol version = 9
2021-03-27 05:03:24 -04:00
if data[0:3] != b"\x01\x00\x09":
logging.warning("is_rawhid: {} does not match - unexpected data in response {}".format(
desc["path"], data.hex()))
return False
logging.info("is_rawhid: {} matched OK".format(desc["path"]))
return True
2020-10-14 15:16:14 -04:00
2020-10-16 15:26:11 -04:00
2021-01-11 01:51:24 -05:00
def find_vial_devices(via_stack_json, sideload_vid=None, sideload_pid=None):
from vial_device import VialBootloader, VialKeyboard, VialDummyKeyboard
2020-12-02 10:10:59 -05:00
2020-10-14 15:16:14 -04:00
filtered = []
for dev in hid.enumerate():
2021-03-27 05:03:24 -04:00
if dev["vendor_id"] == sideload_vid and dev["product_id"] == sideload_pid:
logging.info("Trying VID={:04X}, PID={:04X}, serial={}, path={} - sideload".format(
dev["vendor_id"], dev["product_id"], dev["serial_number"], dev["path"]
))
if is_rawhid(dev):
filtered.append(VialKeyboard(dev, sideload=True))
elif VIAL_SERIAL_NUMBER_MAGIC in dev["serial_number"]:
logging.info("Matching VID={:04X}, PID={:04X}, serial={}, path={} - vial serial magic".format(
dev["vendor_id"], dev["product_id"], dev["serial_number"], dev["path"]
))
if is_rawhid(dev):
filtered.append(VialKeyboard(dev))
2020-12-02 10:10:59 -05:00
elif VIBL_SERIAL_NUMBER_MAGIC in dev["serial_number"]:
2021-03-27 05:03:24 -04:00
logging.info("Matching VID={:04X}, PID={:04X}, serial={}, path={} - vibl serial magic".format(
dev["vendor_id"], dev["product_id"], dev["serial_number"], dev["path"]
))
2020-12-02 10:10:59 -05:00
filtered.append(VialBootloader(dev))
2021-03-27 05:03:24 -04:00
elif str(dev["vendor_id"] * 65536 + dev["product_id"]) in via_stack_json["definitions"]:
logging.info("Matching VID={:04X}, PID={:04X}, serial={}, path={} - VIA stack".format(
dev["vendor_id"], dev["product_id"], dev["serial_number"], dev["path"]
))
if is_rawhid(dev):
filtered.append(VialKeyboard(dev, via_stack=True))
if sideload_vid == sideload_pid == 0:
filtered.append(VialDummyKeyboard())
2020-10-14 15:16:14 -04:00
return filtered
def chunks(data, sz):
for i in range(0, len(data), sz):
yield data[i:i+sz]
def pad_for_vibl(msg):
""" Pads message to vibl fixed 64-byte length """
if len(msg) > 64:
raise RuntimeError("vibl message too long")
return msg + b"\x00" * (64 - len(msg))
2021-03-27 05:03:24 -04:00
def init_logger():
logging.basicConfig(level=logging.INFO)
directory = QStandardPaths.writableLocation(QStandardPaths.AppLocalDataLocation)
if not os.path.exists(directory):
os.mkdir(directory)
path = os.path.join(directory, "vial.log")
handler = RotatingFileHandler(path, maxBytes=5 * 1024 * 1024, backupCount=5)
handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(module)s:%(lineno)d - %(message)s"))
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()