def __init__(self, layout_editor): super().__init__() self.keyboard_container = KeyboardContainer(layout_editor) self.tabbed_keycodes = TabbedKeycodes() self.tabbed_keycodes.keycode_changed.connect(self.on_keycode_changed) self.addWidget(self.keyboard_container) self.addWidget(self.tabbed_keycodes) self.device = None
def on_tab_changed(self, index): TabbedKeycodes.close_tray() old_tab = self.current_tab new_tab = None if index >= 0: new_tab = self.tabs.widget(index) if old_tab is not None: old_tab.editor.deactivate() if new_tab is not None: new_tab.editor.activate() self.current_tab = new_tab
def __init__(self, layout_editor): super().__init__() self.layout_editor = layout_editor self.layout_layers = QHBoxLayout() self.layout_size = QVBoxLayout() layer_label = QLabel(tr("KeymapEditor", "Layer")) layout_labels_container = QHBoxLayout() layout_labels_container.addWidget(layer_label) layout_labels_container.addLayout(self.layout_layers) layout_labels_container.addStretch() layout_labels_container.addLayout(self.layout_size) # contains the actual keyboard self.container = KeyboardWidget(layout_editor) self.container.clicked.connect(self.on_key_clicked) layout = QVBoxLayout() layout.addLayout(layout_labels_container) layout.addWidget(self.container) layout.setAlignment(self.container, Qt.AlignHCenter) self.layer_buttons = [] self.keyboard = None self.current_layer = 0 layout_editor.changed.connect(self.on_layout_changed) self.container.anykey.connect(self.on_any_keycode) self.tabbed_keycodes = TabbedKeycodes() self.tabbed_keycodes.keycode_changed.connect(self.on_keycode_changed) self.tabbed_keycodes.anykey.connect(self.on_any_keycode) self.addLayout(layout) self.addWidget(self.tabbed_keycodes) self.device = None KeycodeDisplay.notify_keymap_override(self)
class KeymapEditor(BasicEditor): def __init__(self, layout_editor): super().__init__() self.keyboard_container = KeyboardContainer(layout_editor) self.tabbed_keycodes = TabbedKeycodes() self.tabbed_keycodes.keycode_changed.connect(self.on_keycode_changed) self.addWidget(self.keyboard_container) self.addWidget(self.tabbed_keycodes) self.device = None def on_keycode_changed(self, code): self.keyboard_container.set_key(code) def rebuild(self, device): super().rebuild(device) if self.valid(): self.keyboard_container.rebuild(device.keyboard) recreate_keyboard_keycodes(self.keyboard_container.keyboard) self.tabbed_keycodes.recreate_keycode_buttons() self.keyboard_container.refresh_layer_display() def valid(self): return isinstance(self.device, VialKeyboard) def save_layout(self): return self.keyboard_container.save_layout() def restore_layout(self, data): self.keyboard_container.restore_layout(data) def set_keymap_override(self, override): self.keyboard_container.set_keymap_override(override) self.tabbed_keycodes.set_keymap_override(override)
def mouseReleaseEvent(self, ev): TabbedKeycodes.close_tray()
class KeymapEditor(BasicEditor): def __init__(self, layout_editor): super().__init__() self.layout_editor = layout_editor self.layout_layers = QHBoxLayout() self.layout_size = QVBoxLayout() layer_label = QLabel(tr("KeymapEditor", "Layer")) layout_labels_container = QHBoxLayout() layout_labels_container.addWidget(layer_label) layout_labels_container.addLayout(self.layout_layers) layout_labels_container.addStretch() layout_labels_container.addLayout(self.layout_size) # contains the actual keyboard self.container = KeyboardWidget(layout_editor) self.container.clicked.connect(self.on_key_clicked) layout = QVBoxLayout() layout.addLayout(layout_labels_container) layout.addWidget(self.container) layout.setAlignment(self.container, Qt.AlignHCenter) self.layer_buttons = [] self.keyboard = None self.current_layer = 0 layout_editor.changed.connect(self.on_layout_changed) self.container.anykey.connect(self.on_any_keycode) self.tabbed_keycodes = TabbedKeycodes() self.tabbed_keycodes.keycode_changed.connect(self.on_keycode_changed) self.tabbed_keycodes.anykey.connect(self.on_any_keycode) self.addLayout(layout) self.addWidget(self.tabbed_keycodes) self.device = None KeycodeDisplay.notify_keymap_override(self) def on_container_clicked(self): """ Called when a mouse click event is bubbled up to the editor's container """ self.container.deselect() self.container.update() def on_keycode_changed(self, code): self.set_key(code) def rebuild_layers(self): # delete old layer labels for label in self.layer_buttons: label.hide() label.deleteLater() self.layer_buttons = [] # create new layer labels for x in range(self.keyboard.layers): btn = SquareButton(str(x)) btn.setFocusPolicy(Qt.NoFocus) btn.setRelSize(1.667) btn.setCheckable(True) btn.clicked.connect(lambda state, idx=x: self.switch_layer(idx)) self.layout_layers.addWidget(btn) self.layer_buttons.append(btn) for x in range(0, 2): btn = SquareButton("-") if x else SquareButton("+") btn.setFocusPolicy(Qt.NoFocus) btn.setCheckable(False) btn.clicked.connect(lambda state, idx=x: self.adjust_size(idx)) self.layout_size.addWidget(btn) self.layer_buttons.append(btn) def adjust_size(self, minus): if minus: self.container.set_scale(self.container.get_scale() - 0.1) else: self.container.set_scale(self.container.get_scale() + 0.1) self.refresh_layer_display() def rebuild(self, device): super().rebuild(device) if self.valid(): self.keyboard = device.keyboard # get number of layers self.rebuild_layers() self.container.set_keys(self.keyboard.keys, self.keyboard.encoders) self.current_layer = 0 self.on_layout_changed() recreate_keyboard_keycodes(self.keyboard) self.tabbed_keycodes.recreate_keycode_buttons() TabbedKeycodes.tray.recreate_keycode_buttons() self.refresh_layer_display() self.container.setEnabled(self.valid()) def valid(self): return isinstance(self.device, VialKeyboard) def save_layout(self): return self.keyboard.save_layout() def restore_layout(self, data): if json.loads( data.decode("utf-8")).get("uid") != self.keyboard.keyboard_id: ret = QMessageBox.question( self.widget(), "", tr( "KeymapEditor", "Saved keymap belongs to a different keyboard," " are you sure you want to continue?"), QMessageBox.Yes | QMessageBox.No) if ret != QMessageBox.Yes: return self.keyboard.restore_layout(data) self.refresh_layer_display() def on_any_keycode(self): if self.container.active_key is None: return current_code = self.code_for_widget(self.container.active_key) if self.container.active_mask: current_code &= 0xFF dlg = AnyKeycodeDialog(current_code) if dlg.exec_() and dlg.value >= 0: self.on_keycode_changed(dlg.value) def code_for_widget(self, widget): if widget.desc.row is not None: return self.keyboard.layout[(self.current_layer, widget.desc.row, widget.desc.col)] else: return self.keyboard.encoder_layout[(self.current_layer, widget.desc.encoder_idx, widget.desc.encoder_dir)] def refresh_layer_display(self): """ Refresh text on key widgets to display data corresponding to current layer """ self.container.update_layout() for idx, btn in enumerate(self.layer_buttons): btn.setEnabled(idx != self.current_layer) btn.setChecked(idx == self.current_layer) for widget in self.container.widgets: code = self.code_for_widget(widget) KeycodeDisplay.display_keycode(widget, code) self.container.update() self.container.updateGeometry() def switch_layer(self, idx): self.container.deselect() self.current_layer = idx self.refresh_layer_display() def set_key(self, keycode): """ Change currently selected key to provided keycode """ if self.container.active_key is None: return if isinstance(self.container.active_key, EncoderWidget): self.set_key_encoder(keycode) else: self.set_key_matrix(keycode) self.container.select_next() def set_key_encoder(self, keycode): l, i, d = self.current_layer, self.container.active_key.desc.encoder_idx,\ self.container.active_key.desc.encoder_dir # if masked, ensure that this is a byte-sized keycode if self.container.active_mask: if keycode > 0xFF: return keycode = (self.keyboard.encoder_layout[(l, i, d)] & 0xFF00) | keycode self.keyboard.set_encoder(l, i, d, keycode) self.refresh_layer_display() def set_key_matrix(self, keycode): l, r, c = self.current_layer, self.container.active_key.desc.row, self.container.active_key.desc.col if r >= 0 and c >= 0: # if masked, ensure that this is a byte-sized keycode if self.container.active_mask: if keycode > 0xFF: return keycode = (self.keyboard.layout[(l, r, c)] & 0xFF00) | keycode self.keyboard.set_key(l, r, c, keycode) self.refresh_layer_display() def on_key_clicked(self): """ Called when a key on the keyboard widget is clicked """ self.refresh_layer_display() def on_layout_changed(self): if self.keyboard is None: return self.refresh_layer_display() self.keyboard.set_layout_options(self.layout_editor.pack()) def on_keymap_override(self): self.refresh_layer_display()
def __init__(self, appctx): super().__init__() self.appctx = appctx self.settings = QSettings("Vial", "Vial") if self.settings.value("size", None) and self.settings.value( "pos", None): self.resize(self.settings.value("size")) self.move(self.settings.value("pos")) else: self.resize(WINDOW_WIDTH, WINDOW_HEIGHT) themes.set_theme(self.get_theme()) self.combobox_devices = QComboBox() self.combobox_devices.currentIndexChanged.connect( self.on_device_selected) self.btn_refresh_devices = QToolButton() self.btn_refresh_devices.setToolButtonStyle(Qt.ToolButtonTextOnly) self.btn_refresh_devices.setText(tr("MainWindow", "Refresh")) self.btn_refresh_devices.clicked.connect(self.on_click_refresh) layout_combobox = QHBoxLayout() layout_combobox.addWidget(self.combobox_devices) layout_combobox.addWidget(self.btn_refresh_devices) self.layout_editor = LayoutEditor() self.keymap_editor = KeymapEditor(self.layout_editor) self.firmware_flasher = FirmwareFlasher(self) self.macro_recorder = MacroRecorder() self.tap_dance = TapDance() self.combos = Combos() QmkSettings.initialize(appctx) self.qmk_settings = QmkSettings() self.matrix_tester = MatrixTest(self.layout_editor) self.rgb_configurator = RGBConfigurator() self.editors = [(self.keymap_editor, "Keymap"), (self.layout_editor, "Layout"), (self.macro_recorder, "Macros"), (self.rgb_configurator, "Lighting"), (self.tap_dance, "Tap Dance"), (self.combos, "Combos"), (self.qmk_settings, "QMK Settings"), (self.matrix_tester, "Matrix tester"), (self.firmware_flasher, "Firmware updater")] Unlocker.global_layout_editor = self.layout_editor self.current_tab = None self.tabs = QTabWidget() self.tabs.currentChanged.connect(self.on_tab_changed) self.refresh_tabs() no_devices = 'No devices detected. Connect a Vial-compatible device and press "Refresh"<br>' \ 'or select "File" → "Download VIA definitions" in order to enable support for VIA keyboards.' if sys.platform.startswith("linux"): no_devices += '<br><br>On Linux you need to set up a custom udev rule for keyboards to be detected. ' \ 'Follow the instructions linked below:<br>' \ '<a href="https://get.vial.today/manual/linux-udev.html">https://get.vial.today/manual/linux-udev.html</a>' self.lbl_no_devices = QLabel(tr("MainWindow", no_devices)) self.lbl_no_devices.setTextFormat(Qt.RichText) self.lbl_no_devices.setAlignment(Qt.AlignCenter) layout = QVBoxLayout() layout.addLayout(layout_combobox) layout.addWidget(self.tabs) layout.addWidget(self.lbl_no_devices) layout.setAlignment(self.lbl_no_devices, Qt.AlignHCenter) self.tray_keycodes = TabbedKeycodes() self.tray_keycodes.make_tray() layout.addWidget(self.tray_keycodes) self.tray_keycodes.hide() w = QWidget() w.setLayout(layout) self.setCentralWidget(w) self.init_menu() self.autorefresh = Autorefresh() self.autorefresh.devices_updated.connect(self.on_devices_updated) # cache for via definition files self.cache_path = QStandardPaths.writableLocation( QStandardPaths.CacheLocation) if not os.path.exists(self.cache_path): os.makedirs(self.cache_path) # check if the via defitions already exist if os.path.isfile(os.path.join(self.cache_path, "via_keyboards.json")): with open(os.path.join(self.cache_path, "via_keyboards.json")) as vf: data = vf.read() try: self.autorefresh.load_via_stack(data) except JSONDecodeError as e: # the saved file is invalid - just ignore this logging.warning( "Failed to parse stored via_keyboards.json: {}".format(e)) # make sure initial state is valid self.on_click_refresh()
class MainWindow(QMainWindow): def __init__(self, appctx): super().__init__() self.appctx = appctx self.settings = QSettings("Vial", "Vial") if self.settings.value("size", None) and self.settings.value( "pos", None): self.resize(self.settings.value("size")) self.move(self.settings.value("pos")) else: self.resize(WINDOW_WIDTH, WINDOW_HEIGHT) themes.set_theme(self.get_theme()) self.combobox_devices = QComboBox() self.combobox_devices.currentIndexChanged.connect( self.on_device_selected) self.btn_refresh_devices = QToolButton() self.btn_refresh_devices.setToolButtonStyle(Qt.ToolButtonTextOnly) self.btn_refresh_devices.setText(tr("MainWindow", "Refresh")) self.btn_refresh_devices.clicked.connect(self.on_click_refresh) layout_combobox = QHBoxLayout() layout_combobox.addWidget(self.combobox_devices) layout_combobox.addWidget(self.btn_refresh_devices) self.layout_editor = LayoutEditor() self.keymap_editor = KeymapEditor(self.layout_editor) self.firmware_flasher = FirmwareFlasher(self) self.macro_recorder = MacroRecorder() self.tap_dance = TapDance() self.combos = Combos() QmkSettings.initialize(appctx) self.qmk_settings = QmkSettings() self.matrix_tester = MatrixTest(self.layout_editor) self.rgb_configurator = RGBConfigurator() self.editors = [(self.keymap_editor, "Keymap"), (self.layout_editor, "Layout"), (self.macro_recorder, "Macros"), (self.rgb_configurator, "Lighting"), (self.tap_dance, "Tap Dance"), (self.combos, "Combos"), (self.qmk_settings, "QMK Settings"), (self.matrix_tester, "Matrix tester"), (self.firmware_flasher, "Firmware updater")] Unlocker.global_layout_editor = self.layout_editor self.current_tab = None self.tabs = QTabWidget() self.tabs.currentChanged.connect(self.on_tab_changed) self.refresh_tabs() no_devices = 'No devices detected. Connect a Vial-compatible device and press "Refresh"<br>' \ 'or select "File" → "Download VIA definitions" in order to enable support for VIA keyboards.' if sys.platform.startswith("linux"): no_devices += '<br><br>On Linux you need to set up a custom udev rule for keyboards to be detected. ' \ 'Follow the instructions linked below:<br>' \ '<a href="https://get.vial.today/manual/linux-udev.html">https://get.vial.today/manual/linux-udev.html</a>' self.lbl_no_devices = QLabel(tr("MainWindow", no_devices)) self.lbl_no_devices.setTextFormat(Qt.RichText) self.lbl_no_devices.setAlignment(Qt.AlignCenter) layout = QVBoxLayout() layout.addLayout(layout_combobox) layout.addWidget(self.tabs) layout.addWidget(self.lbl_no_devices) layout.setAlignment(self.lbl_no_devices, Qt.AlignHCenter) self.tray_keycodes = TabbedKeycodes() self.tray_keycodes.make_tray() layout.addWidget(self.tray_keycodes) self.tray_keycodes.hide() w = QWidget() w.setLayout(layout) self.setCentralWidget(w) self.init_menu() self.autorefresh = Autorefresh() self.autorefresh.devices_updated.connect(self.on_devices_updated) # cache for via definition files self.cache_path = QStandardPaths.writableLocation( QStandardPaths.CacheLocation) if not os.path.exists(self.cache_path): os.makedirs(self.cache_path) # check if the via defitions already exist if os.path.isfile(os.path.join(self.cache_path, "via_keyboards.json")): with open(os.path.join(self.cache_path, "via_keyboards.json")) as vf: data = vf.read() try: self.autorefresh.load_via_stack(data) except JSONDecodeError as e: # the saved file is invalid - just ignore this logging.warning( "Failed to parse stored via_keyboards.json: {}".format(e)) # make sure initial state is valid self.on_click_refresh() def init_menu(self): layout_load_act = QAction(tr("MenuFile", "Load saved layout..."), self) layout_load_act.setShortcut("Ctrl+O") layout_load_act.triggered.connect(self.on_layout_load) layout_save_act = QAction(tr("MenuFile", "Save current layout..."), self) layout_save_act.setShortcut("Ctrl+S") layout_save_act.triggered.connect(self.on_layout_save) sideload_json_act = QAction(tr("MenuFile", "Sideload VIA JSON..."), self) sideload_json_act.triggered.connect(self.on_sideload_json) download_via_stack_act = QAction( tr("MenuFile", "Download VIA definitions"), self) download_via_stack_act.triggered.connect(self.load_via_stack_json) load_dummy_act = QAction(tr("MenuFile", "Load dummy JSON..."), self) load_dummy_act.triggered.connect(self.on_load_dummy) exit_act = QAction(tr("MenuFile", "Exit"), self) exit_act.setShortcut("Ctrl+Q") exit_act.triggered.connect(qApp.exit) file_menu = self.menuBar().addMenu(tr("Menu", "File")) file_menu.addAction(layout_load_act) file_menu.addAction(layout_save_act) file_menu.addSeparator() file_menu.addAction(sideload_json_act) file_menu.addAction(download_via_stack_act) file_menu.addAction(load_dummy_act) file_menu.addSeparator() file_menu.addAction(exit_act) keyboard_unlock_act = QAction(tr("MenuSecurity", "Unlock"), self) keyboard_unlock_act.triggered.connect(self.unlock_keyboard) keyboard_lock_act = QAction(tr("MenuSecurity", "Lock"), self) keyboard_lock_act.triggered.connect(self.lock_keyboard) keyboard_reset_act = QAction( tr("MenuSecurity", "Reboot to bootloader"), self) keyboard_reset_act.triggered.connect(self.reboot_to_bootloader) keyboard_layout_menu = self.menuBar().addMenu( tr("Menu", "Keyboard layout")) keymap_group = QActionGroup(self) selected_keymap = self.settings.value("keymap") for idx, keymap in enumerate(KEYMAPS): act = QAction(tr("KeyboardLayout", keymap[0]), self) act.triggered.connect( lambda checked, x=idx: self.change_keyboard_layout(x)) act.setCheckable(True) if selected_keymap == keymap[0]: self.change_keyboard_layout(idx) act.setChecked(True) keymap_group.addAction(act) keyboard_layout_menu.addAction(act) # check "QWERTY" if nothing else is selected if keymap_group.checkedAction() is None: keymap_group.actions()[0].setChecked(True) self.security_menu = self.menuBar().addMenu(tr("Menu", "Security")) self.security_menu.addAction(keyboard_unlock_act) self.security_menu.addAction(keyboard_lock_act) self.security_menu.addSeparator() self.security_menu.addAction(keyboard_reset_act) self.theme_menu = self.menuBar().addMenu(tr("Menu", "Theme")) theme_group = QActionGroup(self) selected_theme = self.get_theme() for name, _ in [("System", None)] + themes.themes: act = QAction(tr("MenuTheme", name), self) act.triggered.connect(lambda x, name=name: self.set_theme(name)) act.setCheckable(True) act.setChecked(selected_theme == name) theme_group.addAction(act) self.theme_menu.addAction(act) # check "System" if nothing else is selected if theme_group.checkedAction() is None: theme_group.actions()[0].setChecked(True) about_vial_act = QAction(tr("MenuAbout", "About Vial..."), self) about_vial_act.triggered.connect(self.about_vial) self.about_menu = self.menuBar().addMenu(tr("Menu", "About")) self.about_menu.addAction(about_vial_act) def on_layout_load(self): dialog = QFileDialog() dialog.setDefaultSuffix("vil") dialog.setAcceptMode(QFileDialog.AcceptOpen) dialog.setNameFilters(["Vial layout (*.vil)"]) if dialog.exec_() == QDialog.Accepted: with open(dialog.selectedFiles()[0], "rb") as inf: data = inf.read() self.keymap_editor.restore_layout(data) self.rebuild() def on_layout_save(self): dialog = QFileDialog() dialog.setDefaultSuffix("vil") dialog.setAcceptMode(QFileDialog.AcceptSave) dialog.setNameFilters(["Vial layout (*.vil)"]) if dialog.exec_() == QDialog.Accepted: with open(dialog.selectedFiles()[0], "wb") as outf: outf.write(self.keymap_editor.save_layout()) def on_click_refresh(self): # we don't do check_protocol here either because if the matrix test tab is active, # that ends up corrupting usb hid packets self.autorefresh.update(check_protocol=False) def on_devices_updated(self, devices, hard_refresh): self.combobox_devices.blockSignals(True) self.combobox_devices.clear() for dev in devices: self.combobox_devices.addItem(dev.title()) if self.autorefresh.current_device and dev.desc[ "path"] == self.autorefresh.current_device.desc["path"]: self.combobox_devices.setCurrentIndex( self.combobox_devices.count() - 1) self.combobox_devices.blockSignals(False) if devices: self.lbl_no_devices.hide() self.tabs.show() else: self.lbl_no_devices.show() self.tabs.hide() if hard_refresh: self.on_device_selected() def on_device_selected(self): try: self.autorefresh.select_device( self.combobox_devices.currentIndex()) except ProtocolError: QMessageBox.warning( self, "", "Unsupported protocol version!\n" "Please download latest Vial from https://get.vial.today/") if isinstance(self.autorefresh.current_device, VialKeyboard) \ and self.autorefresh.current_device.keyboard.keyboard_id in EXAMPLE_KEYBOARDS: QMessageBox.warning( self, "", "An example keyboard UID was detected.\n" "Please change your keyboard UID to be unique before you ship!" ) self.rebuild() self.refresh_tabs() def rebuild(self): # don't show "Security" menu for bootloader mode, as the bootloader is inherently insecure self.security_menu.menuAction().setVisible( isinstance(self.autorefresh.current_device, VialKeyboard)) # if unlock process was interrupted, we must finish it first if isinstance( self.autorefresh.current_device, VialKeyboard ) and self.autorefresh.current_device.keyboard.get_unlock_in_progress( ): Unlocker.unlock(self.autorefresh.current_device.keyboard) self.autorefresh.current_device.keyboard.reload() for e in [ self.layout_editor, self.keymap_editor, self.firmware_flasher, self.macro_recorder, self.tap_dance, self.combos, self.qmk_settings, self.matrix_tester, self.rgb_configurator ]: e.rebuild(self.autorefresh.current_device) def refresh_tabs(self): self.tabs.clear() for container, lbl in self.editors: if not container.valid(): continue c = EditorContainer(container) self.tabs.addTab(c, tr("MainWindow", lbl)) def load_via_stack_json(self): with urlopen( "https://github.com/vial-kb/via-keymap-precompiled/raw/main/via_keyboard_stack.json" ) as resp: data = resp.read() self.autorefresh.load_via_stack(data) # write to cache with open(os.path.join(self.cache_path, "via_keyboards.json"), "wb") as cf: cf.write(data) def on_sideload_json(self): dialog = QFileDialog() dialog.setDefaultSuffix("json") dialog.setAcceptMode(QFileDialog.AcceptOpen) dialog.setNameFilters(["VIA layout JSON (*.json)"]) if dialog.exec_() == QDialog.Accepted: with open(dialog.selectedFiles()[0], "rb") as inf: data = inf.read() self.autorefresh.sideload_via_json(data) def on_load_dummy(self): dialog = QFileDialog() dialog.setDefaultSuffix("json") dialog.setAcceptMode(QFileDialog.AcceptOpen) dialog.setNameFilters(["VIA layout JSON (*.json)"]) if dialog.exec_() == QDialog.Accepted: with open(dialog.selectedFiles()[0], "rb") as inf: data = inf.read() self.autorefresh.load_dummy(data) def lock_ui(self): self.autorefresh._lock() self.tabs.setEnabled(False) self.combobox_devices.setEnabled(False) self.btn_refresh_devices.setEnabled(False) def unlock_ui(self): self.autorefresh._unlock() self.tabs.setEnabled(True) self.combobox_devices.setEnabled(True) self.btn_refresh_devices.setEnabled(True) def unlock_keyboard(self): if isinstance(self.autorefresh.current_device, VialKeyboard): Unlocker.unlock(self.autorefresh.current_device.keyboard) def lock_keyboard(self): if isinstance(self.autorefresh.current_device, VialKeyboard): self.autorefresh.current_device.keyboard.lock() def reboot_to_bootloader(self): if isinstance(self.autorefresh.current_device, VialKeyboard): Unlocker.unlock(self.autorefresh.current_device.keyboard) self.autorefresh.current_device.keyboard.reset() def change_keyboard_layout(self, index): self.settings.setValue("keymap", KEYMAPS[index][0]) KeycodeDisplay.set_keymap_override(KEYMAPS[index][1]) def get_theme(self): return self.settings.value("theme", "Dark") def set_theme(self, theme): themes.set_theme(theme) self.settings.setValue("theme", theme) msg = QMessageBox() msg.setText( tr( "MainWindow", "In order to fully apply the theme you should restart the application." )) msg.exec_() def on_tab_changed(self, index): TabbedKeycodes.close_tray() old_tab = self.current_tab new_tab = None if index >= 0: new_tab = self.tabs.widget(index) if old_tab is not None: old_tab.editor.deactivate() if new_tab is not None: new_tab.editor.activate() self.current_tab = new_tab def about_vial(self): QMessageBox.about( self, "About Vial", 'Vial {}<br><br>' 'Licensed under the terms of the<br>GNU General Public License (version 2 or later)<br><br>' '<a href="https://get.vial.today/">https://get.vial.today/</a>'. format(self.appctx.build_settings["version"])) def closeEvent(self, e): self.settings.setValue("size", self.size()) self.settings.setValue("pos", self.pos()) e.accept()
def mousePressEvent(self, ev): super().mousePressEvent(ev) if self.active_key is not None: TabbedKeycodes.open_tray(self) else: TabbedKeycodes.close_tray()
class KeymapEditor(BasicEditor): def __init__(self, layout_editor): super().__init__() self.layout_editor = layout_editor self.layout_layers = QHBoxLayout() layer_label = QLabel(tr("KeymapEditor", "Layer")) layout_labels_container = QHBoxLayout() layout_labels_container.addWidget(layer_label) layout_labels_container.addLayout(self.layout_layers) layout_labels_container.addStretch() # contains the actual keyboard self.container = KeyboardWidget(layout_editor) self.container.clicked.connect(self.on_key_clicked) layout = QVBoxLayout() layout.addLayout(layout_labels_container) layout.addWidget(self.container) layout.setAlignment(self.container, Qt.AlignHCenter) self.layer_buttons = [] self.keyboard = None self.current_layer = 0 layout_editor.changed.connect(self.on_layout_changed) self.keymap_override = KEYMAPS[0][1] self.container.anykey.connect(self.on_any_keycode) self.tabbed_keycodes = TabbedKeycodes() self.tabbed_keycodes.keycode_changed.connect(self.on_keycode_changed) self.tabbed_keycodes.anykey.connect(self.on_any_keycode) self.addLayout(layout) self.addWidget(self.tabbed_keycodes) self.device = None def on_keycode_changed(self, code): self.set_key(code) def rebuild_layers(self): # delete old layer labels for label in self.layer_buttons: label.hide() label.deleteLater() self.layer_buttons = [] # create new layer labels for x in range(self.keyboard.layers): btn = SquareButton(str(x)) btn.setRelSize(1.667) btn.setCheckable(True) btn.clicked.connect(lambda state, idx=x: self.switch_layer(idx)) self.layout_layers.addWidget(btn) self.layer_buttons.append(btn) def rebuild(self, device): super().rebuild(device) if self.valid(): self.keyboard = device.keyboard # get number of layers self.rebuild_layers() self.container.set_keys(self.keyboard.keys, self.keyboard.encoders) self.current_layer = 0 self.on_layout_changed() recreate_keyboard_keycodes(self.keyboard) self.tabbed_keycodes.recreate_keycode_buttons() self.refresh_layer_display() def valid(self): return isinstance(self.device, VialKeyboard) def save_layout(self): return self.keyboard.save_layout() def restore_layout(self, data): if json.loads( data.decode("utf-8")).get("uid") != self.keyboard.keyboard_id: ret = QMessageBox.question( self.widget(), "", tr( "KeymapEditor", "Saved keymap belongs to a different keyboard," " are you sure you want to continue?"), QMessageBox.Yes | QMessageBox.No) if ret != QMessageBox.Yes: return self.keyboard.restore_layout(data) self.refresh_layer_display() def set_keymap_override(self, override): self.keymap_override = override self.refresh_layer_display() self.tabbed_keycodes.set_keymap_override(override) def on_any_keycode(self): if self.container.active_key is None: return current_code = self.code_for_widget(self.container.active_key) dlg = AnyKeycodeDialog(current_code) if dlg.exec_() and dlg.value >= 0: self.on_keycode_changed(dlg.value) def code_is_overriden(self, 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 self.keymap_override def get_label(self, code): """ Get label for a specific keycode """ if self.code_is_overriden(code): return self.keymap_override[Keycode.find_outer_keycode( code).qmk_id] return Keycode.label(code) def code_for_widget(self, widget): if widget.desc.row is not None: return self.keyboard.layout[(self.current_layer, widget.desc.row, widget.desc.col)] else: return self.keyboard.encoder_layout[(self.current_layer, widget.desc.encoder_idx, widget.desc.encoder_dir)] def refresh_layer_display(self): """ Refresh text on key widgets to display data corresponding to current layer """ self.container.update_layout() for idx, btn in enumerate(self.layer_buttons): btn.setEnabled(idx != self.current_layer) btn.setChecked(idx == self.current_layer) for widget in self.container.widgets: code = self.code_for_widget(widget) text = self.get_label(code) tooltip = Keycode.tooltip(code) mask = Keycode.is_mask(code) mask_text = self.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 self.code_is_overriden(code): widget.setColor(QApplication.palette().color(QPalette.Link)) else: widget.setColor(None) self.container.update() self.container.updateGeometry() def switch_layer(self, idx): self.container.deselect() self.current_layer = idx self.refresh_layer_display() def set_key(self, keycode): """ Change currently selected key to provided keycode """ if isinstance(self.container.active_key, EncoderWidget): self.set_key_encoder(keycode) else: self.set_key_matrix(keycode) self.container.select_next() def set_key_encoder(self, keycode): l, i, d = self.current_layer, self.container.active_key.desc.encoder_idx,\ self.container.active_key.desc.encoder_dir # if masked, ensure that this is a byte-sized keycode if self.container.active_mask: if keycode > 0xFF: return keycode = (self.keyboard.encoder_layout[(l, i, d)] & 0xFF00) | keycode self.keyboard.set_encoder(l, i, d, keycode) self.refresh_layer_display() def set_key_matrix(self, keycode): l, r, c = self.current_layer, self.container.active_key.desc.row, self.container.active_key.desc.col if r >= 0 and c >= 0: # if masked, ensure that this is a byte-sized keycode if self.container.active_mask: if keycode > 0xFF: return keycode = (self.keyboard.layout[(l, r, c)] & 0xFF00) | keycode self.keyboard.set_key(l, r, c, keycode) self.refresh_layer_display() def on_key_clicked(self): """ Called when a key on the keyboard widget is clicked """ self.refresh_layer_display() def on_layout_changed(self): if self.keyboard is None: return self.refresh_layer_display() self.keyboard.set_layout_options(self.layout_editor.pack())