nixos/frame12/dump-matrix.py

225 lines
5.6 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Dump the full Framework EC keyboard matrix.
Reads all matrix positions and prints those with non-zero scancodes.
Usage:
sudo python3 dump-matrix.py
# or on NixOS:
# nix-shell -p python3 --run 'sudo python3 dump-matrix.py'
"""
import subprocess
import sys
# PS/2 Scan Code Set 2 -> Key name mapping
SCANCODE_NAMES = {
0x00: "(none)",
0x01: "F9",
0x03: "F5",
0x04: "F3",
0x05: "F1",
0x06: "F2",
0x07: "F12",
0x09: "F10",
0x0A: "F8",
0x0B: "F6",
0x0C: "F4",
0x0D: "Tab",
0x0E: "`",
0x11: "L Alt",
0x12: "L Shift",
0x14: "L Ctrl",
0x15: "q",
0x16: "1",
0x1A: "z",
0x1B: "s",
0x1C: "a",
0x1D: "w",
0x1E: "2",
0x21: "c",
0x22: "x",
0x23: "d",
0x24: "e",
0x25: "4",
0x26: "3",
0x29: "Space",
0x2A: "v",
0x2B: "f",
0x2C: "t",
0x2D: "r",
0x2E: "5",
0x31: "n",
0x32: "b",
0x33: "h",
0x34: "g",
0x35: "y",
0x36: "6",
0x3A: "m",
0x3B: "j",
0x3C: "u",
0x3D: "7",
0x3E: "8",
0x41: ",",
0x42: "k",
0x43: "i",
0x44: "o",
0x45: "0",
0x46: "9",
0x49: ".",
0x4A: "/",
0x4B: "l",
0x4C: ";",
0x4D: "p",
0x4E: "-",
0x52: "'",
0x54: "[",
0x55: "=",
0x58: "Caps Lock",
0x59: "R Shift",
0x5A: "Enter",
0x5B: "]",
0x5D: "\\",
0x66: "Backspace",
0x69: "KP 1",
0x6B: "KP 4",
0x6C: "KP 7",
0x70: "KP 0",
0x71: "KP .",
0x72: "KP 2",
0x73: "KP 5",
0x74: "KP 6",
0x75: "KP 8",
0x76: "Esc",
0x77: "Num Lock",
0x78: "F11",
0x79: "KP +",
0x7A: "KP 3",
0x7B: "KP -",
0x7C: "KP *",
0x7D: "KP 9",
0x7E: "Scroll Lock",
0x83: "F7",
0xFF: "Fn",
# Extended keys (E0 prefix) - stored as 16-bit values
0xE011: "R Alt",
0xE014: "R Ctrl",
0xE01F: "L GUI",
0xE027: "R GUI",
0xE02F: "Apps/Menu",
0xE04A: "KP /",
0xE05A: "KP Enter",
0xE069: "End",
0xE06B: "Left Arrow",
0xE06C: "Home",
0xE070: "Insert",
0xE071: "Delete",
0xE072: "Down Arrow",
0xE074: "Right Arrow",
0xE075: "Up Arrow",
0xE07A: "Page Down",
0xE07D: "Page Up",
}
def get_scancode_name(code):
if code in SCANCODE_NAMES:
return SCANCODE_NAMES[code]
return f"unknown"
def read_matrix_position(row, col):
"""Read the scancode at a given matrix position. Returns the 16-bit scancode."""
col_hex = format(col, 'x')
cmd = f"ectool raw 0x3E0C d1,d0,b{row:x},b{col_hex},w0"
try:
result = subprocess.run(
cmd.split(),
capture_output=True, text=True, timeout=5
)
output = result.stdout + result.stderr
# Parse the response bytes. Look for the "Read XXX bytes" line and
# then parse the hex dump that follows.
lines = output.strip().split('\n')
response_bytes = []
reading = False
for line in lines:
if "Read" in line and "bytes" in line:
reading = True
continue
if reading and '|' in line:
# Extract hex bytes before the |
hex_part = line.split('|')[0].strip()
for byte_str in hex_part.split():
if len(byte_str) == 2:
try:
response_bytes.append(int(byte_str, 16))
except ValueError:
pass
# Scancode is at bytes 10-11 (little-endian 16-bit)
if len(response_bytes) >= 12:
scancode = response_bytes[10] | (response_bytes[11] << 8)
return scancode
return None
except (subprocess.TimeoutExpired, Exception) as e:
print(f" Error reading ({row},{col:x}): {e}", file=sys.stderr)
return None
def main():
if subprocess.os.geteuid() != 0:
print("This script must be run as root (sudo).", file=sys.stderr)
sys.exit(1)
# Scan a wide range: rows 0-15, cols 0-15
# This covers well beyond the assumed 8x12 matrix
max_row = 16
max_col = 16
print(f"Scanning matrix positions ({max_row} rows x {max_col} cols)...")
print(f"{'Pos':>8} {'Scancode':>10} Key")
print("-" * 40)
found = []
for row in range(max_row):
for col in range(max_col):
scancode = read_matrix_position(row, col)
if scancode is not None and scancode != 0:
name = get_scancode_name(scancode)
label = f"({row:x},{col:x})"
sc_str = f"{scancode:04x}" if scancode > 0xFF else f"{scancode:02x}"
print(f"{label:>8} {sc_str:>10} {name}")
found.append((row, col, scancode, name))
# Print progress on stderr
print(f"\r Scanning ({row:x},{col:x})... ", end='', file=sys.stderr, flush=True)
print("\r ", file=sys.stderr)
print("-" * 40)
print(f"Found {len(found)} mapped positions.")
# Print a visual grid
print("\n=== Matrix Grid ===")
print(f"{'':>6}", end='')
for col in range(max_col):
print(f" {col:x} ", end='')
print()
for row in range(max_row):
print(f" {row:x} ", end='')
for col in range(max_col):
match = None
for r, c, sc, name in found:
if r == row and c == col:
match = name
break
if match:
print(f" {match:>6}", end='')
else:
print(f" .", end='')
print()
if __name__ == "__main__":
main()