diff --git a/src/main/python/firmware_flasher.py b/src/main/python/firmware_flasher.py index 14c27e3..7ed0e9d 100644 --- a/src/main/python/firmware_flasher.py +++ b/src/main/python/firmware_flasher.py @@ -3,6 +3,7 @@ from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QLineEdit, QToolButton, QPlainTextEdit, QProgressBar from util import tr +from vial_device import VialBootloader class FirmwareFlasher(QVBoxLayout): @@ -23,3 +24,11 @@ class FirmwareFlasher(QVBoxLayout): btn_flash.setText(tr("Flasher", "Flash")) progress_flash.addWidget(btn_flash) self.addLayout(progress_flash) + + self.device = None + + def rebuild(self, device): + self.device = device + + def valid(self): + return isinstance(self.device, VialBootloader) diff --git a/src/main/python/hidproxy.py b/src/main/python/hidproxy.py new file mode 100644 index 0000000..29a9298 --- /dev/null +++ b/src/main/python/hidproxy.py @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +import sys + + +if sys.platform.startswith("linux"): + import hidraw as hid +else: + import hid diff --git a/src/main/python/layout_editor.py b/src/main/python/layout_editor.py index 5a22367..3a595e1 100644 --- a/src/main/python/layout_editor.py +++ b/src/main/python/layout_editor.py @@ -5,6 +5,7 @@ from PyQt5.QtWidgets import QVBoxLayout from keyboard_container import KeyboardContainer from keycodes import recreate_layer_keycodes from tabbed_keycodes import TabbedKeycodes +from vial_device import VialKeyboard class LayoutEditor(QVBoxLayout): @@ -21,6 +22,8 @@ class LayoutEditor(QVBoxLayout): self.addWidget(self.keyboard_container) self.addWidget(self.tabbed_keycodes) + self.device = None + def on_number_layers_changed(self): recreate_layer_keycodes(self.keyboard_container.keyboard.layers) self.tabbed_keycodes.recreate_layer_keycode_buttons() @@ -28,8 +31,13 @@ class LayoutEditor(QVBoxLayout): def on_keycode_changed(self, code): self.keyboard_container.set_key(code) - def rebuild(self, keyboard): - self.keyboard_container.rebuild(keyboard) + def rebuild(self, device): + self.device = device + if isinstance(self.device, VialKeyboard): + self.keyboard_container.rebuild(device.keyboard) + + def valid(self): + return isinstance(self.device, VialKeyboard) def save_layout(self): return self.keyboard_container.save_layout() diff --git a/src/main/python/main_window.py b/src/main/python/main_window.py index ae3db3a..e113da7 100644 --- a/src/main/python/main_window.py +++ b/src/main/python/main_window.py @@ -7,16 +7,15 @@ from PyQt5.QtWidgets import QWidget, QComboBox, QToolButton, QHBoxLayout, QVBoxL import json from firmware_flasher import FirmwareFlasher -from keyboard import Keyboard from layout_editor import LayoutEditor -from util import tr, find_vial_keyboards, open_device +from util import tr, find_vial_devices class MainWindow(QMainWindow): def __init__(self): super().__init__() - self.keyboard = None + self.current_device = None self.devices = [] self.sideload_json = None self.sideload_vid = self.sideload_pid = -1 @@ -34,17 +33,14 @@ class MainWindow(QMainWindow): layout_combobox.addWidget(btn_refresh_devices) self.layout_editor = LayoutEditor() - flasher = FirmwareFlasher() + self.firmware_flasher = FirmwareFlasher() - tabs = QTabWidget() - for container, lbl in [(self.layout_editor, "Layout"), (flasher, "Firmware updater")]: - w = QWidget() - w.setLayout(container) - tabs.addTab(w, tr("MainWindow", lbl)) + self.tabs = QTabWidget() + self.refresh_tabs() layout = QVBoxLayout() layout.addLayout(layout_combobox) - layout.addWidget(tabs) + layout.addWidget(self.tabs) w = QWidget() w.setLayout(layout) self.setCentralWidget(w) @@ -99,27 +95,37 @@ class MainWindow(QMainWindow): outf.write(self.layout_editor.save_layout()) def on_click_refresh(self): - self.devices = find_vial_keyboards(self.sideload_vid, self.sideload_pid) + self.devices = find_vial_devices(self.sideload_vid, self.sideload_pid) self.combobox_devices.clear() for dev in self.devices: - title = "{} {}".format(dev["manufacturer_string"], dev["product_string"]) - if dev["vendor_id"] == self.sideload_vid and dev["product_id"] == self.sideload_pid: - title += " [sideload]" - self.combobox_devices.addItem(title) + self.combobox_devices.addItem(dev.title()) def on_device_selected(self): - if self.keyboard is not None: - self.keyboard.dev.close() + if self.current_device is not None: + self.current_device.close() + self.current_device = None idx = self.combobox_devices.currentIndex() if idx >= 0: - dev = self.devices[idx] - self.keyboard = Keyboard(open_device(dev)) - if dev["vendor_id"] == self.sideload_vid and dev["product_id"] == self.sideload_pid: - self.keyboard.reload(self.sideload_json) - else: - self.keyboard.reload() - self.layout_editor.rebuild(self.keyboard) + self.current_device = self.devices[idx] + + if self.current_device is not None: + self.current_device.open(self.sideload_json if self.current_device.sideload else None) + + self.layout_editor.rebuild(self.current_device) + self.firmware_flasher.rebuild(self.current_device) + + self.refresh_tabs() + + def refresh_tabs(self): + self.tabs.clear() + for container, lbl in [(self.layout_editor, "Layout"), (self.firmware_flasher, "Firmware updater")]: + if not container.valid(): + continue + + w = QWidget() + w.setLayout(container) + self.tabs.addTab(w, tr("MainWindow", lbl)) def on_sideload_json(self): dialog = QFileDialog() diff --git a/src/main/python/util.py b/src/main/python/util.py index ce7bc46..59c10dd 100644 --- a/src/main/python/util.py +++ b/src/main/python/util.py @@ -2,18 +2,17 @@ from PyQt5.QtCore import QCoreApplication -import sys - -if sys.platform.startswith("linux"): - import hidraw as hid -else: - import hid +from hidproxy import hid tr = QCoreApplication.translate +# For Vial keyboard VIAL_SERIAL_NUMBER_MAGIC = "vial:f64c2b3c" +# For bootloader +VIBL_SERIAL_NUMBER_MAGIC = "vibl:d4f8159c" + MSG_LEN = 32 @@ -33,18 +32,15 @@ def is_rawhid(dev): return dev["interface_number"] == 1 -def find_vial_keyboards(sideload_vid, sideload_pid): +def find_vial_devices(sideload_vid, sideload_pid): + from vial_device import VialBootloader, VialKeyboard + filtered = [] for dev in hid.enumerate(): if VIAL_SERIAL_NUMBER_MAGIC in dev["serial_number"] and is_rawhid(dev): - filtered.append(dev) + filtered.append(VialKeyboard(dev)) + elif VIBL_SERIAL_NUMBER_MAGIC in dev["serial_number"]: + filtered.append(VialBootloader(dev)) elif dev["vendor_id"] == sideload_vid and dev["product_id"] == sideload_pid and is_rawhid(dev): - filtered.append(dev) + filtered.append(VialKeyboard(dev, sideload=True)) return filtered - - -def open_device(desc): - # TODO: error handling here - dev = hid.device() - dev.open_path(desc["path"]) - return dev diff --git a/src/main/python/vial_device.py b/src/main/python/vial_device.py new file mode 100644 index 0000000..db3287c --- /dev/null +++ b/src/main/python/vial_device.py @@ -0,0 +1,44 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +from hidproxy import hid +from keyboard import Keyboard + + +class VialDevice: + + def __init__(self, dev): + self.desc = dev + self.dev = None + self.sideload = False + + def open(self, override_json=None): + # TODO: error handling here + self.dev = hid.device() + self.dev.open_path(self.desc["path"]) + + def close(self): + self.dev.close() + + +class VialKeyboard(VialDevice): + + def __init__(self, dev, sideload=False): + super().__init__(dev) + self.sideload = sideload + self.keyboard = None + + def open(self, override_json=None): + super().open(override_json) + self.keyboard = Keyboard(self.dev) + self.keyboard.reload(override_json) + + def title(self): + s = "{} {}".format(self.desc["manufacturer_string"], self.desc["product_string"]) + if self.sideload: + s += " [sideload]" + return s + + +class VialBootloader(VialDevice): + + def title(self): + return "Vial Bootloader [{:04X}:{:04X}]".format(self.desc["vendor_id"], self.desc["product_id"])