german qwertz mode 1 no modifier mapping

This commit is contained in:
Jörg Breitbart
2026-05-13 17:30:15 +02:00
parent d844070855
commit 0ca8107055
2 changed files with 719 additions and 22 deletions
+87 -22
View File
@@ -1,9 +1,11 @@
from termios import tcgetattr, tcsetattr, TCSADRAIN, TIOCGWINSZ
from termios import tcgetattr, tcsetattr, TCSADRAIN, TIOCGWINSZ, TCSAFLUSH
from tty import setcbreak, setraw
import os
import sys
from contextlib import contextmanager
from select import select
from time import sleep
from json import dumps, loads
from typing import Optional, Tuple, Union
@@ -23,7 +25,7 @@ class TerminalContext:
return TerminalContext(fd, True)
def close(self) -> None:
tcsetattr(self.fd, TCSADRAIN, self._initial_attr)
tcsetattr(self.fd, TCSAFLUSH, self._initial_attr)
if self.close_fd:
os.close(self.fd)
@@ -45,11 +47,11 @@ class TerminalContext:
return
tattr = self.termios_attributes
try:
setcbreak(self.fd, TCSADRAIN)
setcbreak(self.fd, TCSAFLUSH)
self.is_cbreak = True
yield
finally:
tcsetattr(self.fd, TCSADRAIN, tattr)
tcsetattr(self.fd, TCSAFLUSH, tattr)
self.is_cbreak = False
@contextmanager
@@ -62,11 +64,11 @@ class TerminalContext:
return
tattr = self.termios_attributes
try:
setraw(self.fd, TCSADRAIN)
setraw(self.fd, TCSAFLUSH)
self.is_raw = True
yield
finally:
tcsetattr(self.fd, TCSADRAIN, tattr)
tcsetattr(self.fd, TCSAFLUSH, tattr)
self.is_raw = False
@contextmanager
@@ -82,11 +84,12 @@ class TerminalContext:
if undo:
undo()
def write(self, s: Union[str, bytes]) -> None:
def write(self, data: Union[str, bytes]) -> None:
"""
Write string or bytes directly to the terminal.
"""
data = s if isinstance(s, bytes) else s.encode('utf-8')
if isinstance(data, str):
data = data.encode('utf-8')
sent = os.write(self.fd, data)
while sent:
data = data[sent:]
@@ -99,7 +102,9 @@ class TerminalContext:
empty bytes are returned.
"""
can_read, _, _ = select([self.fd], [], [], timeout)
return os.read(self.fd, amount) if can_read else b''
if can_read:
return os.read(self.fd, amount)
return b''
@contextmanager
@@ -111,21 +116,81 @@ def cterminal_context():
t.close()
with cterminal_context() as term:
with term.custom_state(undo=lambda:term.write('\x1b[<u')), term.raw_mode():
term.write('\x1b[>3u')
sleep(1)
print('PRESS (within 10s) and HOLD (for 5s)\r')
data = []
cur = term.read(timeout=10)
def extract_events(data: list[str]):
if len(data) < 5:
raise Exception('not enough reports')
types = set(data)
if len(types) == 1:
return {'PRESS': data[0], 'REPEAT': data[0], 'RELEASE': None}
if len(types) == 2:
last = data.pop()
if last != data[0] and len(set(data)) == 1:
return {'PRESS': data[0], 'REPEAT': data[0], 'RELEASE': last}
raise Exception('weird reports, 2 types')
if len(types) == 3:
first = data.pop(0)
last = data.pop()
if first != last and first != data[0] and last != data[0]:
return {'PRESS': first, 'REPEAT': data[0], 'RELEASE': last}
raise Exception('weird reports, 3 types')
raise Exception('more than 3 types')
def query(term: TerminalContext, mode: int):
with term.custom_state(undo=lambda:term.write('\x1b[<u')):
term.write(f'\x1b[>{mode}u')
sleep(.1)
print('PRESS (within 5s) and HOLD (for 5s)\r')
data: list[bytes] = []
cur = term.read(timeout=5)
while cur:
data.append(cur)
cur = term.read(timeout=1)
cur = term.read(timeout=.5)
if len(data) > 5:
print('RELEASE\r')
sleep(0.5)
term.read(timeout=0.1)
print(data, '\r')
print('RELEASE\r', end='')
try:
return extract_events([b.decode('utf-8') for b in data])
except Exception as e:
print('Error:', e, '\r')
print(data, '\r')
term.read(timeout=.1)
sleep(1)
def save(filedata: dict[str, dict[str, str]], filename, entry, mode, events):
try:
filedata[entry][mode] = events
except KeyError:
filedata[entry] = {mode: events}
with open(filename, 'w') as f:
f.write(dumps(filedata, indent=2))
def main():
mode = 1
filedata = {}
print(sys.argv)
if len(sys.argv) != 3:
print('ERROR: not enough arguments')
print('Usage: python kitty_keytester.py <mode> <json-file>')
return
mode = int(sys.argv[1])
filename = sys.argv[2]
if os.path.exists(filename):
with open(filename) as f:
filedata = loads(f.read())
with cterminal_context() as term:
while True:
with term.raw_mode():
events = query(term, mode)
if events:
print(events)
entry = input('Entry: ')
if entry:
save(filedata, filename, entry, str(mode), events)
cont = input('Continue? (y)')
if cont not in ['y', '']:
break
if __name__ == '__main__':
main()
@@ -0,0 +1,632 @@
{
"Escape": {
"1": {
"PRESS": "\u001b[27u",
"REPEAT": "\u001b[27u",
"RELEASE": null
}
},
"F1": {
"1": {
"PRESS": "\u001b[P",
"REPEAT": "\u001b[P",
"RELEASE": null
}
},
"F2": {
"1": {
"PRESS": "\u001b[Q",
"REPEAT": "\u001b[Q",
"RELEASE": null
}
},
"F3": {
"1": {
"PRESS": "\u001b[13~",
"REPEAT": "\u001b[13~",
"RELEASE": null
}
},
"F4": {
"1": {
"PRESS": "\u001b[S",
"REPEAT": "\u001b[S",
"RELEASE": null
}
},
"F5": {
"1": {
"PRESS": "\u001b[15~",
"REPEAT": "\u001b[15~",
"RELEASE": null
}
},
"F6": {
"1": {
"PRESS": "\u001b[17~",
"REPEAT": "\u001b[17~",
"RELEASE": null
}
},
"F7": {
"1": {
"PRESS": "\u001b[18~",
"REPEAT": "\u001b[18~",
"RELEASE": null
}
},
"F8": {
"1": {
"PRESS": "\u001b[19~",
"REPEAT": "\u001b[19~",
"RELEASE": null
}
},
"F9": {
"1": {
"PRESS": "\u001b[20~",
"REPEAT": "\u001b[20~",
"RELEASE": null
}
},
"F10": {
"1": {
"PRESS": "\u001b[21~",
"REPEAT": "\u001b[21~",
"RELEASE": null
}
},
"F11": {
"1": {
"PRESS": "\u001b[23~",
"REPEAT": "\u001b[23~",
"RELEASE": null
}
},
"F12": {
"1": {
"PRESS": "\u001b[24~",
"REPEAT": "\u001b[24~",
"RELEASE": null
}
},
"Insert": {
"1": {
"PRESS": "\u001b[2~",
"REPEAT": "\u001b[2~",
"RELEASE": null
}
},
"Delete": {
"1": {
"PRESS": "\u001b[3~",
"REPEAT": "\u001b[3~",
"RELEASE": null
}
},
"PageUp": {
"1": {
"PRESS": "\u001b[5~",
"REPEAT": "\u001b[5~",
"RELEASE": null
}
},
"PageDown": {
"1": {
"PRESS": "\u001b[6~",
"REPEAT": "\u001b[6~",
"RELEASE": null
}
},
"Home": {
"1": {
"PRESS": "\u001b[H",
"REPEAT": "\u001b[H",
"RELEASE": null
}
},
"End": {
"1": {
"PRESS": "\u001b[F",
"REPEAT": "\u001b[F",
"RELEASE": null
}
},
"Dead (Backquote)": {
"1": {
"PRESS": "^",
"REPEAT": "^",
"RELEASE": null
}
},
"1 (Digit1)": {
"1": {
"PRESS": "1",
"REPEAT": "1",
"RELEASE": null
}
},
"2 (Digit2)": {
"1": {
"PRESS": "2",
"REPEAT": "2",
"RELEASE": null
}
},
"3 (Digit3)": {
"1": {
"PRESS": "3",
"REPEAT": "3",
"RELEASE": null
}
},
"4 (Digit4)": {
"1": {
"PRESS": "4",
"REPEAT": "4",
"RELEASE": null
}
},
"5 (Digit5)": {
"1": {
"PRESS": "5",
"REPEAT": "5",
"RELEASE": null
}
},
"6 (Digit6)": {
"1": {
"PRESS": "6",
"REPEAT": "6",
"RELEASE": null
}
},
"7 (Digit7)": {
"1": {
"PRESS": "7",
"REPEAT": "7",
"RELEASE": null
}
},
"8 (Digit8)": {
"1": {
"PRESS": "8",
"REPEAT": "8",
"RELEASE": null
}
},
"9 (Digit9)": {
"1": {
"PRESS": "9",
"REPEAT": "9",
"RELEASE": null
}
},
"0 (Digit0)": {
"1": {
"PRESS": "0",
"REPEAT": "0",
"RELEASE": null
}
},
"\u00df (Minus)": {
"1": {
"PRESS": "\u00df",
"REPEAT": "\u00df",
"RELEASE": null
}
},
"Dead (Equal)": {
"1": {
"PRESS": "\u00b4",
"REPEAT": "\u00b4",
"RELEASE": null
}
},
"Backspace": {
"1": {
"PRESS": "\u007f",
"REPEAT": "\u007f",
"RELEASE": null
}
},
"Tab": {
"1": {
"PRESS": "\t",
"REPEAT": "\t",
"RELEASE": null
}
},
"q (KeyQ)": {
"1": {
"PRESS": "q",
"REPEAT": "q",
"RELEASE": null
}
},
"w (KeyW)": {
"1": {
"PRESS": "w",
"REPEAT": "w",
"RELEASE": null
}
},
"e (KeyE)": {
"1": {
"PRESS": "e",
"REPEAT": "e",
"RELEASE": null
}
},
"r (KeyR)": {
"1": {
"PRESS": "r",
"REPEAT": "r",
"RELEASE": null
}
},
"t (KeyT)": {
"1": {
"PRESS": "t",
"REPEAT": "t",
"RELEASE": null
}
},
"z (KeyY)": {
"1": {
"PRESS": "z",
"REPEAT": "z",
"RELEASE": null
}
},
"u (KeyU)": {
"1": {
"PRESS": "u",
"REPEAT": "u",
"RELEASE": null
}
},
"i (KeyI)": {
"1": {
"PRESS": "i",
"REPEAT": "i",
"RELEASE": null
}
},
"o (KeyO)": {
"1": {
"PRESS": "o",
"REPEAT": "o",
"RELEASE": null
}
},
"p (KeyP)": {
"1": {
"PRESS": "p",
"REPEAT": "p",
"RELEASE": null
}
},
"\u00fc (BracketLeft)": {
"1": {
"PRESS": "\u00fc",
"REPEAT": "\u00fc",
"RELEASE": null
}
},
"+ (BracketRight)": {
"1": {
"PRESS": "+",
"REPEAT": "+",
"RELEASE": null
}
},
"Enter": {
"1": {
"PRESS": "\r",
"REPEAT": "\r",
"RELEASE": null
}
},
"a (KeyA)": {
"1": {
"PRESS": "a",
"REPEAT": "a",
"RELEASE": null
}
},
"s (KeyS)": {
"1": {
"PRESS": "s",
"REPEAT": "s",
"RELEASE": null
}
},
"d (KeyD)": {
"1": {
"PRESS": "d",
"REPEAT": "d",
"RELEASE": null
}
},
"f (KeyF)": {
"1": {
"PRESS": "f",
"REPEAT": "f",
"RELEASE": null
}
},
"g (KeyG)": {
"1": {
"PRESS": "g",
"REPEAT": "g",
"RELEASE": null
}
},
"h (KeyH)": {
"1": {
"PRESS": "h",
"REPEAT": "h",
"RELEASE": null
}
},
"j (KeyJ)": {
"1": {
"PRESS": "j",
"REPEAT": "j",
"RELEASE": null
}
},
"k (KeyK)": {
"1": {
"PRESS": "k",
"REPEAT": "k",
"RELEASE": null
}
},
"l (KeyL)": {
"1": {
"PRESS": "l",
"REPEAT": "l",
"RELEASE": null
}
},
"\u00f6 (Semicolon)": {
"1": {
"PRESS": "\u00f6",
"REPEAT": "\u00f6",
"RELEASE": null
}
},
"\u00e4 (Quote)": {
"1": {
"PRESS": "\u00e4",
"REPEAT": "\u00e4",
"RELEASE": null
}
},
"# (Backslash)": {
"1": {
"PRESS": "#",
"REPEAT": "#",
"RELEASE": null
}
},
"< (IntlBackslash)": {
"1": {
"PRESS": "<",
"REPEAT": "<",
"RELEASE": null
}
},
"y (KeyZ)": {
"1": {
"PRESS": "y",
"REPEAT": "y",
"RELEASE": null
}
},
"x (KeyX)": {
"1": {
"PRESS": "x",
"REPEAT": "x",
"RELEASE": null
}
},
"c (KeyC)": {
"1": {
"PRESS": "c",
"REPEAT": "c",
"RELEASE": null
}
},
"v (KeyV)": {
"1": {
"PRESS": "v",
"REPEAT": "v",
"RELEASE": null
}
},
"b (KeyB)": {
"1": {
"PRESS": "b",
"REPEAT": "b",
"RELEASE": null
}
},
"n (KeyN)": {
"1": {
"PRESS": "n",
"REPEAT": "n",
"RELEASE": null
}
},
"m (KeyM)": {
"1": {
"PRESS": "m",
"REPEAT": "m",
"RELEASE": null
}
},
", (Comma)": {
"1": {
"PRESS": ",",
"REPEAT": ",",
"RELEASE": null
}
},
". (Period)": {
"1": {
"PRESS": ".",
"REPEAT": ".",
"RELEASE": null
}
},
"- (Slash)": {
"1": {
"PRESS": "-",
"REPEAT": "-",
"RELEASE": null
}
},
"ArrowLeft": {
"1": {
"PRESS": "\u001b[D",
"REPEAT": "\u001b[D",
"RELEASE": null
}
},
"ArrowUp": {
"1": {
"PRESS": "\u001b[A",
"REPEAT": "\u001b[A",
"RELEASE": null
}
},
"ArrowRight": {
"1": {
"PRESS": "\u001b[C",
"REPEAT": "\u001b[C",
"RELEASE": null
}
},
"ArrowDown": {
"1": {
"PRESS": "\u001b[B",
"REPEAT": "\u001b[B",
"RELEASE": null
}
},
"/ (NumpadDivide)": {
"1": {
"PRESS": "/",
"REPEAT": "/",
"RELEASE": null
}
},
"* (NumpadMultiply)": {
"1": {
"PRESS": "*",
"REPEAT": "*",
"RELEASE": null
}
},
"- (NumpadSubtract)": {
"1": {
"PRESS": "-",
"REPEAT": "-",
"RELEASE": null
}
},
"Home (Numpad7)": {
"1": {
"PRESS": "\u001b[57423u",
"REPEAT": "\u001b[57423u",
"RELEASE": null
}
},
"ArrowUp (Numpad8)": {
"1": {
"PRESS": "\u001b[57419u",
"REPEAT": "\u001b[57419u",
"RELEASE": null
}
},
"PageUp (Numpad9)": {
"1": {
"PRESS": "\u001b[57421u",
"REPEAT": "\u001b[57421u",
"RELEASE": null
}
},
"+ (NumpadAdd)": {
"1": {
"PRESS": "+",
"REPEAT": "+",
"RELEASE": null
}
},
"ArrowLeft (Numpad4)": {
"1": {
"PRESS": "\u001b[57417u",
"REPEAT": "\u001b[57417u",
"RELEASE": null
}
},
"Clear (Numpad5)": {
"1": {
"PRESS": "\u001b[E",
"REPEAT": "\u001b[E",
"RELEASE": null
}
},
"ArrowRight (Numpad6)": {
"1": {
"PRESS": "\u001b[57418u",
"REPEAT": "\u001b[57418u",
"RELEASE": null
}
},
"End (Numpad1)": {
"1": {
"PRESS": "\u001b[57424u",
"REPEAT": "\u001b[57424u",
"RELEASE": null
}
},
"ArrowDown (Numpad2)": {
"1": {
"PRESS": "\u001b[57420u",
"REPEAT": "\u001b[57420u",
"RELEASE": null
}
},
"PageDown (Numpad3)": {
"1": {
"PRESS": "\u001b[57422u",
"REPEAT": "\u001b[57422u",
"RELEASE": null
}
},
"Enter (NumpadEnter)": {
"1": {
"PRESS": "\u001b[57414u",
"REPEAT": "\u001b[57414u",
"RELEASE": null
}
},
"Insert (Numpad0)": {
"1": {
"PRESS": "\u001b[57425u",
"REPEAT": "\u001b[57425u",
"RELEASE": null
}
},
"\u0000 (NumpadDecimal)": {
"1": {
"PRESS": "\u001b[57426u",
"REPEAT": "\u001b[57426u",
"RELEASE": null
}
}
}