# -*- coding: utf-8 -*- import struct import traceback from time import time as now from collections import namedtuple from ._keyboard_event import KeyboardEvent, KEY_DOWN, KEY_UP from ._canonical_names import all_modifiers, normalize_name from ._nixcommon import EV_KEY, aggregate_devices, ensure_root # TODO: start by reading current keyboard state, as to not missing any already pressed keys. # See: http://stackoverflow.com/questions/3649874/how-to-get-keyboard-state-in-linux def cleanup_key(name): """ Formats a dumpkeys format to our standard. """ name = name.lstrip('+') is_keypad = name.startswith('KP_') for mod in ('Meta_', 'Control_', 'dead_', 'KP_'): if name.startswith(mod): name = name[len(mod):] # Dumpkeys is weird like that. if name == 'Remove': name = 'Delete' elif name == 'Delete': name = 'Backspace' if name.endswith('_r'): name = 'right ' + name[:-2] if name.endswith('_l'): name = 'left ' + name[:-2] return normalize_name(name), is_keypad def cleanup_modifier(modifier): modifier = normalize_name(modifier) if modifier in all_modifiers: return modifier if modifier[:-1] in all_modifiers: return modifier[:-1] raise ValueError('Unknown modifier {}'.format(modifier)) """ Use `dumpkeys --keys-only` to list all scan codes and their names. We then parse the output and built a table. For each scan code and modifiers we have a list of names and vice-versa. """ from subprocess import check_output from collections import defaultdict import re to_name = defaultdict(list) from_name = defaultdict(list) keypad_scan_codes = set() def register_key(key_and_modifiers, name): if name not in to_name[key_and_modifiers]: to_name[key_and_modifiers].append(name) if key_and_modifiers not in from_name[name]: from_name[name].append(key_and_modifiers) def build_tables(): if to_name and from_name: return ensure_root() modifiers_bits = { 'shift': 1, 'alt gr': 2, 'ctrl': 4, 'alt': 8, } keycode_template = r'^keycode\s+(\d+)\s+=(.*?)$' dump = check_output(['dumpkeys', '--keys-only'], universal_newlines=True) for str_scan_code, str_names in re.findall(keycode_template, dump, re.MULTILINE): scan_code = int(str_scan_code) for i, str_name in enumerate(str_names.strip().split()): modifiers = tuple(sorted(modifier for modifier, bit in modifiers_bits.items() if i & bit)) name, is_keypad = cleanup_key(str_name) register_key((scan_code, modifiers), name) if is_keypad: keypad_scan_codes.add(scan_code) register_key((scan_code, modifiers), 'keypad ' + name) # dumpkeys consistently misreports the Windows key, sometimes # skipping it completely or reporting as 'alt. 125 = left win, # 126 = right win. if (125, ()) not in to_name or to_name[(125, ())] == 'alt': register_key((125, ()), 'windows') if (126, ()) not in to_name or to_name[(126, ())] == 'alt': register_key((126, ()), 'windows') # The menu key is usually skipped altogether, so we also add it manually. if (127, ()) not in to_name: register_key((127, ()), 'menu') synonyms_template = r'^(\S+)\s+for (.+)$' dump = check_output(['dumpkeys', '--long-info'], universal_newlines=True) for synonym_str, original_str in re.findall(synonyms_template, dump, re.MULTILINE): synonym, _ = cleanup_key(synonym_str) original, _ = cleanup_key(original_str) if synonym != original: from_name[original].extend(from_name[synonym]) from_name[synonym].extend(from_name[original]) device = None def build_device(): global device if device: return ensure_root() device = aggregate_devices('kbd') def init(): build_device() build_tables() pressed_modifiers = set() def listen(callback): build_device() build_tables() while True: time, type, code, value, device_id = device.read_event() if type != EV_KEY: continue scan_code = code event_type = KEY_DOWN if value else KEY_UP # 0 = UP, 1 = DOWN, 2 = HOLD pressed_modifiers_tuple = tuple(sorted(pressed_modifiers)) names = to_name[(scan_code, pressed_modifiers_tuple)] or to_name[(scan_code, ())] or ['unknown'] name = names[0] if name in all_modifiers: if event_type == KEY_DOWN: pressed_modifiers.add(name) else: pressed_modifiers.discard(name) is_keypad = scan_code in keypad_scan_codes callback(KeyboardEvent(event_type=event_type, scan_code=scan_code, name=name, time=time, device=device_id, is_keypad=is_keypad, modifiers=pressed_modifiers_tuple)) def write_event(scan_code, is_down): build_device() device.write_event(EV_KEY, scan_code, int(is_down)) def map_name(name): build_tables() for entry in from_name[name]: yield entry parts = name.split(' ', 1) if len(parts) > 1 and parts[0] in ('left', 'right'): for entry in from_name[parts[1]]: yield entry def press(scan_code): write_event(scan_code, True) def release(scan_code): write_event(scan_code, False) def type_unicode(character): codepoint = ord(character) hexadecimal = hex(codepoint)[len('0x'):] for key in ['ctrl', 'shift', 'u']: scan_code, _ = next(map_name(key)) press(scan_code) for key in hexadecimal: scan_code, _ = next(map_name(key)) press(scan_code) release(scan_code) for key in ['ctrl', 'shift', 'u']: scan_code, _ = next(map_name(key)) release(scan_code) if __name__ == '__main__': def p(e): print(e) listen(p)