From 016ac7eed553f60caa188ab08ec23ca63f8102ee Mon Sep 17 00:00:00 2001 From: Pieterv24 <9167905+Pieterv24@users.noreply.github.com> Date: Mon, 19 Apr 2021 00:42:39 +0200 Subject: [PATCH 1/4] started working on matrix test feature --- src/main/python/keyboard_comm.py | 8 ++++ src/main/python/main_window.py | 6 ++- src/main/python/matrix_test.py | 77 ++++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 src/main/python/matrix_test.py diff --git a/src/main/python/keyboard_comm.py b/src/main/python/keyboard_comm.py index 5397506..e3f2b17 100644 --- a/src/main/python/keyboard_comm.py +++ b/src/main/python/keyboard_comm.py @@ -26,6 +26,7 @@ CMD_VIA_KEYMAP_GET_BUFFER = 0x12 CMD_VIA_VIAL_PREFIX = 0xFE VIA_LAYOUT_OPTIONS = 0x02 +VIA_SWITCH_MATRIX_STATE = 0x03 CMD_VIAL_GET_KEYBOARD_ID = 0x00 CMD_VIAL_GET_SIZE = 0x01 @@ -500,6 +501,13 @@ class Keyboard: self.usb_send(self.dev, struct.pack("BB", CMD_VIA_VIAL_PREFIX, CMD_VIAL_LOCK), retries=20) + def matrix_poll(self): + if self.via_protocol < 0: + return + + data = self.usb_send(self.dev, struct.pack("BB", CMD_VIA_GET_KEYBOARD_VALUE, VIA_SWITCH_MATRIX_STATE), retries=20) + return data + def macro_serialize(self, macro): """ Serialize a single macro, a macro is made out of macro actions (BasicAction) diff --git a/src/main/python/main_window.py b/src/main/python/main_window.py index 387aff0..624c024 100644 --- a/src/main/python/main_window.py +++ b/src/main/python/main_window.py @@ -16,6 +16,7 @@ from macro_recorder import MacroRecorder from unlocker import Unlocker from util import tr, find_vial_devices, EXAMPLE_KEYBOARDS from vial_device import VialKeyboard +from matrix_test import MatrixTest import themes @@ -51,9 +52,10 @@ class MainWindow(QMainWindow): self.keymap_editor = KeymapEditor(self.layout_editor) self.firmware_flasher = FirmwareFlasher(self) self.macro_recorder = MacroRecorder() + self.matrix_tester = MatrixTest(self.layout_editor) self.editors = [(self.keymap_editor, "Keymap"), (self.layout_editor, "Layout"), (self.macro_recorder, "Macros"), - (self.firmware_flasher, "Firmware updater")] + (self.matrix_tester, "Matrix tester"), (self.firmware_flasher, "Firmware updater")] Unlocker.global_layout_editor = self.layout_editor self.tabs = QTabWidget() @@ -234,7 +236,7 @@ class MainWindow(QMainWindow): Unlocker.unlock(self.current_device.keyboard) self.current_device.keyboard.reload() - for e in [self.layout_editor, self.keymap_editor, self.firmware_flasher, self.macro_recorder]: + for e in [self.layout_editor, self.keymap_editor, self.firmware_flasher, self.macro_recorder, self.matrix_tester]: e.rebuild(self.current_device) def refresh_tabs(self): diff --git a/src/main/python/matrix_test.py b/src/main/python/matrix_test.py new file mode 100644 index 0000000..33131c4 --- /dev/null +++ b/src/main/python/matrix_test.py @@ -0,0 +1,77 @@ +from PyQt5.QtWidgets import QVBoxLayout, QPushButton +from PyQt5.QtCore import Qt, QTimer +import struct +import math + +from basic_editor import BasicEditor +from keyboard_widget import KeyboardWidget +from vial_device import VialKeyboard +from unlocker import Unlocker + +class MatrixTest(BasicEditor): + def __init__(self, layout_editor): + super().__init__() + + self.layout_editor = layout_editor + + self.keyboardWidget = KeyboardWidget(layout_editor) + self.startButtonWidget = QPushButton("Start testing") + + layout = QVBoxLayout() + layout.addWidget(self.keyboardWidget) + layout.setAlignment(self.keyboardWidget, Qt.AlignCenter) + + self.addLayout(layout) + 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) + + 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): + return isinstance(self.device, VialKeyboard) + + def matrix_poller(self): + # print(f"Rows: {self.keyboard.rows}") + # print(f"Cols: {self.keyboard.cols}") + rows = self.keyboard.rows + cols = self.keyboard.cols + matrix = [ [None for y in range(cols)] for x in range(rows) ] + + data = self.keyboard.matrix_poll() + + row_size = math.ceil(cols / 8) + + for row in range(rows): + row_data_start = 2 + (row * row_size) + row_data_end = row_data_start + row_size + row_data = data[row_data_start:row_data_end] + + for col in range(cols): + col_byte = math.floor(col / 8) + state = (row_data[col_byte] >> col) & 1 + matrix[row][col] = state + + def start_poller(self): + if not self.polling: + Unlocker.unlock(self.keyboard) + self.startButtonWidget.setText("Stop testing") + self.timer.start(100) + self.polling = True + else: + self.timer.stop() + self.keyboard.lock() + self.startButtonWidget.setText("Start testing") + self.polling = False + From 2a6f43c6d47a417de3adf238bfabc6da6f1ff4d9 Mon Sep 17 00:00:00 2001 From: Pieterv24 <9167905+Pieterv24@users.noreply.github.com> Date: Mon, 19 Apr 2021 00:53:07 +0200 Subject: [PATCH 2/4] added keylogger --- requirements.txt | 4 ++-- src/main/python/matrix_test.py | 15 +++++++++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index d022793..e6ab8ba 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,6 +6,6 @@ keyboard==0.13.5 macholib==1.14 pefile==2019.4.18 PyInstaller==3.4 -PyQt5==5.9.2 +PyQt5==5.15.2 https://github.com/danthedeckie/simpleeval/archive/41c99b8e224a7a0ae0ac59c773598fe79a4470db.zip -sip==4.19.8 +PyQt5-sip==12.8.1 \ No newline at end of file diff --git a/src/main/python/matrix_test.py b/src/main/python/matrix_test.py index 33131c4..c97ea59 100644 --- a/src/main/python/matrix_test.py +++ b/src/main/python/matrix_test.py @@ -15,6 +15,8 @@ class MatrixTest(BasicEditor): self.layout_editor = layout_editor self.keyboardWidget = KeyboardWidget(layout_editor) + self.keyboardWidget.set_enabled(False) + self.startButtonWidget = QPushButton("Start testing") layout = QVBoxLayout() @@ -43,8 +45,6 @@ class MatrixTest(BasicEditor): return isinstance(self.device, VialKeyboard) def matrix_poller(self): - # print(f"Rows: {self.keyboard.rows}") - # print(f"Cols: {self.keyboard.cols}") rows = self.keyboard.rows cols = self.keyboard.cols matrix = [ [None for y in range(cols)] for x in range(rows) ] @@ -63,6 +63,17 @@ class MatrixTest(BasicEditor): state = (row_data[col_byte] >> col) & 1 matrix[row][col] = state + for w in self.keyboardWidget.widgets: + row = w.desc.row + col = w.desc.col + + if row < len(matrix) and col < len(matrix[row]): + w.setActive(matrix[row][col]) + + self.keyboardWidget.update_layout() + self.keyboardWidget.update() + self.keyboardWidget.updateGeometry() + def start_poller(self): if not self.polling: Unlocker.unlock(self.keyboard) From b48fa0d8ad5ade2852232fa3d6302ec2f993c999 Mon Sep 17 00:00:00 2001 From: Pieterv24 <9167905+Pieterv24@users.noreply.github.com> Date: Thu, 22 Apr 2021 21:58:50 +0200 Subject: [PATCH 3/4] Added reset button, minimum Vial-qmk version, and press and pressed indication --- src/main/python/keyboard_widget.py | 20 +++++++++++++++ src/main/python/matrix_test.py | 41 +++++++++++++++++++++++++----- 2 files changed, 54 insertions(+), 7 deletions(-) diff --git a/src/main/python/keyboard_widget.py b/src/main/python/keyboard_widget.py index 75e837a..94968e6 100644 --- a/src/main/python/keyboard_widget.py +++ b/src/main/python/keyboard_widget.py @@ -12,6 +12,7 @@ class KeyWidget: def __init__(self, desc, scale, shift_x=0, shift_y=0): self.active = False self.masked = False + self.pressed = False self.desc = desc self.text = "" self.mask_text = "" @@ -109,6 +110,9 @@ class KeyWidget: def setActive(self, active): self.active = active + def setPressed(self, pressed): + self.pressed = pressed + def setColor(self, color): self.color = color @@ -263,6 +267,16 @@ class KeyboardWidget(QWidget): active_brush.setColor(QApplication.palette().color(QPalette.Highlight)) active_brush.setStyle(Qt.SolidPattern) + # for pressed keycaps + pressed_pen = qp.pen() + pressed_pen_color = QApplication.palette().color(QPalette.HighlightedText).lighter(75) + pressed_pen.setColor(pressed_pen_color) + + pressed_brush = QBrush() + pressed_brush_color = QApplication.palette().color(QPalette.Highlight).lighter(75) + pressed_brush.setColor(pressed_brush_color) + pressed_brush.setStyle(Qt.SolidPattern) + mask_font = qp.font() mask_font.setPointSize(mask_font.pointSize() * 0.8) @@ -280,6 +294,12 @@ class KeyboardWidget(QWidget): qp.setPen(active_pen) qp.setBrush(active_brush) + if key.pressed: + # move key slightly down when pressed + qp.translate(0, 5) + qp.setPen(pressed_pen) + qp.setBrush(pressed_brush) + # draw the keycap qp.drawPath(key.draw_path) qp.strokePath(key.draw_path2, regular_pen) diff --git a/src/main/python/matrix_test.py b/src/main/python/matrix_test.py index c97ea59..28ac550 100644 --- a/src/main/python/matrix_test.py +++ b/src/main/python/matrix_test.py @@ -18,12 +18,14 @@ class MatrixTest(BasicEditor): 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 @@ -32,7 +34,9 @@ class MatrixTest(BasicEditor): 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) @@ -42,33 +46,56 @@ class MatrixTest(BasicEditor): self.keyboardWidget.set_keys(self.keyboard.keys, self.keyboard.encoders) def valid(self): - return isinstance(self.device, VialKeyboard) + # 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): - col_byte = math.floor(col / 8) - state = (row_data[col_byte] >> col) & 1 - matrix[row][col] = state + # 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: row = w.desc.row col = w.desc.col if row < len(matrix) and col < len(matrix[row]): - w.setActive(matrix[row][col]) + w.setPressed(matrix[row][col]) + if matrix[row][col]: + w.setActive(True) self.keyboardWidget.update_layout() self.keyboardWidget.update() @@ -78,7 +105,7 @@ class MatrixTest(BasicEditor): if not self.polling: Unlocker.unlock(self.keyboard) self.startButtonWidget.setText("Stop testing") - self.timer.start(100) + self.timer.start(20) self.polling = True else: self.timer.stop() From 5c4a65bb78d9e1ff068add3e83b8acd5f3e3e136 Mon Sep 17 00:00:00 2001 From: Pieterv24 <9167905+Pieterv24@users.noreply.github.com> Date: Thu, 22 Apr 2021 22:10:00 +0200 Subject: [PATCH 4/4] reverted requirements.txt --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index e6ab8ba..55c96eb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,6 +6,6 @@ keyboard==0.13.5 macholib==1.14 pefile==2019.4.18 PyInstaller==3.4 -PyQt5==5.15.2 +PyQt5==5.9.2 https://github.com/danthedeckie/simpleeval/archive/41c99b8e224a7a0ae0ac59c773598fe79a4470db.zip -PyQt5-sip==12.8.1 \ No newline at end of file +sip==4.19.8 \ No newline at end of file