Created
May 30, 2024 20:52
-
-
Save VoxelPrismatic/9f471014ed3de03f8260d83160577030 to your computer and use it in GitHub Desktop.
Terminal based keyboard visualizer based on Evdev. Tested in Linux+Wayland.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# DESCRIPTION: | |
# Terminal based keyboard visualizer based on evdev. Tested in Linux + Wayland. | |
# Do not contact for support. This is my tool | |
# IMPORTANT NOTES: | |
# 1. Must be py3.10; evdev will not build for new versions of python | |
# 2. Use a nerd font. I used some of their symbols | |
# 3. This MUST be run as root. Devices cannot be read by normal users | |
import evdev | |
import os | |
import time | |
import re | |
history = [] | |
scancodes = { | |
# Scancode: ASCIICode | |
0: None, 1: u'ESC', 2: u'1', 3: u'2', 4: u'3', 5: u'4', 6: u'5', 7: u'6', 8: u'7', 9: u'8', | |
10: u'9', 11: u'0', 12: u'-', 13: u'=', 14: u'', 15: u'TAB', 16: u'Q', 17: u'W', 18: u'E', 19: u'R', | |
20: u'T', 21: u'Y', 22: u'U', 23: u'I', 24: u'O', 25: u'P', 26: u'[', 27: u']', 28: u'↲', 29: u'CTRL', | |
30: u'A', 31: u'S', 32: u'D', 33: u'F', 34: u'G', 35: u'H', 36: u'J', 37: u'K', 38: u'L', 39: u';', | |
40: u'"', 41: u'`', 42: u'SHIFT', 43: u'\\', 44: u'Z', 45: u'X', 46: u'C', 47: u'V', 48: u'B', 49: u'N', | |
50: u'M', 51: u',', 52: u'.', 53: u'/', 54: u'SHIFT', 56: u'ALT', 100: u'ALT', 105: u'←', 106: u'→', | |
103: u'↑', 108: u'↓', 111: u'', 57: u'…', 97: u'CTRL', 125: u'META', 102: u'HOME', 107: u'END', | |
104: u'PGUP', 109: u'PGDN', 110: u'INS', 59: u'F1', 60: u'F2', 61: u'F3', 62: u'F4', 63: u'F5', 64: u'F6', | |
65: u'F7', 66: u'F8', 67: u'F9', 68: u'F10', 87: u'F11', 88: u'F12', | |
} | |
shifts = { | |
"a": "A", "b": "B", "c": "C", "d": "D", "e": "E", "f": "F", "g": "G", "h": "H", "i": "I", "j": "J", | |
"k": "K", "l": "L", "m": "M", "n": "N", "o": "O", "p": "P", "q": "Q", "r": "R", "s": "S", "t": "T", | |
"u": "U", "v": "V", "w": "W", "x": "X", "y": "Y", "z": "Z", "`": "~", "1": "!", "2": "@", "3": "#", | |
"4": "$", "5": "%", "6": "^", "7": "&", "8": "*", "9": "(", "0": ")", "-": "_", "=": "+", "[": "{", | |
"]": "}", "\\": "|", ";": ":", "'": '"', ",": "<", ".": ">", "/": "?" | |
} | |
mods = [ | |
"\x1b[94;1m<SHIFT>\x1b[0m", | |
"\x1b[94;1m<CTRL>\x1b[0m", | |
"\x1b[94;1m<ALT>\x1b[0m", | |
"\x1b[94;1m<META>\x1b[0m" | |
] | |
def grab_keyboards(): | |
devices = [evdev.InputDevice(fn) for fn in evdev.list_devices()] | |
keyboards = [] | |
for device in devices: | |
caps = device.capabilities(verbose = True) | |
if ("EV_KEY", 1) not in caps: | |
continue | |
for key in caps[("EV_KEY", 1)]: | |
if key[0] == "KEY_ESC": | |
break | |
else: | |
continue | |
keyboards.append(device) | |
print("\n\n") | |
return keyboards | |
def decode_event(kb, evt: evdev.events.InputEvent) -> str: | |
if evt.value == 0: # key released | |
return | |
evt = evdev.categorize(evt) | |
st = "" | |
if evt.scancode in scancodes: | |
st = scancodes[evt.scancode] | |
if len(st) > 1: | |
st = "\x1b[94;1m<" + st + ">\x1b[0m" | |
elif ord(st) > 255: | |
st = "\x1b[94;1m" + st + "\x1b[0m" | |
else: | |
st = st.lower() | |
else: | |
st = f"\x1b[92;1m<{evt.scancode}:" + evt.keycode + ">\x1b[0m" | |
if st in mods: | |
return | |
mod_shift = False | |
mod_ctrl = False | |
mod_alt = False | |
mod_meta = False | |
for key in kb.active_keys(verbose = True): | |
match key[0]: | |
case "KEY_LEFTSHIFT" | "KEY_RIGHTSHIFT": | |
mod_shift = True | |
case "KEY_LEFTCTRL" | "KEY_RIGHTCTRL": | |
mod_ctrl = True | |
case "KEY_LEFTALT" | "KEY_RIGHTALT": | |
mod_alt = True | |
case "KEY_LEFTMETA" | "KEY_RIGHTMETA": | |
mod_meta = True | |
if mod_shift and len(st) == 1: | |
st = shifts.get(st, st) | |
mod_shift = False | |
if mod_ctrl: | |
st = "\x1b[91;1m\x1b[0m" + st | |
if mod_alt: | |
st = "\x1b[91;1m\x1b[0m" + st | |
if mod_meta: | |
st = "\x1b[91;1m\ue00a\x1b[0m" + st | |
if mod_shift: | |
st = "\x1b[91;1m⮭\x1b[0m" + st | |
history.insert(0, st) | |
def print_history(): | |
global history | |
st = "" | |
w = os.get_terminal_size().columns | |
ln = 0 | |
ls = [] | |
for k in history: | |
s = re.sub("\x1b\\[(0|\\d+;\\d+)m", "", k) | |
ln += len(s) + 1 | |
if ln > w: | |
history = ls | |
return print("\x1b[H\x1b[2J" + st.strip()) | |
st = k + " " + st | |
ls.append(k) | |
print("\x1b[H\x1b[2J" + (" " * (w - ln)) + st.strip()) | |
def main(): | |
keyboards = grab_keyboards() | |
if len(keyboards) == 0: | |
print("No keyboards found. Try running as \x1b[91;1mroot\x1b[0m.") | |
return | |
keyboard = keyboards[0] | |
if len(keyboards) > 1: | |
for i, kb in enumerate(keyboards): | |
print(f"{i}: {kb.name}") | |
i = -1 | |
while i < 0 or i >= len(keyboards): | |
i = int(input("Select keyboard: ")) | |
keyboard = keyboards[i] | |
print(keyboard.name) | |
t = time.time() | |
for evt in keyboard.read_loop(): | |
if evt.type == evdev.ecodes.EV_KEY: | |
if time.time() - t > 3: | |
global history | |
history = [] | |
decode_event(keyboard, evt) | |
print_history() | |
t = time.time() | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment