german qwertz mode 1 no modifier mapping
This commit is contained in:
+87
-22
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user