From d84407085545e4dd8af473f561ee7961fbd53d96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 12 May 2026 15:35:54 +0200 Subject: [PATCH 1/8] draft of keytester --- bin/kitty_keytester.py | 131 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 bin/kitty_keytester.py diff --git a/bin/kitty_keytester.py b/bin/kitty_keytester.py new file mode 100644 index 0000000000..8976c133b0 --- /dev/null +++ b/bin/kitty_keytester.py @@ -0,0 +1,131 @@ +from termios import tcgetattr, tcsetattr, TCSADRAIN, TIOCGWINSZ +from tty import setcbreak, setraw +import os +from contextlib import contextmanager +from select import select +from time import sleep +from typing import Optional, Tuple, Union + + +class TerminalContext: + def __init__(self, fd: int, close_fd=False) -> None: + if not os.isatty(fd): + raise TypeError('fd is not a terminal') + self.close_fd = close_fd + self.fd = fd + self.is_cbreak = False + self.is_raw = False + self._initial_attr = self.termios_attributes + + @classmethod + def from_cterm(cls): + fd = os.open(os.ctermid(), os.O_RDWR) + return TerminalContext(fd, True) + + def close(self) -> None: + tcsetattr(self.fd, TCSADRAIN, self._initial_attr) + if self.close_fd: + os.close(self.fd) + + @property + def termios_attributes(self) -> list: + return tcgetattr(self.fd) + + @property + def ttyname(self) -> str: + return os.ttyname(self.fd) + + @contextmanager + def cbreak_mode(self): + """ + Enter cbreak mode context. + """ + if self.is_cbreak: + yield + return + tattr = self.termios_attributes + try: + setcbreak(self.fd, TCSADRAIN) + self.is_cbreak = True + yield + finally: + tcsetattr(self.fd, TCSADRAIN, tattr) + self.is_cbreak = False + + @contextmanager + def raw_mode(self): + """ + Enter cbreak mode context. + """ + if self.is_raw: + yield + return + tattr = self.termios_attributes + try: + setraw(self.fd, TCSADRAIN) + self.is_raw = True + yield + finally: + tcsetattr(self.fd, TCSADRAIN, tattr) + self.is_raw = False + + @contextmanager + def custom_state(self, undo=None): + """ + Enter custom terminal state, that needs to to be undone by ``undo``. + Useful, if you want to apply a custom terminal state and + have to make sure, that it gets properly reset to previous state. + """ + try: + yield + finally: + if undo: + undo() + + def write(self, s: Union[str, bytes]) -> None: + """ + Write string or bytes directly to the terminal. + """ + data = s if isinstance(s, bytes) else s.encode('utf-8') + sent = os.write(self.fd, data) + while sent: + data = data[sent:] + sent = os.write(self.fd, data) + + def read(self, amount: int = 1024, timeout: Optional[float] = None) -> bytes: + """ + Blocking read from the terminal. + If nothing was sent from the terminal within ``timeout``, + empty bytes are returned. + """ + can_read, _, _ = select([self.fd], [], [], timeout) + return os.read(self.fd, amount) if can_read else b'' + + +@contextmanager +def cterminal_context(): + t = TerminalContext.from_cterm() + try: + yield t + finally: + t.close() + + +with cterminal_context() as term: + with term.custom_state(undo=lambda:term.write('\x1b[3u') + sleep(1) + print('PRESS (within 10s) and HOLD (for 5s)\r') + data = [] + cur = term.read(timeout=10) + while cur: + data.append(cur) + cur = term.read(timeout=1) + if len(data) > 5: + print('RELEASE\r') + sleep(0.5) + term.read(timeout=0.1) + print(data, '\r') + + sleep(1) + From 0ca8107055eb90932fefd1e85995c19dccd5c0a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Wed, 13 May 2026 17:30:15 +0200 Subject: [PATCH 2/8] german qwertz mode 1 no modifier mapping --- bin/kitty_keytester.py | 109 ++- .../german_qwertz_no_modifier.json | 632 ++++++++++++++++++ 2 files changed, 719 insertions(+), 22 deletions(-) create mode 100644 fixtures/kitty_keyboard/german_qwertz_no_modifier.json diff --git a/bin/kitty_keytester.py b/bin/kitty_keytester.py index 8976c133b0..073c34d1c7 100644 --- a/bin/kitty_keytester.py +++ b/bin/kitty_keytester.py @@ -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 @@ def from_cterm(cls): 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 @@ def cbreak_mode(self): 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 @@ def raw_mode(self): 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 @@ def custom_state(self, undo=None): 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 @@ def read(self, amount: int = 1024, timeout: Optional[float] = None) -> bytes: 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[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[{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) + + +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 ') + 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 - sleep(1) +if __name__ == '__main__': + main() diff --git a/fixtures/kitty_keyboard/german_qwertz_no_modifier.json b/fixtures/kitty_keyboard/german_qwertz_no_modifier.json new file mode 100644 index 0000000000..00bb9c9fe5 --- /dev/null +++ b/fixtures/kitty_keyboard/german_qwertz_no_modifier.json @@ -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 + } + } +} \ No newline at end of file From 33eb1dd45e18360be203f82a96f22b7ce70be06f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Wed, 13 May 2026 20:16:33 +0200 Subject: [PATCH 3/8] add modifier keys --- bin/kitty_keytester.py | 12 ++-- .../german_qwertz_no_modifier.json | 70 +++++++++++++++++++ 2 files changed, 76 insertions(+), 6 deletions(-) diff --git a/bin/kitty_keytester.py b/bin/kitty_keytester.py index 073c34d1c7..1575d3bb2a 100644 --- a/bin/kitty_keytester.py +++ b/bin/kitty_keytester.py @@ -25,7 +25,7 @@ def from_cterm(cls): return TerminalContext(fd, True) def close(self) -> None: - tcsetattr(self.fd, TCSAFLUSH, self._initial_attr) + tcsetattr(self.fd, TCSADRAIN, self._initial_attr) if self.close_fd: os.close(self.fd) @@ -47,11 +47,11 @@ def cbreak_mode(self): return tattr = self.termios_attributes try: - setcbreak(self.fd, TCSAFLUSH) + setcbreak(self.fd, TCSADRAIN) self.is_cbreak = True yield finally: - tcsetattr(self.fd, TCSAFLUSH, tattr) + tcsetattr(self.fd, TCSADRAIN, tattr) self.is_cbreak = False @contextmanager @@ -64,11 +64,11 @@ def raw_mode(self): return tattr = self.termios_attributes try: - setraw(self.fd, TCSAFLUSH) + setraw(self.fd, TCSADRAIN) self.is_raw = True yield finally: - tcsetattr(self.fd, TCSAFLUSH, tattr) + tcsetattr(self.fd, TCSADRAIN, tattr) self.is_raw = False @contextmanager @@ -139,7 +139,7 @@ def extract_events(data: list[str]): def query(term: TerminalContext, mode: int): with term.custom_state(undo=lambda:term.write('\x1b[{mode}u') - sleep(.1) + term.read(timeout=.1) print('PRESS (within 5s) and HOLD (for 5s)\r') data: list[bytes] = [] cur = term.read(timeout=5) diff --git a/fixtures/kitty_keyboard/german_qwertz_no_modifier.json b/fixtures/kitty_keyboard/german_qwertz_no_modifier.json index 00bb9c9fe5..472f939ddf 100644 --- a/fixtures/kitty_keyboard/german_qwertz_no_modifier.json +++ b/fixtures/kitty_keyboard/german_qwertz_no_modifier.json @@ -628,5 +628,75 @@ "REPEAT": "\u001b[57426u", "RELEASE": null } + }, + "Numlock": { + "1": { + "PRESS": null, + "REPEAT": null, + "RELEASE": null + } + }, + "CapsLock": { + "1": { + "PRESS": null, + "REPEAT": null, + "RELEASE": null + } + }, + "Shift (ShiftLeft)": { + "1": { + "PRESS": null, + "REPEAT": null, + "RELEASE": null + } + }, + "Shift (ShiftRight)": { + "1": { + "PRESS": null, + "REPEAT": null, + "RELEASE": null + } + }, + "Control (ControlLeft)": { + "1": { + "PRESS": null, + "REPEAT": null, + "RELEASE": null + } + }, + "Meta (MetaLeft)": { + "1": { + "PRESS": null, + "REPEAT": null, + "RELEASE": null + } + }, + "Alt (AltLeft)": { + "1": { + "PRESS": null, + "REPEAT": null, + "RELEASE": null + } + }, + " (Space)": { + "1": { + "PRESS": " ", + "REPEAT": " ", + "RELEASE": null + } + }, + "AltGraph (AltRight)": { + "1": { + "PRESS": null, + "REPEAT": null, + "RELEASE": null + } + }, + "Control (ControlRight)": { + "1": { + "PRESS": null, + "REPEAT": null, + "RELEASE": null + } } } \ No newline at end of file From 6f15b0f916bfd94d5580f57c29a3a372e87b3dce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 14 May 2026 00:43:45 +0200 Subject: [PATCH 4/8] modifier capslock, numlock & shift --- .../german_qwertz_capslock_numlock.json | 373 ++++++++++++++++++ .../kitty_keyboard/german_qwertz_shift.json | 331 ++++++++++++++++ 2 files changed, 704 insertions(+) create mode 100644 fixtures/kitty_keyboard/german_qwertz_capslock_numlock.json create mode 100644 fixtures/kitty_keyboard/german_qwertz_shift.json diff --git a/fixtures/kitty_keyboard/german_qwertz_capslock_numlock.json b/fixtures/kitty_keyboard/german_qwertz_capslock_numlock.json new file mode 100644 index 0000000000..97e063c475 --- /dev/null +++ b/fixtures/kitty_keyboard/german_qwertz_capslock_numlock.json @@ -0,0 +1,373 @@ +{ + "Escape +CapsLock": { + "1": { + "PRESS": "\u001b[27;65u", + "REPEAT": "\u001b[27;65u", + "RELEASE": null + } + }, + "F1 +CapsLock": { + "1": { + "PRESS": "\u001b[1;65P", + "REPEAT": "\u001b[1;65P", + "RELEASE": null + } + }, + "Insert +CapsLock": { + "1": { + "PRESS": "\u001b[2;65~", + "REPEAT": "\u001b[2;65~", + "RELEASE": null + } + }, + "Delete +CapsLock": { + "1": { + "PRESS": "\u001b[3;65~", + "REPEAT": "\u001b[3;65~", + "RELEASE": null + } + }, + "PageUp +CapsLock": { + "1": { + "PRESS": "\u001b[5;65~", + "REPEAT": "\u001b[5;65~", + "RELEASE": null + } + }, + "Dead (Backquote) +CapsLock": { + "1": { + "PRESS": "^", + "REPEAT": "^", + "RELEASE": null + } + }, + "1 (Digit1) +CapsLock": { + "1": { + "PRESS": "1", + "REPEAT": "1", + "RELEASE": null + } + }, + "\u00df (Minus) +CapsLock": { + "1": { + "PRESS": "\u1e9e", + "REPEAT": "\u1e9e", + "RELEASE": null + } + }, + "Dead (Equal) +CapsLock": { + "1": { + "PRESS": "\u00b4", + "REPEAT": "\u00b4", + "RELEASE": null + } + }, + "Backspace +CapsLock": { + "1": { + "PRESS": "\u007f", + "REPEAT": "\u007f", + "RELEASE": null + } + }, + "Tab +CapsLock": { + "1": { + "PRESS": "\t", + "REPEAT": "\t", + "RELEASE": null + } + }, + "Q (KeyQ) +CapsLock": { + "1": { + "PRESS": "Q", + "REPEAT": "Q", + "RELEASE": null + } + }, + "\u00dc (BracketLeft) +CapsLock": { + "1": { + "PRESS": "\u00dc", + "REPEAT": "\u00dc", + "RELEASE": null + } + }, + "+ (BracketRight) +CapsLock": { + "1": { + "PRESS": "+", + "REPEAT": "+", + "RELEASE": null + } + }, + "Enter +CapsLock": { + "1": { + "PRESS": "\r", + "REPEAT": "\r", + "RELEASE": null + } + }, + "\u00d6 (Semicolon) +CapsLock": { + "1": { + "PRESS": "\u00d6", + "REPEAT": "\u00d6", + "RELEASE": null + } + }, + "\u00c4 (Quote) +CapsLock": { + "1": { + "PRESS": "\u00c4", + "REPEAT": "\u00c4", + "RELEASE": null + } + }, + "# (Bashslash) +CapsLock": { + "1": { + "PRESS": "#", + "REPEAT": "#", + "RELEASE": null + } + }, + "< (IntlBackslash) +CapsLock": { + "1": { + "PRESS": "<", + "REPEAT": "<", + "RELEASE": null + } + }, + ", (Comma) +CapsLock": { + "1": { + "PRESS": ",", + "REPEAT": ",", + "RELEASE": null + } + }, + ". (Period) +CapsLock": { + "1": { + "PRESS": ".", + "REPEAT": ".", + "RELEASE": null + } + }, + "- (Slash) +CapsLock": { + "1": { + "PRESS": "-", + "REPEAT": "-", + "RELEASE": null + } + }, + "ArrowUp +CapsLock": { + "1": { + "PRESS": "\u001b[1;65A", + "REPEAT": "\u001b[1;65A", + "RELEASE": null + } + }, + "PageDown +CapsLock": { + "1": { + "PRESS": "\u001b[6;65~", + "REPEAT": "\u001b[6;65~", + "RELEASE": null + } + }, + "Home +CapsLock": { + "1": { + "PRESS": "\u001b[1;65H", + "REPEAT": "\u001b[1;65H", + "RELEASE": null + } + }, + "End +CapsLock": { + "1": { + "PRESS": "\u001b[1;65F", + "REPEAT": "\u001b[1;65F", + "RELEASE": null + } + }, + "+ (NumpadAdd) +CapsLock": { + "1": { + "PRESS": "+", + "REPEAT": "+", + "RELEASE": null + } + }, + "Enter (NumpadEnter) +CapsLock": { + "1": { + "PRESS": "\u001b[57414;65u", + "REPEAT": "\u001b[57414;65u", + "RELEASE": null + } + }, + "Insert (Numpad0) +CapsLock": { + "1": { + "PRESS": "\u001b[57425;65u", + "REPEAT": "\u001b[57425;65u", + "RELEASE": null + } + }, + "\u0000 (NumpadDecimal) +CapsLock": { + "1": { + "PRESS": "\u001b[57426;65u", + "REPEAT": "\u001b[57426;65u", + "RELEASE": null + } + }, + "Numlock +CapsLock": { + "1": { + "PRESS": null, + "REPEAT": null, + "RELEASE": null + } + }, + "CapsLock +CapsLock": { + "1": { + "PRESS": null, + "REPEAT": null, + "RELEASE": null + } + }, + "Shift (ShiftLeft) +CapsLock": { + "1": { + "PRESS": null, + "REPEAT": null, + "RELEASE": null + } + }, + "Shift (ShiftRight) +CapsLock": { + "1": { + "PRESS": null, + "REPEAT": null, + "RELEASE": null + } + }, + "Control (ControlLeft) +CapsLock": { + "1": { + "PRESS": null, + "REPEAT": null, + "RELEASE": null + } + }, + "Meta (MetaLeft) +CapsLock": { + "1": { + "PRESS": null, + "REPEAT": null, + "RELEASE": null + } + }, + "Alt (AltLeft) +CapsLock": { + "1": { + "PRESS": null, + "REPEAT": null, + "RELEASE": null + } + }, + "AltGraph (AltRight) +CapsLock": { + "1": { + "PRESS": null, + "REPEAT": null, + "RELEASE": null + } + }, + "Control (ControlRight) +CapsLock": { + "1": { + "PRESS": null, + "REPEAT": null, + "RELEASE": null + } + }, + "/ (NumpadDivide) +NumLock": { + "1": { + "PRESS": "/", + "REPEAT": "/", + "RELEASE": null + } + }, + "* (NumpadMultiply) +NumLock": { + "1": { + "PRESS": "*", + "REPEAT": "*", + "RELEASE": null + } + }, + "- (NumpadSubtract) +NumLock": { + "1": { + "PRESS": "-", + "REPEAT": "-", + "RELEASE": null + } + }, + "0 (Numpad0) +NumLock": { + "1": { + "PRESS": "0", + "REPEAT": "0", + "RELEASE": null + } + }, + "1 (Numpad1) +NumLock": { + "1": { + "PRESS": "1", + "REPEAT": "1", + "RELEASE": null + } + }, + "2 (Numpad2) +NumLock": { + "1": { + "PRESS": "2", + "REPEAT": "2", + "RELEASE": null + } + }, + "3 (Numpad3) +NumLock": { + "1": { + "PRESS": "3", + "REPEAT": "3", + "RELEASE": null + } + }, + "4 (Numpad4) +NumLock": { + "1": { + "PRESS": "4", + "REPEAT": "4", + "RELEASE": null + } + }, + "5 (Numpad5) +NumLock": { + "1": { + "PRESS": "5", + "REPEAT": "5", + "RELEASE": null + } + }, + "6 (Numpad6) +NumLock": { + "1": { + "PRESS": "6", + "REPEAT": "6", + "RELEASE": null + } + }, + "7 (Numpad7) +NumLock": { + "1": { + "PRESS": "7", + "REPEAT": "7", + "RELEASE": null + } + }, + "8 (Numpad8) +NumLock": { + "1": { + "PRESS": "8", + "REPEAT": "8", + "RELEASE": null + } + }, + "9 (Numpad9) +NumLock": { + "1": { + "PRESS": "9", + "REPEAT": "9", + "RELEASE": null + } + }, + ", (NumpadDecimal) +NumLock": { + "1": { + "PRESS": ",", + "REPEAT": ",", + "RELEASE": null + } + } +} \ No newline at end of file diff --git a/fixtures/kitty_keyboard/german_qwertz_shift.json b/fixtures/kitty_keyboard/german_qwertz_shift.json new file mode 100644 index 0000000000..36dea1f943 --- /dev/null +++ b/fixtures/kitty_keyboard/german_qwertz_shift.json @@ -0,0 +1,331 @@ +{ + "Escape +Shift": { + "1": { + "PRESS": "\u001b[27;2u", + "REPEAT": "\u001b[27;2u", + "RELEASE": null + } + }, + "F1 +Shift": { + "1": { + "PRESS": "\u001b[1;2P", + "REPEAT": "\u001b[1;2P", + "RELEASE": null + } + }, + "Delete +Shift": { + "1": { + "PRESS": "\u001b[3;2~", + "REPEAT": "\u001b[3;2~", + "RELEASE": null + } + }, + "PageUp +Shift": { + "1": { + "PRESS": "\u001b[5;2~", + "REPEAT": "\u001b[5;2~", + "RELEASE": null + } + }, + "Dead (Backquote) +Shift": { + "1": { + "PRESS": "\u00b0", + "REPEAT": "\u00b0", + "RELEASE": null + } + }, + "! (Digit1) +Shift": { + "1": { + "PRESS": "!", + "REPEAT": "!", + "RELEASE": null + } + }, + "\" (Digit2) +Shift": { + "1": { + "PRESS": "\"", + "REPEAT": "\"", + "RELEASE": null + } + }, + "\u00a7 (Digit3) +Shift": { + "1": { + "PRESS": "\u00a7", + "REPEAT": "\u00a7", + "RELEASE": null + } + }, + "$ (Digit4) +Shifft": { + "1": { + "PRESS": "$", + "REPEAT": "$", + "RELEASE": null + } + }, + "% (Digit5) +Shift": { + "1": { + "PRESS": "%", + "REPEAT": "%", + "RELEASE": null + } + }, + "& (Digit6) +Shift": { + "1": { + "PRESS": "&", + "REPEAT": "&", + "RELEASE": null + } + }, + "/ (Digit7) +Shift": { + "1": { + "PRESS": "/", + "REPEAT": "/", + "RELEASE": null + } + }, + "( (Digit8) +Shift": { + "1": { + "PRESS": "(", + "REPEAT": "(", + "RELEASE": null + } + }, + ") (Digit9) +Shift": { + "1": { + "PRESS": ")", + "REPEAT": ")", + "RELEASE": null + } + }, + "= (Digit0) +Shift": { + "1": { + "PRESS": "=", + "REPEAT": "=", + "RELEASE": null + } + }, + "? (Minus) +Shift": { + "1": { + "PRESS": "?", + "REPEAT": "?", + "RELEASE": null + } + }, + "Backspace +Shift": { + "1": { + "PRESS": "\u001b[127;2u", + "REPEAT": "\u001b[127;2u", + "RELEASE": null + } + }, + "Tab +Shift": { + "1": { + "PRESS": "\u001b[9;2u", + "REPEAT": "\u001b[9;2u", + "RELEASE": null + } + }, + "Q (KeyQ) +Shift": { + "1": { + "PRESS": "Q", + "REPEAT": "Q", + "RELEASE": null + } + }, + "\u00dc (BracketLeft) +Shift": { + "1": { + "PRESS": "\u00dc", + "REPEAT": "\u00dc", + "RELEASE": null + } + }, + "* (BracketRight) +Shift": { + "1": { + "PRESS": "*", + "REPEAT": "*", + "RELEASE": null + } + }, + "Enter +Shift": { + "1": { + "PRESS": "\u001b[13;2u", + "REPEAT": "\u001b[13;2u", + "RELEASE": null + } + }, + "\u00d6 (Semicolon) +Shift": { + "1": { + "PRESS": "\u00d6", + "REPEAT": "\u00d6", + "RELEASE": null + } + }, + "\u00c4 (Quote) +Shift": { + "1": { + "PRESS": "\u00c4", + "REPEAT": "\u00c4", + "RELEASE": null + } + }, + "' (Backslash) +Shift": { + "1": { + "PRESS": "'", + "REPEAT": "'", + "RELEASE": null + } + }, + "> (IntlBackslash) +Shift": { + "1": { + "PRESS": ">", + "REPEAT": ">", + "RELEASE": null + } + }, + "; (Comma) +Shift": { + "1": { + "PRESS": ";", + "REPEAT": ";", + "RELEASE": null + } + }, + ": (Period) +Shift": { + "1": { + "PRESS": ":", + "REPEAT": ":", + "RELEASE": null + } + }, + "_ (Slash) +Shift": { + "1": { + "PRESS": "_", + "REPEAT": "_", + "RELEASE": null + } + }, + "ArrowUp +Shift": { + "1": { + "PRESS": "\u001b[1;2A", + "REPEAT": "\u001b[1;2A", + "RELEASE": null + } + }, + "PageDown +Shift": { + "1": { + "PRESS": "\u001b[6;2~", + "REPEAT": "\u001b[6;2~", + "RELEASE": null + } + }, + "Home +Shift": { + "1": { + "PRESS": "\u001b[1;2H", + "REPEAT": "\u001b[1;2H", + "RELEASE": null + } + }, + "Home (Numpad7) +Shift": { + "1": { + "PRESS": "\u001b[57423;2u", + "REPEAT": "\u001b[57423;2u", + "RELEASE": null + } + }, + "ArrowUp (Numapd8) +Shift": { + "1": { + "PRESS": "\u001b[57419;2u", + "REPEAT": "\u001b[57419;2u", + "RELEASE": null + } + }, + "Clear (Numpad5) +Shift": { + "1": { + "PRESS": "\u001b[1;2E", + "REPEAT": "\u001b[1;2E", + "RELEASE": null + } + }, + "Enter (NumpadEnter) +Shift": { + "1": { + "PRESS": "\u001b[57414;2u", + "REPEAT": "\u001b[57414;2u", + "RELEASE": null + } + }, + "Insert (Numpad0) +Shift": { + "1": { + "PRESS": "\u001b[57425;2u", + "REPEAT": "\u001b[57425;2u", + "RELEASE": null + } + }, + "\u0000 (NumpadDecimal) +Shift": { + "1": { + "PRESS": "\u001b[57426;2u", + "REPEAT": "\u001b[57426;2u", + "RELEASE": null + } + }, + "Numlock +Shift": { + "1": { + "PRESS": null, + "REPEAT": null, + "RELEASE": null + } + }, + "CapsLock +Shift": { + "1": { + "PRESS": null, + "REPEAT": null, + "RELEASE": null + } + }, + "Shift (ShiftRight) +Shift": { + "1": { + "PRESS": null, + "REPEAT": null, + "RELEASE": null + } + }, + "Control (ControlLeft) +Shift": { + "1": { + "PRESS": null, + "REPEAT": null, + "RELEASE": null + } + }, + "Meta (MetaLeft) +Shift": { + "1": { + "PRESS": null, + "REPEAT": null, + "RELEASE": null + } + }, + "Alt (AltLeft) +Shift": { + "1": { + "PRESS": null, + "REPEAT": null, + "RELEASE": null + } + }, + " (Space) +Shift": { + "1": { + "PRESS": " ", + "REPEAT": " ", + "RELEASE": null + } + }, + "AltGraph (AltRight) +Shift": { + "1": { + "PRESS": null, + "REPEAT": null, + "RELEASE": null + } + }, + "Control (ControlRight) +Shift": { + "1": { + "PRESS": null, + "REPEAT": null, + "RELEASE": null + } + } +} \ No newline at end of file From e24b043ab237d32e4c1a7e4d13a424b096865edb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 14 May 2026 19:27:50 +0200 Subject: [PATCH 5/8] add DE_QWERTZ, FR_AZERTY and US_QWERTY layouts --- fixtures/keyboard_layouts/DE_QWERTZ.json | 50 ++++++++++++++++++++++++ fixtures/keyboard_layouts/FR_AZERTY.json | 50 ++++++++++++++++++++++++ fixtures/keyboard_layouts/US_QWERTY.json | 50 ++++++++++++++++++++++++ 3 files changed, 150 insertions(+) create mode 100644 fixtures/keyboard_layouts/DE_QWERTZ.json create mode 100644 fixtures/keyboard_layouts/FR_AZERTY.json create mode 100644 fixtures/keyboard_layouts/US_QWERTY.json diff --git a/fixtures/keyboard_layouts/DE_QWERTZ.json b/fixtures/keyboard_layouts/DE_QWERTZ.json new file mode 100644 index 0000000000..acd5123131 --- /dev/null +++ b/fixtures/keyboard_layouts/DE_QWERTZ.json @@ -0,0 +1,50 @@ +{ + "Backquote": "^", + "Backslash": "#", + "BracketLeft": "\u00fc", + "BracketRight": "+", + "Comma": ",", + "Digit0": "0", + "Digit1": "1", + "Digit2": "2", + "Digit3": "3", + "Digit4": "4", + "Digit5": "5", + "Digit6": "6", + "Digit7": "7", + "Digit8": "8", + "Digit9": "9", + "Equal": "'", + "IntlBackslash": "<", + "KeyA": "a", + "KeyB": "b", + "KeyC": "c", + "KeyD": "d", + "KeyE": "e", + "KeyF": "f", + "KeyG": "g", + "KeyH": "h", + "KeyI": "i", + "KeyJ": "j", + "KeyK": "k", + "KeyL": "l", + "KeyM": "m", + "KeyN": "n", + "KeyO": "o", + "KeyP": "p", + "KeyQ": "q", + "KeyR": "r", + "KeyS": "s", + "KeyT": "t", + "KeyU": "u", + "KeyV": "v", + "KeyW": "w", + "KeyX": "x", + "KeyY": "z", + "KeyZ": "y", + "Minus": "\u00df", + "Period": ".", + "Quote": "\u00e4", + "Semicolon": "\u00f6", + "Slash": "-" +} \ No newline at end of file diff --git a/fixtures/keyboard_layouts/FR_AZERTY.json b/fixtures/keyboard_layouts/FR_AZERTY.json new file mode 100644 index 0000000000..a8e31bb12c --- /dev/null +++ b/fixtures/keyboard_layouts/FR_AZERTY.json @@ -0,0 +1,50 @@ +{ + "Backquote": "\u00b2", + "Backslash": "\u20ac", + "BracketLeft": "^", + "BracketRight": "$", + "Comma": ";", + "Digit0": "0", + "Digit1": "&", + "Digit2": "\u00e9", + "Digit3": "\"", + "Digit4": "'", + "Digit5": "", + "Digit6": "-", + "Digit7": "\u00e8", + "Digit8": "*", + "Digit9": ")", + "Equal": "=", + "IntlBackslash": "\\", + "KeyA": "q", + "KeyB": "b", + "KeyC": "c", + "KeyD": "d", + "KeyE": "e", + "KeyF": "f", + "KeyG": "g", + "KeyH": "h", + "KeyI": "i", + "KeyJ": "j", + "KeyK": "k", + "KeyL": "l", + "KeyM": ",", + "KeyN": "n", + "KeyO": "o", + "KeyP": "p", + "KeyQ": "a", + "KeyR": "r", + "KeyS": "s", + "KeyT": "t", + "KeyU": "u", + "KeyV": "v", + "KeyW": "z", + "KeyX": "x", + "KeyY": "y", + "KeyZ": "w", + "Minus": "_", + "Period": ".", + "Quote": "\u00f9", + "Semicolon": "m", + "Slash": "/" +} \ No newline at end of file diff --git a/fixtures/keyboard_layouts/US_QWERTY.json b/fixtures/keyboard_layouts/US_QWERTY.json new file mode 100644 index 0000000000..9ac9d72007 --- /dev/null +++ b/fixtures/keyboard_layouts/US_QWERTY.json @@ -0,0 +1,50 @@ +{ + "Backquote": "`", + "Backslash": "\\", + "BracketLeft": "[", + "BracketRight": "]", + "Comma": ",", + "Digit0": "0", + "Digit1": "1", + "Digit2": "2", + "Digit3": "3", + "Digit4": "4", + "Digit5": "5", + "Digit6": "6", + "Digit7": "7", + "Digit8": "8", + "Digit9": "9", + "Equal": "=", + "IntlBackslash": "\\", + "KeyA": "a", + "KeyB": "b", + "KeyC": "c", + "KeyD": "d", + "KeyE": "e", + "KeyF": "f", + "KeyG": "g", + "KeyH": "h", + "KeyI": "i", + "KeyJ": "j", + "KeyK": "k", + "KeyL": "l", + "KeyM": "m", + "KeyN": "n", + "KeyO": "o", + "KeyP": "p", + "KeyQ": "q", + "KeyR": "r", + "KeyS": "s", + "KeyT": "t", + "KeyU": "u", + "KeyV": "v", + "KeyW": "w", + "KeyX": "x", + "KeyY": "y", + "KeyZ": "z", + "Minus": "-", + "Period": ".", + "Quote": "'", + "Semicolon": ";", + "Slash": "/" +} \ No newline at end of file From 0d8996395ce2e7df78ed854f394ffdbacd464f7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 14 May 2026 21:05:39 +0200 Subject: [PATCH 6/8] fix AZERTY, layout map generator --- bin/keyboard_layouts.mjs | 38 ++++++++++++++++++++++++ fixtures/keyboard_layouts/DE_QWERTZ.json | 2 +- fixtures/keyboard_layouts/FR_AZERTY.json | 22 +++++++------- 3 files changed, 50 insertions(+), 12 deletions(-) create mode 100644 bin/keyboard_layouts.mjs diff --git a/bin/keyboard_layouts.mjs b/bin/keyboard_layouts.mjs new file mode 100644 index 0000000000..4f8aa64ebe --- /dev/null +++ b/bin/keyboard_layouts.mjs @@ -0,0 +1,38 @@ +import fs from 'node:fs'; +import path from 'node:path'; + +const DATA = {}; + +for (let i = 2; i < process.argv.length; ++i) { + const filename = process.argv[i]; + const key = path.basename(filename).split('.')[0]; + const value = JSON.parse(fs.readFileSync(filename)); + DATA[key] = value; +} + +let keys = []; +for (const map in DATA) { + keys = keys.concat(Object.keys(DATA[map])); +} +const KEYS = Array.from(new Set(keys)).sort(); +const ACC = Object.fromEntries(Object.keys(DATA).map((el, i) => [el, i])); +const MAP = Object.fromEntries(KEYS.map(el => [el, []])); +for (const key of KEYS) { + for (const map in ACC) { + MAP[key].push(DATA[map][key]); + } +} + +// minify if all entries are of length 1 +for (const key in MAP) { + for (const c of MAP[key]) { + if (!c || c.length !== 1) + break; + } + MAP[key] = MAP[key].join(''); +} + +const FINAL = {acc: ACC, map: MAP}; + +console.log(FINAL); +console.log(JSON.stringify(FINAL).length); diff --git a/fixtures/keyboard_layouts/DE_QWERTZ.json b/fixtures/keyboard_layouts/DE_QWERTZ.json index acd5123131..9f737086e4 100644 --- a/fixtures/keyboard_layouts/DE_QWERTZ.json +++ b/fixtures/keyboard_layouts/DE_QWERTZ.json @@ -14,7 +14,7 @@ "Digit7": "7", "Digit8": "8", "Digit9": "9", - "Equal": "'", + "Equal": "´", "IntlBackslash": "<", "KeyA": "a", "KeyB": "b", diff --git a/fixtures/keyboard_layouts/FR_AZERTY.json b/fixtures/keyboard_layouts/FR_AZERTY.json index a8e31bb12c..5a5db35aa2 100644 --- a/fixtures/keyboard_layouts/FR_AZERTY.json +++ b/fixtures/keyboard_layouts/FR_AZERTY.json @@ -1,21 +1,21 @@ { "Backquote": "\u00b2", - "Backslash": "\u20ac", + "Backslash": "μ", "BracketLeft": "^", "BracketRight": "$", "Comma": ";", - "Digit0": "0", + "Digit0": "\u00e0", "Digit1": "&", "Digit2": "\u00e9", "Digit3": "\"", "Digit4": "'", - "Digit5": "", - "Digit6": "-", + "Digit5": "(", + "Digit6": "§", "Digit7": "\u00e8", - "Digit8": "*", - "Digit9": ")", - "Equal": "=", - "IntlBackslash": "\\", + "Digit8": "!", + "Digit9": "ç", + "Equal": "-", + "IntlBackslash": "<", "KeyA": "q", "KeyB": "b", "KeyC": "c", @@ -42,9 +42,9 @@ "KeyX": "x", "KeyY": "y", "KeyZ": "w", - "Minus": "_", - "Period": ".", + "Minus": ")", + "Period": ":", "Quote": "\u00f9", "Semicolon": "m", - "Slash": "/" + "Slash": "=" } \ No newline at end of file From c0007301c123c85887d2dee93995aa13e7735377 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sat, 16 May 2026 18:08:10 +0200 Subject: [PATCH 7/8] multiple changes: - layout detector - browser helper to create layout maps - DE, ES, GR, RU, US maps --- fixtures/keyboard_layout.html | 270 ++++++++++++++++ .../{DE_QWERTZ.json => DE.json} | 8 +- fixtures/keyboard_layouts/ES.json | 50 +++ fixtures/keyboard_layouts/ES_LA.json | 50 +++ .../{FR_AZERTY.json => FR.json} | 20 +- fixtures/keyboard_layouts/GR.json | 50 +++ fixtures/keyboard_layouts/RU.json | 50 +++ .../{US_QWERTY.json => US.json} | 0 src/common/input/Layout.ts | 293 ++++++++++++++++++ 9 files changed, 777 insertions(+), 14 deletions(-) create mode 100644 fixtures/keyboard_layout.html rename fixtures/keyboard_layouts/{DE_QWERTZ.json => DE.json} (88%) create mode 100644 fixtures/keyboard_layouts/ES.json create mode 100644 fixtures/keyboard_layouts/ES_LA.json rename fixtures/keyboard_layouts/{FR_AZERTY.json => FR.json} (75%) create mode 100644 fixtures/keyboard_layouts/GR.json create mode 100644 fixtures/keyboard_layouts/RU.json rename fixtures/keyboard_layouts/{US_QWERTY.json => US.json} (100%) create mode 100644 src/common/input/Layout.ts diff --git a/fixtures/keyboard_layout.html b/fixtures/keyboard_layout.html new file mode 100644 index 0000000000..9ac0688636 --- /dev/null +++ b/fixtures/keyboard_layout.html @@ -0,0 +1,270 @@ + + + + + + + Keyboard Layout + + + +

Keyboard Base Layout

+

+ To record your keyboard base layout (unshifted), press all keys from left to right.
+ While you can record most keys with this helper, it is enough to press all character producing keys.

+ + Esc deletes last key except itself.
+ Enter starts new key row (Enter itself gets not shown as key). +

+
+
+ Esc +
+ +
+
+ + +
+

+    
+  
+
diff --git a/fixtures/keyboard_layouts/DE_QWERTZ.json b/fixtures/keyboard_layouts/DE.json
similarity index 88%
rename from fixtures/keyboard_layouts/DE_QWERTZ.json
rename to fixtures/keyboard_layouts/DE.json
index 9f737086e4..1623cab942 100644
--- a/fixtures/keyboard_layouts/DE_QWERTZ.json
+++ b/fixtures/keyboard_layouts/DE.json
@@ -1,7 +1,7 @@
 {
   "Backquote": "^",
   "Backslash": "#",
-  "BracketLeft": "\u00fc",
+  "BracketLeft": "ü",
   "BracketRight": "+",
   "Comma": ",",
   "Digit0": "0",
@@ -42,9 +42,9 @@
   "KeyX": "x",
   "KeyY": "z",
   "KeyZ": "y",
-  "Minus": "\u00df",
+  "Minus": "ß",
   "Period": ".",
-  "Quote": "\u00e4",
-  "Semicolon": "\u00f6",
+  "Quote": "ä",
+  "Semicolon": "ö",
   "Slash": "-"
 }
\ No newline at end of file
diff --git a/fixtures/keyboard_layouts/ES.json b/fixtures/keyboard_layouts/ES.json
new file mode 100644
index 0000000000..130be6d3db
--- /dev/null
+++ b/fixtures/keyboard_layouts/ES.json
@@ -0,0 +1,50 @@
+{
+  "Backquote": "º",
+  "Backslash": "ç",
+  "BracketLeft": "`",
+  "BracketRight": "+",
+  "Comma": ",",
+  "Digit0": "0",
+  "Digit1": "1",
+  "Digit2": "2",
+  "Digit3": "3",
+  "Digit4": "4",
+  "Digit5": "5",
+  "Digit6": "6",
+  "Digit7": "7",
+  "Digit8": "8",
+  "Digit9": "9",
+  "Equal": "¡",
+  "IntlBackslash": "<",
+  "KeyA": "a",
+  "KeyB": "b",
+  "KeyC": "c",
+  "KeyD": "d",
+  "KeyE": "e",
+  "KeyF": "f",
+  "KeyG": "g",
+  "KeyH": "h",
+  "KeyI": "i",
+  "KeyJ": "j",
+  "KeyK": "k",
+  "KeyL": "l",
+  "KeyM": "m",
+  "KeyN": "n",
+  "KeyO": "o",
+  "KeyP": "p",
+  "KeyQ": "q",
+  "KeyR": "r",
+  "KeyS": "s",
+  "KeyT": "t",
+  "KeyU": "u",
+  "KeyV": "v",
+  "KeyW": "w",
+  "KeyX": "x",
+  "KeyY": "y",
+  "KeyZ": "z",
+  "Minus": "'",
+  "Period": ".",
+  "Quote": "´",
+  "Semicolon": "ñ",
+  "Slash": "-"
+}
\ No newline at end of file
diff --git a/fixtures/keyboard_layouts/ES_LA.json b/fixtures/keyboard_layouts/ES_LA.json
new file mode 100644
index 0000000000..7178f43558
--- /dev/null
+++ b/fixtures/keyboard_layouts/ES_LA.json
@@ -0,0 +1,50 @@
+{
+  "Backquote": "|",
+  "Backslash": "}",
+  "BracketLeft": "´",
+  "BracketRight": "+",
+  "Comma": ",",
+  "Digit0": "0",
+  "Digit1": "1",
+  "Digit2": "2",
+  "Digit3": "3",
+  "Digit4": "4",
+  "Digit5": "5",
+  "Digit6": "6",
+  "Digit7": "7",
+  "Digit8": "8",
+  "Digit9": "9",
+  "Equal": "¿",
+  "IntlBackslash": "<",
+  "KeyA": "a",
+  "KeyB": "b",
+  "KeyC": "c",
+  "KeyD": "d",
+  "KeyE": "e",
+  "KeyF": "f",
+  "KeyG": "g",
+  "KeyH": "h",
+  "KeyI": "i",
+  "KeyJ": "j",
+  "KeyK": "k",
+  "KeyL": "l",
+  "KeyM": "m",
+  "KeyN": "n",
+  "KeyO": "o",
+  "KeyP": "p",
+  "KeyQ": "q",
+  "KeyR": "r",
+  "KeyS": "s",
+  "KeyT": "t",
+  "KeyU": "u",
+  "KeyV": "v",
+  "KeyW": "w",
+  "KeyX": "x",
+  "KeyY": "y",
+  "KeyZ": "z",
+  "Minus": "'",
+  "Period": ".",
+  "Quote": "{",
+  "Semicolon": "ñ",
+  "Slash": "-"
+}
\ No newline at end of file
diff --git a/fixtures/keyboard_layouts/FR_AZERTY.json b/fixtures/keyboard_layouts/FR.json
similarity index 75%
rename from fixtures/keyboard_layouts/FR_AZERTY.json
rename to fixtures/keyboard_layouts/FR.json
index 5a5db35aa2..bfde358295 100644
--- a/fixtures/keyboard_layouts/FR_AZERTY.json
+++ b/fixtures/keyboard_layouts/FR.json
@@ -1,20 +1,20 @@
 {
-  "Backquote": "\u00b2",
-  "Backslash": "μ",
+  "Backquote": "²",
+  "Backslash": "*",
   "BracketLeft": "^",
   "BracketRight": "$",
   "Comma": ";",
-  "Digit0": "\u00e0",
+  "Digit0": "à",
   "Digit1": "&",
-  "Digit2": "\u00e9",
+  "Digit2": "é",
   "Digit3": "\"",
   "Digit4": "'",
   "Digit5": "(",
-  "Digit6": "§",
-  "Digit7": "\u00e8",
-  "Digit8": "!",
+  "Digit6": "-",
+  "Digit7": "è",
+  "Digit8": "_",
   "Digit9": "ç",
-  "Equal": "-",
+  "Equal": "=",
   "IntlBackslash": "<",
   "KeyA": "q",
   "KeyB": "b",
@@ -44,7 +44,7 @@
   "KeyZ": "w",
   "Minus": ")",
   "Period": ":",
-  "Quote": "\u00f9",
+  "Quote": "ù",
   "Semicolon": "m",
-  "Slash": "="
+  "Slash": "!"
 }
\ No newline at end of file
diff --git a/fixtures/keyboard_layouts/GR.json b/fixtures/keyboard_layouts/GR.json
new file mode 100644
index 0000000000..fc106276ff
--- /dev/null
+++ b/fixtures/keyboard_layouts/GR.json
@@ -0,0 +1,50 @@
+{
+  "Backquote": "`",
+  "Backslash": "\\",
+  "BracketLeft": "[",
+  "BracketRight": "]",
+  "Comma": ",",
+  "Digit0": "0",
+  "Digit1": "1",
+  "Digit2": "2",
+  "Digit3": "3",
+  "Digit4": "4",
+  "Digit5": "5",
+  "Digit6": "6",
+  "Digit7": "7",
+  "Digit8": "8",
+  "Digit9": "9",
+  "Equal": "=",
+  "IntlBackslash": "«",
+  "KeyA": "α",
+  "KeyB": "β",
+  "KeyC": "ψ",
+  "KeyD": "δ",
+  "KeyE": "ε",
+  "KeyF": "φ",
+  "KeyG": "γ",
+  "KeyH": "η",
+  "KeyI": "ι",
+  "KeyJ": "ξ",
+  "KeyK": "κ",
+  "KeyL": "λ",
+  "KeyM": "μ",
+  "KeyN": "ν",
+  "KeyO": "ο",
+  "KeyP": "π",
+  "KeyQ": ";",
+  "KeyR": "ρ",
+  "KeyS": "σ",
+  "KeyT": "τ",
+  "KeyU": "θ",
+  "KeyV": "ω",
+  "KeyW": "ς",
+  "KeyX": "χ",
+  "KeyY": "υ",
+  "KeyZ": "ζ",
+  "Minus": "-",
+  "Period": ".",
+  "Quote": "'",
+  "Semicolon": "´",
+  "Slash": "/"
+}
\ No newline at end of file
diff --git a/fixtures/keyboard_layouts/RU.json b/fixtures/keyboard_layouts/RU.json
new file mode 100644
index 0000000000..ef0fce4783
--- /dev/null
+++ b/fixtures/keyboard_layouts/RU.json
@@ -0,0 +1,50 @@
+{
+  "Backquote": "ё",
+  "Backslash": "\\",
+  "BracketLeft": "х",
+  "BracketRight": "ъ",
+  "Comma": "б",
+  "Digit0": "0",
+  "Digit1": "1",
+  "Digit2": "2",
+  "Digit3": "3",
+  "Digit4": "4",
+  "Digit5": "5",
+  "Digit6": "6",
+  "Digit7": "7",
+  "Digit8": "8",
+  "Digit9": "9",
+  "Equal": "=",
+  "IntlBackslash": "/",
+  "KeyA": "ф",
+  "KeyB": "и",
+  "KeyC": "с",
+  "KeyD": "в",
+  "KeyE": "у",
+  "KeyF": "а",
+  "KeyG": "п",
+  "KeyH": "р",
+  "KeyI": "ш",
+  "KeyJ": "о",
+  "KeyK": "л",
+  "KeyL": "д",
+  "KeyM": "ь",
+  "KeyN": "т",
+  "KeyO": "щ",
+  "KeyP": "з",
+  "KeyQ": "й",
+  "KeyR": "к",
+  "KeyS": "ы",
+  "KeyT": "е",
+  "KeyU": "г",
+  "KeyV": "м",
+  "KeyW": "ц",
+  "KeyX": "ч",
+  "KeyY": "н",
+  "KeyZ": "я",
+  "Minus": "-",
+  "Period": "ю",
+  "Quote": "э",
+  "Semicolon": "ж",
+  "Slash": "."
+}
\ No newline at end of file
diff --git a/fixtures/keyboard_layouts/US_QWERTY.json b/fixtures/keyboard_layouts/US.json
similarity index 100%
rename from fixtures/keyboard_layouts/US_QWERTY.json
rename to fixtures/keyboard_layouts/US.json
diff --git a/src/common/input/Layout.ts b/src/common/input/Layout.ts
new file mode 100644
index 0000000000..40e7e3f486
--- /dev/null
+++ b/src/common/input/Layout.ts
@@ -0,0 +1,293 @@
+/**
+ * Copyright (c) 2026 The xterm.js authors. All rights reserved.
+ * @license MIT
+ *
+ * Keyboard layout detection.
+ */
+
+
+interface IMap {
+  acc: {[index: string]: number};
+  map: {[index: string]: string | string[]};
+}
+
+interface IKeyResult {
+  layouts: string[];
+  certain: number;
+  key: string | undefined | (string | undefined)[];
+}
+
+
+/**
+ * rebuild map: node bin/keyboard_layouts.mjs fixtures/keyboard_layouts/*
+ */
+const MAP: IMap = {
+  acc: { DE: 0, ES: 1, ES_LA: 2, FR: 3, GR: 4, RU: 5, US: 6 },
+  map: {
+    Backquote: '^º|²`ё`',
+    Backslash: '#ç}*\\\\\\',
+    BracketLeft: 'ü`´^[х[',
+    BracketRight: '+++$]ъ]',
+    Comma: ',,,;,б,',
+    Digit0: '000à000',
+    Digit1: '111&111',
+    Digit2: '222é222',
+    Digit3: '333"333',
+    Digit4: "444'444",
+    Digit5: '555(555',
+    Digit6: '666-666',
+    Digit7: '777è777',
+    Digit8: '888_888',
+    Digit9: '999ç999',
+    Equal: '´¡¿====',
+    IntlBackslash: '<<<<«/\\',
+    KeyA: 'aaaqαфa',
+    KeyB: 'bbbbβиb',
+    KeyC: 'ccccψсc',
+    KeyD: 'ddddδвd',
+    KeyE: 'eeeeεуe',
+    KeyF: 'ffffφаf',
+    KeyG: 'ggggγпg',
+    KeyH: 'hhhhηрh',
+    KeyI: 'iiiiιшi',
+    KeyJ: 'jjjjξоj',
+    KeyK: 'kkkkκлk',
+    KeyL: 'llllλдl',
+    KeyM: 'mmm,μьm',
+    KeyN: 'nnnnνтn',
+    KeyO: 'ooooοщo',
+    KeyP: 'ppppπзp',
+    KeyQ: 'qqqa;йq',
+    KeyR: 'rrrrρкr',
+    KeyS: 'ssssσыs',
+    KeyT: 'ttttτеt',
+    KeyU: 'uuuuθгu',
+    KeyV: 'vvvvωмv',
+    KeyW: 'wwwzςцw',
+    KeyX: 'xxxxχчx',
+    KeyY: 'zyyyυнy',
+    KeyZ: 'yzzwζяz',
+    Minus: "ß'')---",
+    Period: '...:.ю.',
+    Quote: "ä´{ù'э'",
+    Semicolon: 'öññm´ж;',
+    Slash: '---!/./'
+  }
+};
+
+
+export class LayoutDetector {
+  private _keys: {[index: string]: number} = {};
+  private _values: (string | string[])[] = [];
+  private _rec: string[] = [];
+  private _acc: string[] = Object.keys(MAP.acc);
+  private _cand: {layout: string; match: number;}[];
+  private _key: IKeyResult;
+
+  constructor() {
+    let p = 0;
+    for (const key of Object.keys(MAP.map)) {
+      this._keys[key] = p++;
+      this._values.push(MAP.map[key]);
+      this._rec.push('');
+    }
+    this._cand = [...this._acc].map(e => ({layout: e, match: 0 }));
+    this._key = { layouts: ['US_QWERTY'], certain: 0, key: undefined };
+  }
+
+  /**
+   * Reset detector.
+   */
+  public reset(): void {
+    this._rec = this._rec.map(e => '');
+  }
+
+  /**
+   * Return list of registered layouts.
+   */
+  public get layouts(): string[] {
+    return Object.keys(MAP.acc);
+  }
+
+  /**
+   * Return list of supported key codes.
+   */
+  public get codes(): string[] {
+    return Object.keys(MAP.map);
+  }
+
+  /**
+   * Get character key for key code and layout.
+   * The known key codes can be requested with `.codes`,
+   * the registered layouts with `.layouts`.
+   */
+  public getLayoutKey(code: string, layout: string): string | undefined {
+    if (MAP.acc[layout] !== undefined && MAP.map[code]) {
+      return MAP.map[code][MAP.acc[layout]];
+    }
+  }
+
+  /**
+   * Feed a keyboard event `ev` to the detector.
+   */
+  public feed(ev: KeyboardEvent): void {
+    // FIXME: Should we check for OS? (not supported by Safari)
+    if (ev.shiftKey || ev.ctrlKey || ev.altKey || ev.metaKey
+      || ev.getModifierState('AltGraph')
+      || ev.getModifierState('CapsLock')
+    ) {
+      return;
+    }
+    const pos = this._keys[ev.code];
+    if (pos !== undefined) {
+      if (this._rec[pos] && this._rec[pos] !== ev.key) {
+        // The key value should never change for the same layout,
+        // so we treat a sudden change as a layout change.
+        this.reset();
+      }
+      this._rec[pos] = ev.key;
+    }
+  }
+
+  /**
+   * Shows all known layouts and their degree of matching mappings
+   * sorted descending (likely layouts first).
+   * Ideally there is only one leading layout with a match of 1.
+   * If the leading match is not 1, then the user uses an
+   * unknown or custom layout.
+   */
+  public matches(): {layout: string; match: number;}[] {
+    for (let i = 0; i < this._cand.length; ++i) {
+      this._cand[i].match = 0;
+    }
+    let c = 0;
+    for (let k = 0; k < this._rec.length; ++k) {
+      const v = this._rec[k];
+      if (v) {
+        c++;
+        const values = this._values[k];
+        for (let i = 0; i < this._acc.length; ++i) {
+          if (v === values[i]) {
+            this._cand[i].match++;
+          }
+        }
+      }
+    }
+    if (!c) {
+      return this._cand;
+    }
+    const sorted = [...this._cand].sort((a, b) => b.match - a.match);
+    for (let i = 0; i < sorted.length; ++i) {
+      sorted[i].match /= c;
+    }
+    return sorted;
+  }
+
+  /**
+   * Tries to resolve a key code to a key character.
+   * If `certain` is 1 then the result matches the listed layouts.
+   * Ideally only one layout is returned, then the detector has seen enough
+   * key events in `feed`.
+   * If multiple layouts are returned but only one key, then the layout is
+   * not yet fully determined but the key code is already known from `feed`.
+   * When multiple keys are returned, then the character undetermined
+   * and the layout needs further resolving with resolve.
+   * A certainty lesser than 1 can have different reasons:
+   * - not enough key event fed yet (multiple layouts returned)
+   * - user has an unknown or custom layout
+   * If `certain` is 0 the result should not be used as the dector
+   * has not seen any key events at all.
+   */
+  public getKey(code: string): IKeyResult {
+    const lm = this.matches();
+    let candidates = [];
+    let lastMatch = 0;
+    for (let i = 0; i < lm.length; ++i) {
+      if (lm[i].match === 0) {
+        break;
+      }
+      if (lm[i].match === 1) {
+        lastMatch = 1;
+        candidates.push(lm[i].layout);
+      } else if (lm[i].match >= lastMatch) {
+        lastMatch = lm[i].match;
+        candidates.push(lm[i].layout);
+      }
+    }
+    if (candidates.length === 1) {
+      this._key.layouts = candidates;
+      this._key.certain = lastMatch;
+      this._key.key = MAP.map[code]?.[MAP.acc[candidates[0]]];
+      return this._key;
+    }
+    if (!candidates.length) {
+      candidates = [...this._acc];
+    }
+    const values = [];
+    for (let i = 0; i < candidates.length; ++i) {
+      values.push(MAP.map[code]?.[MAP.acc[candidates[i]]]);
+    }
+    const valuesSet = new Set(values);
+    // if all candidates yield the same char, return it
+    if (valuesSet.size === 1) {
+      this._key.layouts = candidates;
+      this._key.certain = lastMatch;
+      const [value] = values;
+      this._key.key = value;
+      return this._key;
+    }
+    // fallthrough
+    this._key.layouts = candidates;
+    this._key.certain = lastMatch / valuesSet.size;
+    this._key.key = values;
+    return this._key;
+  }
+
+  /**
+   * Calculate distance to resolve keyboard layout.
+   * Returns the candicate layouts and a list of keys resolving layout ambiguity.
+   * The key list is sorted descending by candidate differences for a key code
+   * (picking a high difference code needs less follow-up steps).
+   * The user should be asked to press the corresponding key and the key event
+   * should be fed to `feed`.
+   * Repeat this process until this method returns only one layout.
+   */
+  public resolve(): any {
+    const lm = this.matches();
+    let candidates = [];
+    let lastMatch = 0;
+    for (let i = 0; i < lm.length; ++i) {
+      if (lm[i].match === 0) {
+        break;
+      }
+      if (lm[i].match === 1) {
+        lastMatch = 1;
+        candidates.push(lm[i].layout);
+      } else if (lm[i].match >= lastMatch) {
+        lastMatch = lm[i].match;
+        candidates.push(lm[i].layout);
+      }
+    }
+    if (candidates.length === 1) {
+      return { layouts: candidates, keys: [] };
+    }
+    if (!candidates.length) {
+      candidates = [...this._acc];
+    }
+    const acc = candidates.map(e => MAP.acc[e]);
+    const codes = Object.keys(this._keys);
+    const values = [];
+    for (let i = 0; i < this._values.length; ++i) {
+      const value = new Set();
+      for (let k = 0; k < acc.length; ++k) {
+        value.add(this._values[i][k]);
+      }
+      const len = (new Set(value)).size;
+      if (len > 1) {
+        values.push({code: codes[i], keys: [...value].sort()});
+      }
+    }
+    values.sort((a, b) => b.keys.length - a.keys.length);
+    return { layouts: candidates, keys: values };
+  }
+}

From 2f49f321c12bf10a2f4140dcc4b713dec16c2ffb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= 
Date: Mon, 18 May 2026 21:15:29 +0200
Subject: [PATCH 8/8] remove layout stuff

---
 bin/keyboard_layouts.mjs             |  38 ----
 fixtures/keyboard_layout.html        | 270 ------------------------
 fixtures/keyboard_layouts/DE.json    |  50 -----
 fixtures/keyboard_layouts/ES.json    |  50 -----
 fixtures/keyboard_layouts/ES_LA.json |  50 -----
 fixtures/keyboard_layouts/FR.json    |  50 -----
 fixtures/keyboard_layouts/GR.json    |  50 -----
 fixtures/keyboard_layouts/RU.json    |  50 -----
 fixtures/keyboard_layouts/US.json    |  50 -----
 src/common/input/Layout.ts           | 293 ---------------------------
 10 files changed, 951 deletions(-)
 delete mode 100644 bin/keyboard_layouts.mjs
 delete mode 100644 fixtures/keyboard_layout.html
 delete mode 100644 fixtures/keyboard_layouts/DE.json
 delete mode 100644 fixtures/keyboard_layouts/ES.json
 delete mode 100644 fixtures/keyboard_layouts/ES_LA.json
 delete mode 100644 fixtures/keyboard_layouts/FR.json
 delete mode 100644 fixtures/keyboard_layouts/GR.json
 delete mode 100644 fixtures/keyboard_layouts/RU.json
 delete mode 100644 fixtures/keyboard_layouts/US.json
 delete mode 100644 src/common/input/Layout.ts

diff --git a/bin/keyboard_layouts.mjs b/bin/keyboard_layouts.mjs
deleted file mode 100644
index 4f8aa64ebe..0000000000
--- a/bin/keyboard_layouts.mjs
+++ /dev/null
@@ -1,38 +0,0 @@
-import fs from 'node:fs';
-import path from 'node:path';
-
-const DATA = {};
-
-for (let i = 2; i < process.argv.length; ++i) {
-  const filename = process.argv[i];
-  const key = path.basename(filename).split('.')[0];
-  const value = JSON.parse(fs.readFileSync(filename));
-  DATA[key] = value;
-}
-
-let keys = [];
-for (const map in DATA) {
-  keys = keys.concat(Object.keys(DATA[map]));
-}
-const KEYS = Array.from(new Set(keys)).sort();
-const ACC = Object.fromEntries(Object.keys(DATA).map((el, i) => [el, i]));
-const MAP = Object.fromEntries(KEYS.map(el => [el, []]));
-for (const key of KEYS) {
-  for (const map in ACC) {
-    MAP[key].push(DATA[map][key]);
-  }
-}
-
-// minify if all entries are of length 1
-for (const key in MAP) {
-  for (const c of MAP[key]) {
-    if (!c || c.length !== 1)
-      break;
-  }
-  MAP[key] = MAP[key].join('');
-}
-
-const FINAL = {acc: ACC, map: MAP};
-
-console.log(FINAL);
-console.log(JSON.stringify(FINAL).length);
diff --git a/fixtures/keyboard_layout.html b/fixtures/keyboard_layout.html
deleted file mode 100644
index 9ac0688636..0000000000
--- a/fixtures/keyboard_layout.html
+++ /dev/null
@@ -1,270 +0,0 @@
-
-
-  
-    
-    
-    
-    Keyboard Layout
-    
-  
-  
-    

Keyboard Base Layout

-

- To record your keyboard base layout (unshifted), press all keys from left to right.
- While you can record most keys with this helper, it is enough to press all character producing keys.

- - Esc deletes last key except itself.
- Enter starts new key row (Enter itself gets not shown as key). -

-
-
- Esc -
- -
-
- - -
-

-    
-  
-
diff --git a/fixtures/keyboard_layouts/DE.json b/fixtures/keyboard_layouts/DE.json
deleted file mode 100644
index 1623cab942..0000000000
--- a/fixtures/keyboard_layouts/DE.json
+++ /dev/null
@@ -1,50 +0,0 @@
-{
-  "Backquote": "^",
-  "Backslash": "#",
-  "BracketLeft": "ü",
-  "BracketRight": "+",
-  "Comma": ",",
-  "Digit0": "0",
-  "Digit1": "1",
-  "Digit2": "2",
-  "Digit3": "3",
-  "Digit4": "4",
-  "Digit5": "5",
-  "Digit6": "6",
-  "Digit7": "7",
-  "Digit8": "8",
-  "Digit9": "9",
-  "Equal": "´",
-  "IntlBackslash": "<",
-  "KeyA": "a",
-  "KeyB": "b",
-  "KeyC": "c",
-  "KeyD": "d",
-  "KeyE": "e",
-  "KeyF": "f",
-  "KeyG": "g",
-  "KeyH": "h",
-  "KeyI": "i",
-  "KeyJ": "j",
-  "KeyK": "k",
-  "KeyL": "l",
-  "KeyM": "m",
-  "KeyN": "n",
-  "KeyO": "o",
-  "KeyP": "p",
-  "KeyQ": "q",
-  "KeyR": "r",
-  "KeyS": "s",
-  "KeyT": "t",
-  "KeyU": "u",
-  "KeyV": "v",
-  "KeyW": "w",
-  "KeyX": "x",
-  "KeyY": "z",
-  "KeyZ": "y",
-  "Minus": "ß",
-  "Period": ".",
-  "Quote": "ä",
-  "Semicolon": "ö",
-  "Slash": "-"
-}
\ No newline at end of file
diff --git a/fixtures/keyboard_layouts/ES.json b/fixtures/keyboard_layouts/ES.json
deleted file mode 100644
index 130be6d3db..0000000000
--- a/fixtures/keyboard_layouts/ES.json
+++ /dev/null
@@ -1,50 +0,0 @@
-{
-  "Backquote": "º",
-  "Backslash": "ç",
-  "BracketLeft": "`",
-  "BracketRight": "+",
-  "Comma": ",",
-  "Digit0": "0",
-  "Digit1": "1",
-  "Digit2": "2",
-  "Digit3": "3",
-  "Digit4": "4",
-  "Digit5": "5",
-  "Digit6": "6",
-  "Digit7": "7",
-  "Digit8": "8",
-  "Digit9": "9",
-  "Equal": "¡",
-  "IntlBackslash": "<",
-  "KeyA": "a",
-  "KeyB": "b",
-  "KeyC": "c",
-  "KeyD": "d",
-  "KeyE": "e",
-  "KeyF": "f",
-  "KeyG": "g",
-  "KeyH": "h",
-  "KeyI": "i",
-  "KeyJ": "j",
-  "KeyK": "k",
-  "KeyL": "l",
-  "KeyM": "m",
-  "KeyN": "n",
-  "KeyO": "o",
-  "KeyP": "p",
-  "KeyQ": "q",
-  "KeyR": "r",
-  "KeyS": "s",
-  "KeyT": "t",
-  "KeyU": "u",
-  "KeyV": "v",
-  "KeyW": "w",
-  "KeyX": "x",
-  "KeyY": "y",
-  "KeyZ": "z",
-  "Minus": "'",
-  "Period": ".",
-  "Quote": "´",
-  "Semicolon": "ñ",
-  "Slash": "-"
-}
\ No newline at end of file
diff --git a/fixtures/keyboard_layouts/ES_LA.json b/fixtures/keyboard_layouts/ES_LA.json
deleted file mode 100644
index 7178f43558..0000000000
--- a/fixtures/keyboard_layouts/ES_LA.json
+++ /dev/null
@@ -1,50 +0,0 @@
-{
-  "Backquote": "|",
-  "Backslash": "}",
-  "BracketLeft": "´",
-  "BracketRight": "+",
-  "Comma": ",",
-  "Digit0": "0",
-  "Digit1": "1",
-  "Digit2": "2",
-  "Digit3": "3",
-  "Digit4": "4",
-  "Digit5": "5",
-  "Digit6": "6",
-  "Digit7": "7",
-  "Digit8": "8",
-  "Digit9": "9",
-  "Equal": "¿",
-  "IntlBackslash": "<",
-  "KeyA": "a",
-  "KeyB": "b",
-  "KeyC": "c",
-  "KeyD": "d",
-  "KeyE": "e",
-  "KeyF": "f",
-  "KeyG": "g",
-  "KeyH": "h",
-  "KeyI": "i",
-  "KeyJ": "j",
-  "KeyK": "k",
-  "KeyL": "l",
-  "KeyM": "m",
-  "KeyN": "n",
-  "KeyO": "o",
-  "KeyP": "p",
-  "KeyQ": "q",
-  "KeyR": "r",
-  "KeyS": "s",
-  "KeyT": "t",
-  "KeyU": "u",
-  "KeyV": "v",
-  "KeyW": "w",
-  "KeyX": "x",
-  "KeyY": "y",
-  "KeyZ": "z",
-  "Minus": "'",
-  "Period": ".",
-  "Quote": "{",
-  "Semicolon": "ñ",
-  "Slash": "-"
-}
\ No newline at end of file
diff --git a/fixtures/keyboard_layouts/FR.json b/fixtures/keyboard_layouts/FR.json
deleted file mode 100644
index bfde358295..0000000000
--- a/fixtures/keyboard_layouts/FR.json
+++ /dev/null
@@ -1,50 +0,0 @@
-{
-  "Backquote": "²",
-  "Backslash": "*",
-  "BracketLeft": "^",
-  "BracketRight": "$",
-  "Comma": ";",
-  "Digit0": "à",
-  "Digit1": "&",
-  "Digit2": "é",
-  "Digit3": "\"",
-  "Digit4": "'",
-  "Digit5": "(",
-  "Digit6": "-",
-  "Digit7": "è",
-  "Digit8": "_",
-  "Digit9": "ç",
-  "Equal": "=",
-  "IntlBackslash": "<",
-  "KeyA": "q",
-  "KeyB": "b",
-  "KeyC": "c",
-  "KeyD": "d",
-  "KeyE": "e",
-  "KeyF": "f",
-  "KeyG": "g",
-  "KeyH": "h",
-  "KeyI": "i",
-  "KeyJ": "j",
-  "KeyK": "k",
-  "KeyL": "l",
-  "KeyM": ",",
-  "KeyN": "n",
-  "KeyO": "o",
-  "KeyP": "p",
-  "KeyQ": "a",
-  "KeyR": "r",
-  "KeyS": "s",
-  "KeyT": "t",
-  "KeyU": "u",
-  "KeyV": "v",
-  "KeyW": "z",
-  "KeyX": "x",
-  "KeyY": "y",
-  "KeyZ": "w",
-  "Minus": ")",
-  "Period": ":",
-  "Quote": "ù",
-  "Semicolon": "m",
-  "Slash": "!"
-}
\ No newline at end of file
diff --git a/fixtures/keyboard_layouts/GR.json b/fixtures/keyboard_layouts/GR.json
deleted file mode 100644
index fc106276ff..0000000000
--- a/fixtures/keyboard_layouts/GR.json
+++ /dev/null
@@ -1,50 +0,0 @@
-{
-  "Backquote": "`",
-  "Backslash": "\\",
-  "BracketLeft": "[",
-  "BracketRight": "]",
-  "Comma": ",",
-  "Digit0": "0",
-  "Digit1": "1",
-  "Digit2": "2",
-  "Digit3": "3",
-  "Digit4": "4",
-  "Digit5": "5",
-  "Digit6": "6",
-  "Digit7": "7",
-  "Digit8": "8",
-  "Digit9": "9",
-  "Equal": "=",
-  "IntlBackslash": "«",
-  "KeyA": "α",
-  "KeyB": "β",
-  "KeyC": "ψ",
-  "KeyD": "δ",
-  "KeyE": "ε",
-  "KeyF": "φ",
-  "KeyG": "γ",
-  "KeyH": "η",
-  "KeyI": "ι",
-  "KeyJ": "ξ",
-  "KeyK": "κ",
-  "KeyL": "λ",
-  "KeyM": "μ",
-  "KeyN": "ν",
-  "KeyO": "ο",
-  "KeyP": "π",
-  "KeyQ": ";",
-  "KeyR": "ρ",
-  "KeyS": "σ",
-  "KeyT": "τ",
-  "KeyU": "θ",
-  "KeyV": "ω",
-  "KeyW": "ς",
-  "KeyX": "χ",
-  "KeyY": "υ",
-  "KeyZ": "ζ",
-  "Minus": "-",
-  "Period": ".",
-  "Quote": "'",
-  "Semicolon": "´",
-  "Slash": "/"
-}
\ No newline at end of file
diff --git a/fixtures/keyboard_layouts/RU.json b/fixtures/keyboard_layouts/RU.json
deleted file mode 100644
index ef0fce4783..0000000000
--- a/fixtures/keyboard_layouts/RU.json
+++ /dev/null
@@ -1,50 +0,0 @@
-{
-  "Backquote": "ё",
-  "Backslash": "\\",
-  "BracketLeft": "х",
-  "BracketRight": "ъ",
-  "Comma": "б",
-  "Digit0": "0",
-  "Digit1": "1",
-  "Digit2": "2",
-  "Digit3": "3",
-  "Digit4": "4",
-  "Digit5": "5",
-  "Digit6": "6",
-  "Digit7": "7",
-  "Digit8": "8",
-  "Digit9": "9",
-  "Equal": "=",
-  "IntlBackslash": "/",
-  "KeyA": "ф",
-  "KeyB": "и",
-  "KeyC": "с",
-  "KeyD": "в",
-  "KeyE": "у",
-  "KeyF": "а",
-  "KeyG": "п",
-  "KeyH": "р",
-  "KeyI": "ш",
-  "KeyJ": "о",
-  "KeyK": "л",
-  "KeyL": "д",
-  "KeyM": "ь",
-  "KeyN": "т",
-  "KeyO": "щ",
-  "KeyP": "з",
-  "KeyQ": "й",
-  "KeyR": "к",
-  "KeyS": "ы",
-  "KeyT": "е",
-  "KeyU": "г",
-  "KeyV": "м",
-  "KeyW": "ц",
-  "KeyX": "ч",
-  "KeyY": "н",
-  "KeyZ": "я",
-  "Minus": "-",
-  "Period": "ю",
-  "Quote": "э",
-  "Semicolon": "ж",
-  "Slash": "."
-}
\ No newline at end of file
diff --git a/fixtures/keyboard_layouts/US.json b/fixtures/keyboard_layouts/US.json
deleted file mode 100644
index 9ac9d72007..0000000000
--- a/fixtures/keyboard_layouts/US.json
+++ /dev/null
@@ -1,50 +0,0 @@
-{
-  "Backquote": "`",
-  "Backslash": "\\",
-  "BracketLeft": "[",
-  "BracketRight": "]",
-  "Comma": ",",
-  "Digit0": "0",
-  "Digit1": "1",
-  "Digit2": "2",
-  "Digit3": "3",
-  "Digit4": "4",
-  "Digit5": "5",
-  "Digit6": "6",
-  "Digit7": "7",
-  "Digit8": "8",
-  "Digit9": "9",
-  "Equal": "=",
-  "IntlBackslash": "\\",
-  "KeyA": "a",
-  "KeyB": "b",
-  "KeyC": "c",
-  "KeyD": "d",
-  "KeyE": "e",
-  "KeyF": "f",
-  "KeyG": "g",
-  "KeyH": "h",
-  "KeyI": "i",
-  "KeyJ": "j",
-  "KeyK": "k",
-  "KeyL": "l",
-  "KeyM": "m",
-  "KeyN": "n",
-  "KeyO": "o",
-  "KeyP": "p",
-  "KeyQ": "q",
-  "KeyR": "r",
-  "KeyS": "s",
-  "KeyT": "t",
-  "KeyU": "u",
-  "KeyV": "v",
-  "KeyW": "w",
-  "KeyX": "x",
-  "KeyY": "y",
-  "KeyZ": "z",
-  "Minus": "-",
-  "Period": ".",
-  "Quote": "'",
-  "Semicolon": ";",
-  "Slash": "/"
-}
\ No newline at end of file
diff --git a/src/common/input/Layout.ts b/src/common/input/Layout.ts
deleted file mode 100644
index 40e7e3f486..0000000000
--- a/src/common/input/Layout.ts
+++ /dev/null
@@ -1,293 +0,0 @@
-/**
- * Copyright (c) 2026 The xterm.js authors. All rights reserved.
- * @license MIT
- *
- * Keyboard layout detection.
- */
-
-
-interface IMap {
-  acc: {[index: string]: number};
-  map: {[index: string]: string | string[]};
-}
-
-interface IKeyResult {
-  layouts: string[];
-  certain: number;
-  key: string | undefined | (string | undefined)[];
-}
-
-
-/**
- * rebuild map: node bin/keyboard_layouts.mjs fixtures/keyboard_layouts/*
- */
-const MAP: IMap = {
-  acc: { DE: 0, ES: 1, ES_LA: 2, FR: 3, GR: 4, RU: 5, US: 6 },
-  map: {
-    Backquote: '^º|²`ё`',
-    Backslash: '#ç}*\\\\\\',
-    BracketLeft: 'ü`´^[х[',
-    BracketRight: '+++$]ъ]',
-    Comma: ',,,;,б,',
-    Digit0: '000à000',
-    Digit1: '111&111',
-    Digit2: '222é222',
-    Digit3: '333"333',
-    Digit4: "444'444",
-    Digit5: '555(555',
-    Digit6: '666-666',
-    Digit7: '777è777',
-    Digit8: '888_888',
-    Digit9: '999ç999',
-    Equal: '´¡¿====',
-    IntlBackslash: '<<<<«/\\',
-    KeyA: 'aaaqαфa',
-    KeyB: 'bbbbβиb',
-    KeyC: 'ccccψсc',
-    KeyD: 'ddddδвd',
-    KeyE: 'eeeeεуe',
-    KeyF: 'ffffφаf',
-    KeyG: 'ggggγпg',
-    KeyH: 'hhhhηрh',
-    KeyI: 'iiiiιшi',
-    KeyJ: 'jjjjξоj',
-    KeyK: 'kkkkκлk',
-    KeyL: 'llllλдl',
-    KeyM: 'mmm,μьm',
-    KeyN: 'nnnnνтn',
-    KeyO: 'ooooοщo',
-    KeyP: 'ppppπзp',
-    KeyQ: 'qqqa;йq',
-    KeyR: 'rrrrρкr',
-    KeyS: 'ssssσыs',
-    KeyT: 'ttttτеt',
-    KeyU: 'uuuuθгu',
-    KeyV: 'vvvvωмv',
-    KeyW: 'wwwzςцw',
-    KeyX: 'xxxxχчx',
-    KeyY: 'zyyyυнy',
-    KeyZ: 'yzzwζяz',
-    Minus: "ß'')---",
-    Period: '...:.ю.',
-    Quote: "ä´{ù'э'",
-    Semicolon: 'öññm´ж;',
-    Slash: '---!/./'
-  }
-};
-
-
-export class LayoutDetector {
-  private _keys: {[index: string]: number} = {};
-  private _values: (string | string[])[] = [];
-  private _rec: string[] = [];
-  private _acc: string[] = Object.keys(MAP.acc);
-  private _cand: {layout: string; match: number;}[];
-  private _key: IKeyResult;
-
-  constructor() {
-    let p = 0;
-    for (const key of Object.keys(MAP.map)) {
-      this._keys[key] = p++;
-      this._values.push(MAP.map[key]);
-      this._rec.push('');
-    }
-    this._cand = [...this._acc].map(e => ({layout: e, match: 0 }));
-    this._key = { layouts: ['US_QWERTY'], certain: 0, key: undefined };
-  }
-
-  /**
-   * Reset detector.
-   */
-  public reset(): void {
-    this._rec = this._rec.map(e => '');
-  }
-
-  /**
-   * Return list of registered layouts.
-   */
-  public get layouts(): string[] {
-    return Object.keys(MAP.acc);
-  }
-
-  /**
-   * Return list of supported key codes.
-   */
-  public get codes(): string[] {
-    return Object.keys(MAP.map);
-  }
-
-  /**
-   * Get character key for key code and layout.
-   * The known key codes can be requested with `.codes`,
-   * the registered layouts with `.layouts`.
-   */
-  public getLayoutKey(code: string, layout: string): string | undefined {
-    if (MAP.acc[layout] !== undefined && MAP.map[code]) {
-      return MAP.map[code][MAP.acc[layout]];
-    }
-  }
-
-  /**
-   * Feed a keyboard event `ev` to the detector.
-   */
-  public feed(ev: KeyboardEvent): void {
-    // FIXME: Should we check for OS? (not supported by Safari)
-    if (ev.shiftKey || ev.ctrlKey || ev.altKey || ev.metaKey
-      || ev.getModifierState('AltGraph')
-      || ev.getModifierState('CapsLock')
-    ) {
-      return;
-    }
-    const pos = this._keys[ev.code];
-    if (pos !== undefined) {
-      if (this._rec[pos] && this._rec[pos] !== ev.key) {
-        // The key value should never change for the same layout,
-        // so we treat a sudden change as a layout change.
-        this.reset();
-      }
-      this._rec[pos] = ev.key;
-    }
-  }
-
-  /**
-   * Shows all known layouts and their degree of matching mappings
-   * sorted descending (likely layouts first).
-   * Ideally there is only one leading layout with a match of 1.
-   * If the leading match is not 1, then the user uses an
-   * unknown or custom layout.
-   */
-  public matches(): {layout: string; match: number;}[] {
-    for (let i = 0; i < this._cand.length; ++i) {
-      this._cand[i].match = 0;
-    }
-    let c = 0;
-    for (let k = 0; k < this._rec.length; ++k) {
-      const v = this._rec[k];
-      if (v) {
-        c++;
-        const values = this._values[k];
-        for (let i = 0; i < this._acc.length; ++i) {
-          if (v === values[i]) {
-            this._cand[i].match++;
-          }
-        }
-      }
-    }
-    if (!c) {
-      return this._cand;
-    }
-    const sorted = [...this._cand].sort((a, b) => b.match - a.match);
-    for (let i = 0; i < sorted.length; ++i) {
-      sorted[i].match /= c;
-    }
-    return sorted;
-  }
-
-  /**
-   * Tries to resolve a key code to a key character.
-   * If `certain` is 1 then the result matches the listed layouts.
-   * Ideally only one layout is returned, then the detector has seen enough
-   * key events in `feed`.
-   * If multiple layouts are returned but only one key, then the layout is
-   * not yet fully determined but the key code is already known from `feed`.
-   * When multiple keys are returned, then the character undetermined
-   * and the layout needs further resolving with resolve.
-   * A certainty lesser than 1 can have different reasons:
-   * - not enough key event fed yet (multiple layouts returned)
-   * - user has an unknown or custom layout
-   * If `certain` is 0 the result should not be used as the dector
-   * has not seen any key events at all.
-   */
-  public getKey(code: string): IKeyResult {
-    const lm = this.matches();
-    let candidates = [];
-    let lastMatch = 0;
-    for (let i = 0; i < lm.length; ++i) {
-      if (lm[i].match === 0) {
-        break;
-      }
-      if (lm[i].match === 1) {
-        lastMatch = 1;
-        candidates.push(lm[i].layout);
-      } else if (lm[i].match >= lastMatch) {
-        lastMatch = lm[i].match;
-        candidates.push(lm[i].layout);
-      }
-    }
-    if (candidates.length === 1) {
-      this._key.layouts = candidates;
-      this._key.certain = lastMatch;
-      this._key.key = MAP.map[code]?.[MAP.acc[candidates[0]]];
-      return this._key;
-    }
-    if (!candidates.length) {
-      candidates = [...this._acc];
-    }
-    const values = [];
-    for (let i = 0; i < candidates.length; ++i) {
-      values.push(MAP.map[code]?.[MAP.acc[candidates[i]]]);
-    }
-    const valuesSet = new Set(values);
-    // if all candidates yield the same char, return it
-    if (valuesSet.size === 1) {
-      this._key.layouts = candidates;
-      this._key.certain = lastMatch;
-      const [value] = values;
-      this._key.key = value;
-      return this._key;
-    }
-    // fallthrough
-    this._key.layouts = candidates;
-    this._key.certain = lastMatch / valuesSet.size;
-    this._key.key = values;
-    return this._key;
-  }
-
-  /**
-   * Calculate distance to resolve keyboard layout.
-   * Returns the candicate layouts and a list of keys resolving layout ambiguity.
-   * The key list is sorted descending by candidate differences for a key code
-   * (picking a high difference code needs less follow-up steps).
-   * The user should be asked to press the corresponding key and the key event
-   * should be fed to `feed`.
-   * Repeat this process until this method returns only one layout.
-   */
-  public resolve(): any {
-    const lm = this.matches();
-    let candidates = [];
-    let lastMatch = 0;
-    for (let i = 0; i < lm.length; ++i) {
-      if (lm[i].match === 0) {
-        break;
-      }
-      if (lm[i].match === 1) {
-        lastMatch = 1;
-        candidates.push(lm[i].layout);
-      } else if (lm[i].match >= lastMatch) {
-        lastMatch = lm[i].match;
-        candidates.push(lm[i].layout);
-      }
-    }
-    if (candidates.length === 1) {
-      return { layouts: candidates, keys: [] };
-    }
-    if (!candidates.length) {
-      candidates = [...this._acc];
-    }
-    const acc = candidates.map(e => MAP.acc[e]);
-    const codes = Object.keys(this._keys);
-    const values = [];
-    for (let i = 0; i < this._values.length; ++i) {
-      const value = new Set();
-      for (let k = 0; k < acc.length; ++k) {
-        value.add(this._values[i][k]);
-      }
-      const len = (new Set(value)).size;
-      if (len > 1) {
-        values.push({code: codes[i], keys: [...value].sort()});
-      }
-    }
-    values.sort((a, b) => b.keys.length - a.keys.length);
-    return { layouts: candidates, keys: values };
-  }
-}