class LayoutEditor(BasicEditor): changed = pyqtSignal() def __init__(self, parent=None): super().__init__(parent) self.device = self.keyboard = None self.choices = [] self.widgets = [] self.addStretch() self.keyboard_preview = KeyboardWidget(self) self.keyboard_preview.set_enabled(False) self.keyboard_preview.set_scale(0.7) self.addWidget(self.keyboard_preview) self.setAlignment(self.keyboard_preview, QtCore.Qt.AlignHCenter) w = QWidget() w.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) self.container = QGridLayout() w.setLayout(self.container) self.addWidget(w) self.setAlignment(w, QtCore.Qt.AlignHCenter) self.addStretch() def update_preview(self): self.keyboard_preview.set_keys(self.keyboard.keys, self.keyboard.encoders) self.keyboard_preview.update_layout() self.keyboard_preview.update() self.keyboard_preview.updateGeometry() def rebuild(self, device): super().rebuild(device) if not self.valid(): return self.keyboard = device.keyboard self.blockSignals(True) for choice in self.choices: choice.delete() self.choices = [] for item in device.keyboard.layout_labels: if isinstance(item, str): choice = BooleanChoice(self.on_changed, self.container, item) else: choice = SelectChoice(self.on_changed, self.container, item[0], item[1:]) self.choices.append(choice) self.unpack(self.device.keyboard.layout_options) self.blockSignals(False) self.update_preview() def valid(self): return isinstance(self.device, VialKeyboard) and self.device.keyboard.layout_labels def pack(self): if not self.choices: return 0 val = "" for choice in self.choices: val += choice.pack() return int(val, 2) def unpack(self, value): # we operate on bit strings value = "0" * 100 + bin(value)[2:] # VIA stores option choices backwards, we need to parse the input in reverse for choice in self.choices[::-1]: sz = len(choice.pack()) choice.unpack(value[-sz:]) value = value[:-sz] def get_choice(self, index): return int(self.choices[index].pack(), 2) def on_changed(self): self.changed.emit() self.update_preview()
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()
class Unlocker(QDialog): def __init__(self, layout_editor, keyboard): super().__init__() self.keyboard = keyboard layout = QVBoxLayout() self.progress = QProgressBar() layout.addWidget( QLabel( tr( "Unlocker", "In order to proceed, the keyboard must be set into unlocked mode.\n" "You should only perform this operation on computers that you trust." ))) layout.addWidget( QLabel( tr( "Unlocker", "To exit this mode, you will need to replug the keyboard\n" "or select Security->Lock from the menu."))) layout.addWidget( QLabel( tr( "Unlocker", "Press and hold the following keys until the progress bar " "below fills up:"))) self.keyboard_reference = KeyboardWidget(layout_editor) self.keyboard_reference.set_enabled(False) self.keyboard_reference.set_scale(0.5) layout.addWidget(self.keyboard_reference) layout.setAlignment(self.keyboard_reference, Qt.AlignHCenter) layout.addWidget(self.progress) self.setLayout(layout) self.setWindowFlags(Qt.Dialog | Qt.WindowTitleHint | Qt.CustomizeWindowHint) self.update_reference() self.timer = QTimer() self.timer.timeout.connect(self.unlock_poller) self.perform_unlock() def update_reference(self): """ Updates keycap reference image """ self.keyboard_reference.set_keys(self.keyboard.keys, self.keyboard.encoders) # use "active" background to indicate keys to hold lock_keys = self.keyboard.get_unlock_keys() for w in self.keyboard_reference.widgets: if (w.desc.row, w.desc.col) in lock_keys: w.setActive(True) self.keyboard_reference.update_layout() self.keyboard_reference.update() self.keyboard_reference.updateGeometry() def unlock_poller(self): data = self.keyboard.unlock_poll() unlocked = data[0] unlock_counter = data[2] self.progress.setMaximum(max(self.progress.maximum(), unlock_counter)) self.progress.setValue(self.progress.maximum() - unlock_counter) if unlocked == 1: self.accept() def perform_unlock(self): self.progress.setMaximum(1) self.progress.setValue(0) self.keyboard.unlock_start() self.timer.start(200) @classmethod def unlock(cls, keyboard): if keyboard.get_unlock_status() == 1: return True dlg = cls(cls.global_layout_editor, keyboard) return bool(dlg.exec_()) def keyPressEvent(self, ev): """ Ignore all key presses, e.g. Esc should not close the window """ pass