class MatrixTest(BasicEditor): def __init__(self, layout_editor): super().__init__() self.layout_editor = layout_editor self.keyboardWidget = KeyboardWidget(layout_editor) self.keyboardWidget.set_enabled(False) self.unlock_btn = QPushButton("Unlock") self.reset_btn = QPushButton("Reset") layout = QVBoxLayout() layout.addWidget(self.keyboardWidget) layout.setAlignment(self.keyboardWidget, Qt.AlignCenter) self.addLayout(layout) btn_layout = QHBoxLayout() btn_layout.addStretch() self.unlock_lbl = QLabel(tr("MatrixTest", "Unlock the keyboard before testing:")) btn_layout.addWidget(self.unlock_lbl) btn_layout.addWidget(self.unlock_btn) btn_layout.addWidget(self.reset_btn) self.addLayout(btn_layout) self.keyboard = None self.device = None self.polling = False self.timer = QTimer() self.timer.timeout.connect(self.matrix_poller) self.unlock_btn.clicked.connect(self.unlock) self.reset_btn.clicked.connect(self.reset_keyboard_widget) self.grabber = QWidget() def rebuild(self, device): super().rebuild(device) if self.valid(): self.keyboard = device.keyboard self.keyboardWidget.set_keys(self.keyboard.keys, self.keyboard.encoders) self.keyboardWidget.setEnabled(self.valid()) def valid(self): # Check if vial protocol is v3 or later return isinstance(self.device, VialKeyboard) and \ (self.device.keyboard and self.device.keyboard.vial_protocol >= 3) def reset_keyboard_widget(self): # reset keyboard widget for w in self.keyboardWidget.widgets: w.setPressed(False) w.setActive(False) self.keyboardWidget.update_layout() self.keyboardWidget.update() self.keyboardWidget.updateGeometry() def matrix_poller(self): if not self.valid(): self.timer.stop() return try: unlocked = self.keyboard.get_unlock_status(3) except (RuntimeError, ValueError): self.timer.stop() return if not unlocked: self.unlock_btn.show() self.unlock_lbl.show() return # we're unlocked, so hide unlock button and label self.unlock_btn.hide() self.unlock_lbl.hide() # Get size for matrix rows = self.keyboard.rows cols = self.keyboard.cols # Generate 2d array of matrix matrix = [[None] * cols for x in range(rows)] # Get matrix data from keyboard try: data = self.keyboard.matrix_poll() except (RuntimeError, ValueError): self.timer.stop() return # Calculate the amount of bytes belong to 1 row, each bit is 1 key, so per 8 keys in a row, # a byte is needed for the row. row_size = math.ceil(cols / 8) for row in range(rows): # Make slice of bytes for the row (skip first 2 bytes, they're for VIAL) row_data_start = 2 + (row * row_size) row_data_end = row_data_start + row_size row_data = data[row_data_start:row_data_end] # Get each bit representing pressed state for col for col in range(cols): # row_data is array of bytes, calculate in which byte the col is located col_byte = len(row_data) - 1 - math.floor(col / 8) # since we select a single byte as slice of byte, mod 8 to get nth pos of byte col_mod = (col % 8) # write to matrix array matrix[row][col] = (row_data[col_byte] >> col_mod) & 1 # write matrix state to keyboard widget for w in self.keyboardWidget.widgets: if w.desc.row is not None and w.desc.col is not None: row = w.desc.row col = w.desc.col if row < len(matrix) and col < len(matrix[row]): w.setPressed(matrix[row][col]) if matrix[row][col]: w.setActive(True) self.keyboardWidget.update_layout() self.keyboardWidget.update() self.keyboardWidget.updateGeometry() def unlock(self): Unlocker.unlock(self.keyboard) def activate(self): self.grabber.grabKeyboard() self.timer.start(20) def deactivate(self): self.grabber.releaseKeyboard() self.timer.stop()
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 MatrixTest(BasicEditor): def __init__(self, layout_editor): super().__init__() self.layout_editor = layout_editor self.keyboardWidget = KeyboardWidget(layout_editor) self.keyboardWidget.set_enabled(False) self.startButtonWidget = QPushButton("Start testing") self.resetButtonWidget = QPushButton("Reset") layout = QVBoxLayout() layout.addWidget(self.keyboardWidget) layout.setAlignment(self.keyboardWidget, Qt.AlignCenter) self.addLayout(layout) self.addWidget(self.resetButtonWidget) self.addWidget(self.startButtonWidget) self.keyboard = None self.device = None self.polling = False self.timer = QTimer() self.timer.timeout.connect(self.matrix_poller) self.startButtonWidget.clicked.connect(self.start_poller) self.resetButtonWidget.clicked.connect(self.reset_keyboard_widget) def rebuild(self, device): super().rebuild(device) if self.valid(): self.keyboard = device.keyboard self.keyboardWidget.set_keys(self.keyboard.keys, self.keyboard.encoders) def valid(self): # Check if vial protocol is v3 or later return isinstance(self.device, VialKeyboard) and (self.device.keyboard and self.device.keyboard.vial_protocol >= 3) def reset_keyboard_widget(self): # reset keyboard widget for w in self.keyboardWidget.widgets: w.setPressed(False) w.setActive(False) self.keyboardWidget.update_layout() self.keyboardWidget.update() self.keyboardWidget.updateGeometry() def matrix_poller(self): # Get size for matrix rows = self.keyboard.rows cols = self.keyboard.cols # Generate 2d array of matrix matrix = [ [None for y in range(cols)] for x in range(rows) ] # Get matrix data from keyboard data = self.keyboard.matrix_poll() # Calculate the amount of bytes belong to 1 row, each bit is 1 key, so per 8 keys in a row, a byte is needed for the row. row_size = math.ceil(cols / 8) for row in range(rows): # Make slice of bytes for the row (skip first 2 bytes, they're for VIAL) row_data_start = 2 + (row * row_size) row_data_end = row_data_start + row_size row_data = data[row_data_start:row_data_end] #Get each bit representing pressed state for col for col in range(cols): # row_data is array of bytes, calculate in which byte the col is located col_byte = len(row_data) - 1 - math.floor(col / 8) # since we select a single byte as slice of byte, mod 8 to get nth pos of byte col_mod = (col % 8) # write to matrix array matrix[row][col] = (row_data[col_byte] >> col_mod) & 1 # write matrix state to keyboard widget for w in self.keyboardWidget.widgets: if w.desc.row is not None and w.desc.col is not None: row = w.desc.row col = w.desc.col if row < len(matrix) and col < len(matrix[row]): w.setPressed(matrix[row][col]) if matrix[row][col]: w.setActive(True) self.keyboardWidget.update_layout() self.keyboardWidget.update() self.keyboardWidget.updateGeometry() def start_poller(self): if not self.polling: Unlocker.unlock(self.keyboard) self.startButtonWidget.setText("Stop testing") self.timer.start(20) self.polling = True else: self.timer.stop() self.keyboard.lock() self.startButtonWidget.setText("Start testing") self.polling = False
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