Skip to content

Instantly share code, notes, and snippets.

@VoxelPrismatic
Created May 30, 2024 20:52
Show Gist options
  • Save VoxelPrismatic/9f471014ed3de03f8260d83160577030 to your computer and use it in GitHub Desktop.
Save VoxelPrismatic/9f471014ed3de03f8260d83160577030 to your computer and use it in GitHub Desktop.
Terminal based keyboard visualizer based on Evdev. Tested in Linux+Wayland.
# 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