class PyMouseEvent(PyMouseEventMeta): def __init__(self): PyMouseEventMeta.__init__(self) self.display = Display() self.display2 = Display() self.ctx = self.display2.record_create_context( 0, [record.AllClients ], [{ 'core_requests': (0, 0), 'core_replies': (0, 0), 'ext_requests': (0, 0, 0, 0), 'ext_replies': (0, 0, 0, 0), 'delivered_events': (0, 0), 'device_events': (X.KeyPress, X.MotionNotify), 'errors': (0, 0), 'client_started': False, 'client_died': False, }]) def run(self): if self.capture: self.display2.screen().root.grab_pointer( True, X.ButtonPressMask | X.ButtonReleaseMask, X.GrabModeAsync, X.GrabModeAsync, 0, 0, X.CurrentTime) self.display2.record_enable_context(self.ctx, self.handler) self.display2.record_free_context(self.ctx) def stop(self): self.display.record_disable_context(self.ctx) self.display.ungrab_pointer(X.CurrentTime) self.display.flush() self.display2.record_disable_context(self.ctx) self.display2.ungrab_pointer(X.CurrentTime) self.display2.flush() def handler(self, reply): data = reply.data while len(data): event, data = rq.EventField(None).parse_binary_value( data, self.display.display, None, None) if event.type == X.KeyPress: keysym = self.display.keycode_to_keysym(event.detail, 0) self.keypress(keysym) if event.type == X.KeyRelease: keysym = self.display.keycode_to_keysym(event.detail, 0) self.keyrelease(keysym) if event.type == X.ButtonPress: self.click(event.root_x, event.root_y, (None, 1, 3, 2, 3, 3, 3)[event.detail], True) elif event.type == X.ButtonRelease: self.click(event.root_x, event.root_y, (None, 1, 3, 2, 3, 3, 3)[event.detail], False) else: self.move(event.root_x, event.root_y)
class PyMouseEvent(PyMouseEventMeta): def __init__(self): PyMouseEventMeta.__init__(self) self.display = Display() self.display2 = Display() self.ctx = self.display2.record_create_context ( 0, [record.AllClients], [{ 'core_requests': (0, 0), 'core_replies': (0, 0), 'ext_requests': (0, 0, 0, 0), 'ext_replies': (0, 0, 0, 0), 'delivered_events': (0, 0), 'device_events': (X.KeyPress, X.MotionNotify), 'errors': (0, 0), 'client_started': False, 'client_died': False, }]) def run(self): if self.capture: self.display2.screen().root.grab_pointer(True, X.ButtonPressMask | X.ButtonReleaseMask, X.GrabModeAsync, X.GrabModeAsync, 0, 0, X.CurrentTime) self.display2.record_enable_context(self.ctx, self.handler) self.display2.record_free_context(self.ctx) def stop(self): self.display.record_disable_context(self.ctx) self.display.ungrab_pointer(X.CurrentTime) self.display.flush() self.display2.record_disable_context(self.ctx) self.display2.ungrab_pointer(X.CurrentTime) self.display2.flush() def handler(self, reply): data = reply.data while len(data): event, data = rq.EventField(None).parse_binary_value(data, self.display.display, None, None) if event.type == X.KeyPress: keysym = self.display.keycode_to_keysym(event.detail, 0) self.keypress(keysym) if event.type == X.KeyRelease: keysym = self.display.keycode_to_keysym(event.detail, 0) self.keyrelease(keysym) if event.type == X.ButtonPress: self.click(event.root_x, event.root_y, (None, 1, 3, 2, 3, 3, 3)[event.detail], True) elif event.type == X.ButtonRelease: self.click(event.root_x, event.root_y, (None, 1, 3, 2, 3, 3, 3)[event.detail], False) else: self.move(event.root_x, event.root_y)
def main(options): # current display pid_file = "/var/lock/easyxmotion.pid" # kill any old versions that are still running, # we do it this way so the current one has input focus. # might be a better way to just exit and give focus to the old one. try: with open(pid_file, "r") as fp: pid = int(fp.read()) try: os.kill(pid, signal.SIGTERM) except OSError: # other isn't running pass except IOError: # first ever run pass with open(pid_file, "w") as fp: fp.write(str(os.getpid())) osds, windows = display_osd(options) disp = Display() root = disp.screen().root root.change_attributes(event_mask=X.KeyPressMask) root.grab_keyboard(False, X.GrabModeAsync, X.GrabModeAsync, X.CurrentTime) event = disp.next_event() keycode = event.detail if event.type == X.KeyPress: key = XK.keysym_to_string(disp.keycode_to_keysym(keycode, 0)) if key and key in string.lowercase and string.lowercase.index(key) < len(windows): windows[string.lowercase.index(key)].activate(timestamp) disp.ungrab_keyboard(X.CurrentTime) sys.exit()
def main(options): # current display pid_file = '/var/lock/easyxmotion.pid' # kill any old versions that are still running, # we do it this way so the current one has input focus. # might be a better way to just exit and give focus to the old one. try: with open(pid_file, 'r') as fp: pid = int(fp.read()) try: os.kill(pid, signal.SIGTERM) except OSError: #other isn't running pass except IOError: # first ever run pass with open(pid_file, 'w') as fp: fp.write(str(os.getpid())) osds, windows = display_osd(options) disp = Display() root = disp.screen().root root.change_attributes(event_mask = X.KeyPressMask) root.grab_keyboard(False, X.GrabModeAsync, X.GrabModeAsync, X.CurrentTime) event = disp.next_event() keycode = event.detail if event.type == X.KeyPress: key = XK.keysym_to_string(disp.keycode_to_keysym(keycode, 0)) if key and key in string.lowercase and string.lowercase.index(key) < len(windows): windows[string.lowercase.index(key)].activate(timestamp) disp.ungrab_keyboard(X.CurrentTime) sys.exit()
class KeyListener: def __init__(self): self.disp = None self.root = None self.record = None def setup(self): # get current display self.disp = Display() self.root = self.disp.screen().root # Monitor keypress and button press ctx = self.disp.record_create_context( 0, [record.AllClients ], [{ 'core_requests': (0, 0), 'core_replies': (0, 0), 'ext_requests': (0, 0, 0, 0), 'ext_replies': (0, 0, 0, 0), 'delivered_events': (0, 0), 'device_events': (X.KeyReleaseMask, X.ButtonReleaseMask), 'errors': (0, 0), 'client_started': False, 'client_died': False, }]) self.disp.record_enable_context(ctx, self.handler) self.disp.record_free_context(ctx) def handler(self, reply): """ Self function is called when a xlib event is fired """ data = reply.data while len(data): event, data = rq.EventField(None).parse_binary_value( data, self.disp.display, None, None) if event.type == X.KeyPress: if event.detail == 36: self.record("enter") elif event.detail == 22: self.record("backspace") elif event.detail == 37: self.record("control") elif event.detail == 64: self.record("alt") elif event.detail == 108: self.record("alt-gr") key = XK.keysym_to_string( self.disp.keycode_to_keysym(event.detail, event.state)) if key: self.record(key) def key_listen_loop(self): while True: self.root.display.next_event() def set_recorder(self, r): self.record = r
class KeyEventLoop(object): def __init__(self): self.disp = Display() self.root = self.disp.screen().root def handler(self, reply): """ This function is called when a xlib event is fired """ data = reply.data while len(data): event, data = rq.EventField(None).parse_binary_value( data, self.disp.display, None, None) if event.type == X.KeyPress: pass elif event.type == X.KeyRelease: keycode = event.detail keysym = self.disp.keycode_to_keysym(keycode, 0) if hex(keysym) == MediaKey.media_next.value: ControllerApi.player.play_next() elif hex(keysym) == MediaKey.media_previous.value: ControllerApi.player.play_last() elif hex(keysym) == MediaKey.media_stop.value: ControllerApi.player.stop() elif hex(keysym) == MediaKey.media_play_pause.value: ControllerApi.player.play_or_pause() def run(self): # Monitor keypress and button press LOG.info("Linux multimedia hotkey start") ctx = self.disp.record_create_context( 0, [record.AllClients], [{ 'core_requests': (0, 0), 'core_replies': (0, 0), 'ext_requests': (0, 0, 0, 0), 'ext_replies': (0, 0, 0, 0), 'delivered_events': (0, 0), 'device_events': (X.KeyReleaseMask, X.ButtonReleaseMask), 'errors': (0, 0), 'client_started': False, 'client_died': False, }]) self.disp.record_enable_context(ctx, self.handler) self.disp.record_free_context(ctx) while 1: # Infinite wait, doesn't do anything as no events are grabbed event = self.root.display.next_event()
class KeyEventLoop(object): def __init__(self, player): self.player = player self.disp = Display() self.root = self.disp.screen().root def handler(self, reply): """ This function is called when a xlib event is fired """ data = reply.data while len(data): event, data = rq.EventField(None).parse_binary_value( data, self.disp.display, None, None) if event.type == X.KeyPress: pass elif event.type == X.KeyRelease: keycode = event.detail keysym = self.disp.keycode_to_keysym(keycode, 0) if hex(keysym) == MediaKey.media_next.value: self.player.play_next() elif hex(keysym) == MediaKey.media_previous.value: self.player.play_last() elif hex(keysym) == MediaKey.media_stop.value: self.player.stop() elif hex(keysym) == MediaKey.media_play_pause.value: self.player.play_or_pause() def run(self): # Monitor keypress and button press ctx = self.disp.record_create_context( 0, [record.AllClients], [{ 'core_requests': (0, 0), 'core_replies': (0, 0), 'ext_requests': (0, 0, 0, 0), 'ext_replies': (0, 0, 0, 0), 'delivered_events': (0, 0), 'device_events': (X.KeyReleaseMask, X.ButtonReleaseMask), 'errors': (0, 0), 'client_started': False, 'client_died': False, }]) self.disp.record_enable_context(ctx, self.handler) self.disp.record_free_context(ctx) while 1: # Infinite wait, doesn't do anything as no events are grabbed event = self.root.display.next_event()
class KeyBinder: def __init__(self): self._hold_keys = {} self._display = Display() def listen_hold(self, key, pressed_callback, released_callback): self._hold_keys[key] = (pressed_callback, released_callback) def bind_to_keys(self, keys, pressed_callback, *args): return XlibKeybinder.bind(keys, pressed_callback, *args) def start(self): XlibKeybinder.init() threading.Thread(target=self.run).start() def run(self): context = self._display.record_create_context( 0, [record.AllClients], [{ 'core_requests': (0, 0), 'core_replies': (0, 0), 'ext_requests': (0, 0, 0, 0), 'ext_replies': (0, 0, 0, 0), 'delivered_events': (0, 0), 'device_events': (X.KeyReleaseMask, X.ButtonReleaseMask), 'errors': (0, 0), 'client_started': False, 'client_died': False, }]) self._display.record_enable_context(context, self._event_handler) self._display.record_free_context(context) def _event_handler(self, reply): data = reply.data while len(data): event, data = rq.EventField(None).parse_binary_value(data, self._display.display, None, None) keysym = self._display.keycode_to_keysym(event.detail, 0) if keysym in self._hold_keys.keys(): pressed_callback, released_callback = self._hold_keys[keysym] if event.type == X.KeyPress: if pressed_callback: GLib.idle_add(pressed_callback) elif event.type == X.KeyRelease: if released_callback: GLib.idle_add(released_callback)
class PyKeyboardEvent(PyKeyboardEventMeta): """ The PyKeyboardEvent implementation for X11 systems (mostly linux). This allows one to listen for keyboard input. """ def __init__(self, display=None): PyKeyboardEventMeta.__init__(self) self.display = Display(display) self.display2 = Display(display) self.ctx = self.display2.record_create_context( 0, [record.AllClients], [{ 'core_requests': (0, 0), 'core_replies': (0, 0), 'ext_requests': (0, 0, 0, 0), 'ext_replies': (0, 0, 0, 0), 'delivered_events': (0, 0), 'device_events': (X.KeyPress, X.KeyRelease), 'errors': (0, 0), 'client_started': False, 'client_died': False, }]) self.shift_state = 0 # 0 is off, 1 is on self.alt_state = 0 # 0 is off, 2 is on self.mod_keycodes = self.get_mod_keycodes() def run(self): """Begin listening for keyboard input events.""" self.state = True if self.capture: self.display2.screen().root.grab_keyboard(True, X.KeyPressMask | X.KeyReleaseMask, X.GrabModeAsync, X.GrabModeAsync, 0, 0, X.CurrentTime) self.display2.record_enable_context(self.ctx, self.handler) self.display2.record_free_context(self.ctx) def stop(self): """Stop listening for keyboard input events.""" self.state = False self.display.record_disable_context(self.ctx) self.display.ungrab_keyboard(X.CurrentTime) self.display.flush() self.display2.record_disable_context(self.ctx) self.display2.ungrab_keyboard(X.CurrentTime) self.display2.flush() def handler(self, reply): """Upper level handler of keyboard events.""" data = reply.data while len(data): event, data = rq.EventField(None).parse_binary_value(data, self.display.display, None, None) if event.type == X.KeyPress: if self.escape_code(event): # Quit if this returns True self.stop() else: self._key_press(event.detail) elif event.type == X.KeyRelease: self._key_release(event.detail) else: print('WTF: {0}'.format(event.type)) def _key_press(self, keycode): """A key has been pressed, do stuff.""" #Alter modification states if keycode in self.mod_keycodes['Shift'] or keycode in self.mod_keycodes['Lock']: self.toggle_shift_state() elif keycode in self.mod_keycodes['Alt']: self.toggle_alt_state() else: self.key_press(keycode) def _key_release(self, keycode): """A key has been released, do stuff.""" #Alter modification states if keycode in self.mod_keycodes['Shift']: self.toggle_shift_state() elif keycode in self.mod_keycodes['Alt']: self.toggle_alt_state() else: self.key_release(keycode) def escape_code(self, event): if event.detail == self.lookup_character_value('Escape'): return True return False def lookup_char_from_keycode(self, keycode): keysym =self.display.keycode_to_keysym(keycode, self.shift_state + self.alt_state) if keysym: char = self.display.lookup_string(keysym) return char else: return None def get_mod_keycodes(self): """ Detects keycodes for modifiers and parses them into a dictionary for easy access. """ modifier_mapping = self.display.get_modifier_mapping() modifier_dict = {} nti = [('Shift', X.ShiftMapIndex), ('Control', X.ControlMapIndex), ('Mod1', X.Mod1MapIndex), ('Alt', X.Mod1MapIndex), ('Mod2', X.Mod2MapIndex), ('Mod3', X.Mod3MapIndex), ('Mod4', X.Mod4MapIndex), ('Mod5', X.Mod5MapIndex), ('Lock', X.LockMapIndex)] for n, i in nti: modifier_dict[n] = list(modifier_mapping[i]) return modifier_dict def lookup_character_value(self, character): """ Looks up the keysym for the character then returns the keycode mapping for that keysym. """ ch_keysym = string_to_keysym(character) if ch_keysym == 0: ch_keysym = string_to_keysym(special_X_keysyms[character]) return self.display.keysym_to_keycode(ch_keysym) def toggle_shift_state(self): '''Does toggling for the shift state.''' if self.shift_state == 0: self.shift_state = 1 elif self.shift_state == 1: self.shift_state = 0 else: return False return True def toggle_alt_state(self): '''Does toggling for the alt state.''' if self.alt_state == 0: self.alt_state = 2 elif self.alt_state == 2: self.alt_state = 0 else: return False return True
class KeyListener(threading.Thread): ''' Usage: keylistener = KeyListener() Initially: keylistener.addKeyListener("L_CTRL+L_SHIFT+y", callable) Note that it is necessary to bind all possible combinations because an order of key presses can be different, for example, "L_CTRL+y+L_SHIFT" Now: keylistener.addKeyListener("Control_L+c+c", callable) ''' def __init__(self): threading.Thread.__init__(self) self.finished = threading.Event() self.contextEventMask = [X.KeyPress, X.MotionNotify] # Give these some initial values # Hook to our display. self.local_dpy = Display() self.record_dpy = Display() self.pressed = [] self.listeners = {} self.character = None ''' 0: Nothing caught; 1: Read buffer and call main module; 2: Call main module ''' self.status = 0 ''' need the following because XK.keysym_to_string() only does printable chars rather than being the correct inverse of XK.string_to_keysym() ''' def lookup_keysym(self, keysym): for name in dir(XK): if name.startswith("XK_") and getattr(XK, name) == keysym: return name.lstrip("XK_") return '[%d]' % keysym def processevents(self, reply): if reply.category != record.FromServer: return if reply.client_swapped: print_v('* received swapped protocol data, cowardly ignored') return # I added 'str', since we receive an error without it if not len(str(reply.data)) or ord(str(reply.data[0])) < 2: # not an event return data = reply.data while len(data): event, data = rq.EventField(None).parse_binary_value( data, self.record_dpy.display, None, None) keycode = event.detail keysym = self.local_dpy.keycode_to_keysym(event.detail, 0) self.character = self.lookup_keysym(keysym) if self.character: if event.type == X.KeyPress: self.press() elif event.type == X.KeyRelease: self.release() def run(self): # Check if the extension is present if not self.record_dpy.has_extension('RECORD'): print_v('RECORD extension not found') sys.exit(1) r = self.record_dpy.record_get_version(0, 0) mes = 'RECORD extension version {}.{}'.format(r.major_version, r.minor_version) print_v(mes) # Create a recording context; we only want key events self.ctx = self.record_dpy.record_create_context( 0, [record.AllClients], [{ 'core_requests': (0, 0), 'core_replies': (0, 0), 'ext_requests': (0, 0, 0, 0), 'ext_replies': (0, 0, 0, 0), 'delivered_events': (0, 0) # (X.KeyPress, X.ButtonPress) , 'device_events': tuple(self.contextEventMask), 'errors': (0, 0), 'client_started': False, 'client_died': False, }]) ''' Enable the context; this only returns after a call to record_disable_context, while calling the callback function in the meantime ''' self.record_dpy.record_enable_context(self.ctx, self.processevents) # Finally free the context self.record_dpy.record_free_context(self.ctx) def cancel(self): self.finished.set() self.local_dpy.record_disable_context(self.ctx) self.local_dpy.flush() def append(self): if len(self.pressed) > 0: if self.pressed[0] in ('Control_L', 'Control_R', 'Alt_L', 'Alt_R'): self.pressed.append(self.character) def press(self): if len(self.pressed) == 2: if self.pressed[1] == 'grave': self.pressed = [] elif len(self.pressed) == 3: self.pressed = [] if self.character in ('Control_L', 'Control_R', 'Alt_L', 'Alt_R'): self.pressed = [self.character] elif self.character in ('c', 'Insert', 'grave'): self.append() action = self.listeners.get(tuple(self.pressed), False) print_v('Current action:', str(tuple(self.pressed))) if action: action() def release(self): """must be called whenever a key release event has occurred.""" # A released Control key is not taken into account # A cyrillic 'с' symbol is recognized as Latin 'c' if not self.character in ('c', 'Insert', 'grave'): self.pressed = [] def addKeyListener(self, hotkeys, callable): keys = tuple(hotkeys.split('+')) print_v('Added new keylistener for :', str(keys)) self.listeners[keys] = callable def check(self): # Returns 0..2 if self.status: print_v('Hotkey has been caught!') status = self.status self.status = 0 return status def set_status(self, status=0): self.status = status print_v('Setting status to %d!' % self.status)
class Handler(object): def __init__(self, fed): self.disp = None self.listenerObject = fed self.keysym_map = { 32: "SPACE", 39: "'", 44: ",", 45: "-", 46: ".", 47: "/", 48: "0", 49: "1", 50: "2", 51: "3", 52: "4", 53: "5", 54: "6", 55: "7", 56: "8", 57: "9", 59: ";", 61: "=", 91: "[", 92: "\\", 93: "]", 96: "`", 97: "a", 98: "b", 99: "c", 100: "d", 101: "e", 102: "f", 103: "g", 104: "h", 105: "i", 106: "j", 107: "k", 108: "l", 109: "m", 110: "n", 111: "o", 112: "p", 113: "q", 114: "r", 115: "s", 116: "t", 117: "u", 118: "v", 119: "w", 120: "x", 121: "y", 122: "z", 65293: "ENTER", 65307: "ESC", 65360: "HOME", 65361: "ARROW_LEFT", 65362: "ARROW_UP", 65363: "ARROW_RIGHT", 65505: "L_SHIFT", 65506: "R_SHIFT", 65507: "L_CTRL", 65508: "R_CTRL", 65513: "L_ALT", 65514: "R_ALT", 65515: "SUPER_KEY", 65288: "BACKSPACE", 65364: "ARROW_DOWN", 65365: "PG_UP", 65366: "PG_DOWN", 65367: "END", 65377: "PRTSCRN", 65535: "DELETE", 65383: "PRINT?", 65509: "CAPS_LOCK", 65289: "TAB", 65470: "F1", 65471: "F2", 65472: "F3", 65473: "F4", 65474: "F5", 65475: "F6", 65476: "F7", 65477: "F8", 65478: "F9", 65479: "F10", 65480: "F11", 65481: "F12" } # get current display self.disp = Display() self.root = self.disp.screen().root # Monitor keypress and button press self.context = self.disp.record_create_context( 0, [record.AllClients ], [{ 'core_requests': (0, 0), 'core_replies': (0, 0), 'ext_requests': (0, 0, 0, 0), 'ext_replies': (0, 0, 0, 0), 'delivered_events': (0, 0), 'device_events': (X.KeyReleaseMask, X.ButtonReleaseMask), 'errors': (0, 0), 'client_started': False, 'client_died': False, }]) self.disp.record_enable_context(self.context, self.handler) self.disp.record_free_context(self.context) while True: # Infinite wait, doesn't do anything as no events are grabbed event = self.root.display.next_event() def keyBind(self, sym): if sym in self.keysym_map: return self.keysym_map[sym] else: return sym def handler(self, reply): """ This function is called when a xlib event is fired """ data = reply.data while len(data): event, data = rq.EventField(None).parse_binary_value( data, self.disp.display, None, None) keycode = event.detail keysym = self.disp.keycode_to_keysym(event.detail, 0) if keysym in self.keysym_map: character = self.keyBind(keysym) # print character if event.type == X.KeyPress: self.listenerObject.press(character) elif event.type == X.KeyRelease: self.listenerObject.release(character)
class Service: def __init__(self) -> None: self.active_keys = { XK.string_to_keysym(key): False for key in SETTINGS.keybindings } self.modifying_keys = { XK.string_to_keysym(key): False for key in SETTINGS.keybinding_modifier } self.zp = ZoneProfile.from_file() self.coordinates = Coordinates() self.display = Display() self.root = self.display.screen().root self.context = self.display.record_create_context( 0, [record.AllClients], [ { "core_requests": (0, 0), "core_replies": (0, 0), "ext_requests": (0, 0, 0, 0), "ext_replies": (0, 0, 0, 0), "delivered_events": (0, 0), "device_events": (X.KeyReleaseMask, X.ButtonReleaseMask), "errors": (0, 0), "client_started": False, "client_died": False, } ], ) self.display.record_enable_context(self.context, self.handler) self.display.record_free_context(self.context) def handler(self, reply): data = reply.data while len(data): event, data = rq.EventField(None).parse_binary_value( data, self.display.display, None, None ) if event.type in (X.KeyPress, X.KeyRelease): keysym = self.display.keycode_to_keysym(event.detail, 0) if keysym in self.active_keys: self.active_keys[keysym] = ( True if event.type == X.KeyPress else False ) elif keysym in self.modifying_keys: self.modifying_keys[keysym] = ( True if event.type == X.KeyPress else False ) if all(self.active_keys.values()): self.coordinates.add(event.root_x, event.root_y) if (event.type, event.detail) == (X.ButtonRelease, X.Button1): snap_window(self, event.root_x, event.root_y) elif event.type == X.KeyPress: keysym = self.display.keycode_to_keysym(event.detail, 0) shift_window(self, keysym) else: self.coordinates.clear() def listen(self): while True: self.root.display.next_event()
class PyKeyboardEvent(PyKeyboardEventMeta): """ The PyKeyboardEvent implementation for X11 systems (mostly linux). This allows one to listen for keyboard input. """ def __init__(self, display=None): self.display = Display(display) self.display2 = Display(display) self.ctx = self.display2.record_create_context( 0, [record.AllClients], [{ 'core_requests': (0, 0), 'core_replies': (0, 0), 'ext_requests': (0, 0, 0, 0), 'ext_replies': (0, 0, 0, 0), 'delivered_events': (0, 0), 'device_events': (X.KeyPress, X.KeyRelease), 'errors': (0, 0), 'client_started': False, 'client_died': False, }]) self.lock_meaning = None #Get these dictionaries for converting keysyms and strings self.keysym_to_string, self.string_to_keysym = self.get_translation_dicts() #Identify and register special groups of keys self.modifier_keycodes = {} self.all_mod_keycodes = [] self.keypad_keycodes = [] #self.configure_keys() #Direct access to the display's keycode-to-keysym array #print('Keycode to Keysym map') #for i in range(len(self.display._keymap_codes)): # print('{0}: {1}'.format(i, self.display._keymap_codes[i])) PyKeyboardEventMeta.__init__(self) def run(self): """Begin listening for keyboard input events.""" self.state = True if self.capture: self.display2.screen().root.grab_keyboard(True, X.KeyPressMask | X.KeyReleaseMask, X.GrabModeAsync, X.GrabModeAsync, 0, 0, X.CurrentTime) self.display2.record_enable_context(self.ctx, self.handler) self.display2.record_free_context(self.ctx) def stop(self): """Stop listening for keyboard input events.""" self.state = False self.display.record_disable_context(self.ctx) self.display.ungrab_keyboard(X.CurrentTime) self.display.flush() self.display2.record_disable_context(self.ctx) self.display2.ungrab_keyboard(X.CurrentTime) self.display2.flush() def handler(self, reply): """Upper level handler of keyboard events.""" data = reply.data while len(data): event, data = rq.EventField(None).parse_binary_value(data, self.display.display, None, None) if self.escape(event): # Quit if this returns True self.stop() else: self._tap(event) def _tap(self, event): keycode = event.detail press_bool = (event.type == X.KeyPress) #Detect modifier states from event.state for mod, bit in self.modifier_bits.items(): self.modifiers[mod] = event.state & bit if keycode in self.all_mod_keycodes: keysym = self.display.keycode_to_keysym(keycode, 0) character = self.keysym_to_string[keysym] else: character = self.lookup_char_from_keycode(keycode) #All key events get passed to self.tap() self.tap(keycode, character, press=press_bool) def lookup_char_from_keycode(self, keycode): """ This will conduct a lookup of the character or string associated with a given keycode. """ #TODO: Logic should be strictly adapted from X11's src/KeyBind.c #Right now the logic is based off of #http://tronche.com/gui/x/xlib/input/keyboard-encoding.html #Which I suspect is not the whole story and may likely cause bugs keysym_index = 0 #TODO: Display's Keysyms per keycode count? Do I need this? #If the Num_Lock is on, and the keycode corresponds to the keypad if self.modifiers['Num_Lock'] and keycode in self.keypad_keycodes: if self.modifiers['Shift'] or self.modifiers['Shift_Lock']: keysym_index = 0 else: keysym_index = 1 elif not self.modifiers['Shift'] and self.modifiers['Caps_Lock']: #Use the first keysym if uppercase or uncased #Use the uppercase keysym if the first is lowercase (second) keysym_index = 0 keysym = self.display.keycode_to_keysym(keycode, keysym_index) #TODO: Support Unicode, Greek, and special latin characters if keysym & 0x7f == keysym and chr(keysym) in 'abcdefghijklmnopqrstuvwxyz': keysym_index = 1 elif self.modifiers['Shift'] and self.modifiers['Caps_Lock']: keysym_index = 1 keysym = self.display.keycode_to_keysym(keycode, keysym_index) #TODO: Support Unicode, Greek, and special latin characters if keysym & 0x7f == keysym and chr(keysym) in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ': keysym_index = 0 elif self.modifiers['Shift'] or self.modifiers['Shift_Lock']: keysym_index = 1 if self.modifiers['Mode_switch']: keysym_index += 2 #Finally! Get the keysym keysym = self.display.keycode_to_keysym(keycode, keysym_index) #If the character is ascii printable, return that character if keysym & 0x7f == keysym and self.ascii_printable(keysym): return chr(keysym) #If the character was not printable, look for its name try: char = self.keysym_to_string[keysym] except KeyError: print('Unable to determine character.') print('Keycode: {0} KeySym {1}'.format(keycode, keysym)) return None else: return char def escape(self, event): if event.detail == self.lookup_character_keycode('Escape'): return True return False def configure_keys(self): """ This function locates the keycodes corresponding to special groups of keys and creates data structures of them for use by the PyKeyboardEvent instance; including the keypad keys and the modifiers. The keycodes pertaining to the keyboard modifiers are assigned by the modifier name in a dictionary. This dictionary can be accessed in the following manner: self.modifier_keycodes['Shift'] # All keycodes for Shift Masking It also assigns certain named modifiers (Alt, Num_Lock, Super), which may be dynamically assigned to Mod1 - Mod5 on different platforms. This should generally allow the user to do the following lookups on any system: self.modifier_keycodes['Alt'] # All keycodes for Alt Masking self.modifiers['Alt'] # State of Alt mask, non-zero if "ON" """ modifier_mapping = self.display.get_modifier_mapping() all_mod_keycodes = [] mod_keycodes = {} mod_index = [('Shift', X.ShiftMapIndex), ('Lock', X.LockMapIndex), ('Control', X.ControlMapIndex), ('Mod1', X.Mod1MapIndex), ('Mod2', X.Mod2MapIndex), ('Mod3', X.Mod3MapIndex), ('Mod4', X.Mod4MapIndex), ('Mod5', X.Mod5MapIndex)] #This gets the list of all keycodes per Modifier, assigns to name for name, index in mod_index: codes = [v for v in list(modifier_mapping[index]) if v] mod_keycodes[name] = codes all_mod_keycodes += codes def lookup_keycode(string): keysym = self.string_to_keysym[string] return self.display.keysym_to_keycode(keysym) #Dynamically assign Lock to Caps_Lock, Shift_Lock, Alt, Num_Lock, Super, #and mode switch. Set in both mod_keycodes and self.modifier_bits #Try to assign Lock to Caps_Lock or Shift_Lock shift_lock_keycode = lookup_keycode('Shift_Lock') caps_lock_keycode = lookup_keycode('Caps_Lock') if shift_lock_keycode in mod_keycodes['Lock']: mod_keycodes['Shift_Lock'] = [shift_lock_keycode] self.modifier_bits['Shift_Lock'] = self.modifier_bits['Lock'] self.lock_meaning = 'Shift_Lock' elif caps_lock_keycode in mod_keycodes['Lock']: mod_keycodes['Caps_Lock'] = [caps_lock_keycode] self.modifier_bits['Caps_Lock'] = self.modifier_bits['Lock'] self.lock_meaning = 'Caps_Lock' else: self.lock_meaning = None #print('Lock is bound to {0}'.format(self.lock_meaning)) #Need to find out which Mod# to use for Alt, Num_Lock, Super, and #Mode_switch num_lock_keycodes = [lookup_keycode('Num_Lock')] alt_keycodes = [lookup_keycode(i) for i in ['Alt_L', 'Alt_R']] super_keycodes = [lookup_keycode(i) for i in ['Super_L', 'Super_R']] mode_switch_keycodes = [lookup_keycode('Mode_switch')] #Detect Mod number for Alt, Num_Lock, and Super for name, keycodes in list(mod_keycodes.items()): for alt_key in alt_keycodes: if alt_key in keycodes: mod_keycodes['Alt'] = keycodes self.modifier_bits['Alt'] = self.modifier_bits[name] for num_lock_key in num_lock_keycodes: if num_lock_key in keycodes: mod_keycodes['Num_Lock'] = keycodes self.modifier_bits['Num_Lock'] = self.modifier_bits[name] for super_key in super_keycodes: if super_key in keycodes: mod_keycodes['Super'] = keycodes self.modifier_bits['Super'] = self.modifier_bits[name] for mode_switch_key in mode_switch_keycodes: if mode_switch_key in keycodes: mod_keycodes['Mode_switch'] = keycodes self.modifier_bits['Mode_switch'] = self.modifier_bits[name] #Assign the mod_keycodes to a local variable for access self.modifier_keycodes = mod_keycodes self.all_mod_keycodes = all_mod_keycodes #TODO: Determine if this might fail, perhaps iterate through the mapping #and identify all keycodes with registered keypad keysyms? #Acquire the full list of keypad keycodes self.keypad_keycodes = [] keypad = ['Space', 'Tab', 'Enter', 'F1', 'F2', 'F3', 'F4', 'Home', 'Left', 'Up', 'Right', 'Down', 'Prior', 'Page_Up', 'Next', 'Page_Down', 'End', 'Begin', 'Insert', 'Delete', 'Equal', 'Multiply', 'Add', 'Separator', 'Subtract', 'Decimal', 'Divide', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'] for keyname in keypad: keypad_keycode = self.lookup_character_keycode('KP_' + keyname) self.keypad_keycodes.append(keypad_keycode) def lookup_character_keycode(self, character): """ Looks up the keysym for the character then returns the keycode mapping for that keysym. """ keysym = self.string_to_keysym.get(character, 0) if keysym == 0: keysym = self.string_to_keysym.get(special_X_keysyms[character], 0) return self.display.keysym_to_keycode(keysym) def get_translation_dicts(self): """ Returns dictionaries for the translation of keysyms to strings and from strings to keysyms. """ keysym_to_string_dict = {} string_to_keysym_dict = {} #XK loads latin1 and miscellany on its own; load latin2-4 and greek Xlib.XK.load_keysym_group('latin2') Xlib.XK.load_keysym_group('latin3') Xlib.XK.load_keysym_group('latin4') Xlib.XK.load_keysym_group('greek') #Make a standard dict and the inverted dict for string, keysym in Xlib.XK.__dict__.items(): if string.startswith('XK_'): string_to_keysym_dict[string[3:]] = keysym keysym_to_string_dict[keysym] = string[3:] return keysym_to_string_dict, string_to_keysym_dict def ascii_printable(self, keysym): """ If the keysym corresponds to a non-printable ascii character this will return False. If it is printable, then True will be returned. ascii 11 (vertical tab) and ascii 12 are printable, chr(11) and chr(12) will return '\x0b' and '\x0c' respectively. """ if 0 <= keysym < 9: return False elif 13 < keysym < 32: return False elif keysym > 126: return False else: return True
class Wrapper: def __init__(self): self.dpy = Display() self.drag_start_event = None self.drag_attr = None self.on_alt_drag_handler = None self.on_alt_click_handler = None self.keys = {} self.screen_width = self.dpy.screen().width_in_pixels self.screen_height = self.dpy.screen().height_in_pixels self.gc = self.dpy.screen().root.create_gc( foreground=self.dpy.screen().black_pixel, background=self.dpy.screen().white_pixel, ) self.on(lambda ev: print(ev._data), XK.XK_F1, X.Mod1Mask) self.on(lambda ev: print(ev._data), XK.XK_a, X.Mod1Mask) def on(self, func, key, modifier1=0, modifier2=0): self.keys[(key, modifier1, modifier2)] = func def on_alt_drag(self, func): self.on_alt_drag_handler = func def on_alt_drag_end(self, func): self.on_alt_drag_end_handler = func def on_alt_click(self, func): self.on_alt_click_handler = func def connect_key_combinations(self): for key in self.keys.keys(): self.dpy.screen().root.grab_key(self.dpy.keysym_to_keycode(key[0]), key[1] | key[2], 1, X.GrabModeAsync, X.GrabModeAsync) # self.dpy.screen().root.grab_key(X.AnyKey, X.AnyModifier, 1, # X.GrabModeAsync, X.GrabModeAsync) self.dpy.screen().root.grab_button( 1, X.Mod1Mask, 1, X.ButtonPressMask | X.ButtonReleaseMask | X.PointerMotionMask, X.GrabModeAsync, X.GrabModeAsync, X.NONE, X.NONE) self.dpy.screen().root.grab_button( 3, X.Mod1Mask, 1, X.ButtonPressMask | X.ButtonReleaseMask | X.PointerMotionMask, X.GrabModeAsync, X.GrabModeAsync, X.NONE, X.NONE) def run(self): self.connect_key_combinations() while 1: event = self.dpy.next_event() self.dpy.screen().root.fill_rectangle(self.gc, 0, 0, self.screen_width, self.screen_height) if event.type == X.KeyPress: for handler, func in self.keys.items(): if self.dpy.keycode_to_keysym( event.detail, 0 ) == handler[0] and event.state == handler[1] | handler[2]: func(event) # event.detail # print('\n', '\n'.join(dir(event))) # for item, value in event.__dict__.items(): # print(item, value) print(event._data, event.detail, self.dpy.keycode_to_keysym(event.detail, 0), chr(event.detail), XK.XK_F1) if self.dpy.keycode_to_keysym( event.detail, 0) == XK.XK_F1 and event.child != X.NONE: self.handle_move_to_front(event) elif event.type == X.ButtonPress: self.handle_drag_or_resize_start(event) if self.on_alt_click_handler: self.on_alt_click_handler(event) elif event.type == X.MotionNotify and self.drag_start_event: if (self.on_alt_drag_handler): xdiff = event.root_x - self.drag_start_event.root_x ydiff = event.root_y - self.drag_start_event.root_y self.on_alt_drag_handler((xdiff, ydiff), self.drag_attr, self.drag_start_event, event) # if self.drag_start_event.detail == 1: # self.handle_move(event) # elif self.drag_start_event.detail == 3: # self.handle_resize(event) elif event.type == X.ButtonRelease: if (self.on_alt_drag_end_handler): self.on_alt_drag_end_handler(self.drag_start_event, event) self.drag_start_event = None def handle_move_to_front(self, event): event.child.configure(stack_mode=X.Above) def handle_drag_or_resize_start(self, event): if event.child != X.NONE: self.drag_attr = event.child.get_geometry() self.drag_start_event = event def handle_move(self, event): xdiff = event.root_x - self.drag_start_event.root_x ydiff = event.root_y - self.drag_start_event.root_y self.drag_start_event.child.configure(x=self.drag_attr.x + xdiff, y=self.drag_attr.y + ydiff) def handle_resize(self, event): xdiff = event.root_x - self.drag_start_event.root_x ydiff = event.root_y - self.drag_start_event.root_y self.drag_start_event.child.configure( width=max(1, self.drag_attr.width + xdiff), height=max(1, self.drag_attr.height + ydiff))
activate_window(ev.window) elif ev.type == X.ButtonPress and ev.child != X.NONE: geom = ev.child.get_geometry() mouse_ev = ev elif ev.type == X.MotionNotify and mouse_ev: dx = ev.root_x - mouse_ev.root_x dy = ev.root_y - mouse_ev.root_y mouse_ev.child.configure( x=(geom.x + (dx if mouse_ev.detail == 1 else 0)), y=(geom.y + (dy if mouse_ev.detail == 1 else 0)), width=max(1, geom.width + (dx if mouse_ev.detail == 3 else 0)), height=max(1, geom.height + (dy if mouse_ev.detail == 3 else 0))) elif ev.type == X.ButtonRelease: mouse_ev = None elif ev.type == X.KeyPress and wids: handler = bindings.get((ev.state, dpy.keycode_to_keysym(ev.detail, 0))) if handler: handler[0](handler[1]) root.change_property( net_client_list, Xatom.WINDOW, 32, [w.id for w in wids], ) root.change_property( net_active_window, Xatom.WINDOW, 32, [wids[-1].id] if wids else [X.NONE], )
class PyKeyboardEvent(PyKeyboardEventMeta): """ The PyKeyboardEvent implementation for X11 systems (mostly linux). This allows one to listen for keyboard input. """ def __init__(self, display=None): PyKeyboardEventMeta.__init__(self) self.display = Display(display) self.display2 = Display(display) self.ctx = self.display2.record_create_context( 0, [record.AllClients], [{ 'core_requests': (0, 0), 'core_replies': (0, 0), 'ext_requests': (0, 0, 0, 0), 'ext_replies': (0, 0, 0, 0), 'delivered_events': (0, 0), 'device_events': (X.KeyPress, X.KeyRelease), 'errors': (0, 0), 'client_started': False, 'client_died': False, }]) self.shift_state = 0 # 0 is off, 1 is on self.alt_state = 0 # 0 is off, 2 is on self.mod_keycodes = self.get_mod_keycodes() def run(self): """Begin listening for keyboard input events.""" self.state = True if self.capture: self.display2.screen().root.grab_keyboard(True, X.KeyPressMask | X.KeyReleaseMask, X.GrabModeAsync, X.GrabModeAsync, 0, 0, X.CurrentTime) self.display2.record_enable_context(self.ctx, self.handler) self.display2.record_free_context(self.ctx) def stop(self): """Stop listening for keyboard input events.""" self.state = False self.display.record_disable_context(self.ctx) self.display.ungrab_keyboard(X.CurrentTime) self.display.flush() self.display2.record_disable_context(self.ctx) self.display2.ungrab_keyboard(X.CurrentTime) self.display2.flush() def handler(self, reply): """Upper level handler of keyboard events.""" data = reply.data while len(data): event, data = rq.EventField(None).parse_binary_value(data, self.display.display, None, None) if event.type == X.KeyPress: if self.escape_code(event): # Quit if this returns True self.stop() else: self._key_press(event.detail) elif event.type == X.KeyRelease: self._key_release(event.detail) else: print('WTF: {0}'.format(event.type)) def _key_press(self, keycode): """A key has been pressed, do stuff.""" #Alter modification states if keycode in self.mod_keycodes['Shift'] or keycode in self.mod_keycodes['Lock']: self.toggle_shift_state() elif keycode in self.mod_keycodes['Alt']: self.toggle_alt_state() else: self.key_press(keycode) def _key_release(self, keycode): """A key has been released, do stuff.""" #Alter modification states if keycode in self.mod_keycodes['Shift']: self.toggle_shift_state() elif keycode in self.mod_keycodes['Alt']: self.toggle_alt_state() else: self.key_release(keycode) def escape_code(self, event): if event.detail == self.lookup_character_value('Escape'): return True return False def lookup_char_from_keycode(self, keycode): keysym =self.display.keycode_to_keysym(keycode, self.shift_state + self.alt_state) if keysym: char = self.display.lookup_string(keysym) return char else: return None def get_mod_keycodes(self): """ Detects keycodes for modifiers and parses them into a dictionary for easy access. """ modifier_mapping = self.display.get_modifier_mapping() modifier_dict = {} nti = [('Shift', X.ShiftMapIndex), ('Control', X.ControlMapIndex), ('Mod1', X.Mod1MapIndex), ('Alt', X.Mod1MapIndex), ('Mod2', X.Mod2MapIndex), ('Mod3', X.Mod3MapIndex), ('Mod4', X.Mod4MapIndex), ('Mod5', X.Mod5MapIndex), ('Lock', X.LockMapIndex)] for n, i in nti: modifier_dict[n] = list(modifier_mapping[i]) return modifier_dict def lookup_character_value(self, character): """ Looks up the keysym for the character then returns the keycode mapping for that keysym. """ ch_keysym = string_to_keysym(character) if ch_keysym == 0: ch_keysym = string_to_keysym(special_X_keysyms[character]) return self.display.keysym_to_keycode(ch_keysym) def toggle_shift_state(self): '''Does toggling for the shift state.''' if self.shift_state == 0: self.shift_state = 1 elif self.shift_state == 1: self.shift_state = 0 else: return False return True def toggle_alt_state(self): '''Does toggling for the alt state.''' if self.alt_state == 0: self.alt_state = 2 elif self.alt_state == 2: self.alt_state = 0 else: return False return True
class LinuxKeyLogger(threading.Thread): """ This implementation of the keylogger is designed to work on linux based systems. WILL NOT FUNCTION ON OTHER OPERATING SYSTEMS. """ def __init__(self): """ Constructs the logger and its internal objects """ super().__init__() self.display = Display() self.root = self.display.screen().root self.capturedKeys = [] self.windowKeys = [] self.capture = True def run(self): """ Starts the logging process """ self.log() def log(self): """ Sets up the root window to capture the keys being typed in. """ self.root.change_attributes(event_mask=X.KeyPressMask | X.KeyReleaseMask) self.root.grab_keyboard(0, X.GrabModeAsync, X.GrabModeAsync, X.CurrentTime) try: while self.capture: event = self.display.next_event() self.handleEvent(event) self.display.allow_events(X.AsyncKeyboard, X.CurrentTime) except Exception as e: print(e) def handleEvent(self, event): """ The Xlib library will produce events when a key is pressed this method will analyze those events and extract the pertainent information along with passing that information further down the line to be processed by the operating system. :param event: :return: None """ if (event.type == X.KeyRelease): char = Xlib.XK.keysym_to_string( self.display.keycode_to_keysym(event.detail, event.state)) if char is not None: self.capturedKeys.append(char) self.windowKeys.append(char) self.send_key(event.detail, event.state) self.phrase_check() # self.send_keyup(event.detail, event.state) elif (event.type == X.KeyPress): pass # try: # self.send_keydown(event.detail, event.status) # except AttributeError as ae: # print(ae) # window = self.display.get_input_focus()._data["focus"] # window.send_event(event,propagate=True) def phrase_check(self): """ This method will check to see if the typed in keys correspond to any of the preset phrases that have associated executions. :return: """ # will need to create a smarter way of doing this stop = self.checkPhrase(self.getStopPhrase()) if (stop): self.capture = False openT = self.checkPhrase(self.getTerminalPhrase()) if (openT): self.openterminal() # ensure window size is maintained maxLength = max(len(self.getStopPhrase()), len(self.getTerminalPhrase())) if len(self.windowKeys) > maxLength: self.windowKeys = self.windowKeys[len(self.windowKeys) - maxLength:] def checkPhrase(self, phrase): """ Checks whether a phrase matches the most recently typed in keys. """ length = len(phrase) capLength = len(self.windowKeys) if (capLength >= length): section = self.windowKeys[capLength - length:capLength] lastWords = ''.join(section) if (lastWords.upper() == phrase): return True return False def send_key(self, emulated_key, state): """Sends a key downstream to be processed by the computer""" self.send_keydown(emulated_key, state) self.send_keyup(emulated_key, state) def send_keyup(self, emulated_key, state): """Sends an key up message downstream to be processed by the computer""" shift_mask = state # Xlib.X.ShiftMask window = self.display.get_input_focus()._data["focus"] event = Xlib.protocol.event.KeyRelease(time=int(time.time()), root=self.display.screen().root, window=window, same_screen=0, child=Xlib.X.NONE, root_x=0, root_y=0, event_x=0, event_y=0, state=shift_mask, detail=emulated_key) window.send_event(event, propagate=True) def send_keydown(self, emulated_key, state): """Sends a key down message downstream to be processed by the computer""" shift_mask = state # Xlib.X.ShiftMask window = self.display.get_input_focus()._data["focus"] event = Xlib.protocol.event.KeyPress(time=int(time.time()), root=self.root, window=window, same_screen=0, child=Xlib.X.NONE, root_x=0, root_y=0, event_x=0, event_y=0, state=shift_mask, detail=emulated_key) window.send_event(event, propagate=True) def openterminal(self): """ This method will open up a terminal on a linux machine. If this application is running with root privileges then the terminal will also be given root privileges. :return: """ subprocess.call("gnome-terminal") def getStopPhrase(self): """ When this phrase is typed in the keylogging will stop running """ return "MISCHIEF MANAGED" def getTerminalPhrase(self): """ When this phrase is typed in a terminal will be created and this terminal will have the same privileges that the key logger is running as. """ return "ROOT" def hasInfoToSend(self): """ Determines whether or not there have been keys that have been captured. :return: True if there are captured keys otherwise False """ return len(self.capturedKeys) > 0 def getInfo(self): """ Provides the caller with a string of the captured keys. It is important to note that a call to this method is a mutating call. This means that once the information is retrieved it is purged from this object. :return: A string of the captured keys """ ret = ''.join(self.capturedKeys) self.capturedKeys = [] return ret
class SystemHotkey(MixIn): ''' Cross platform System Wide Hotkeys Modifer oder doesn't matter, e.g binding to control shift k is the same as shift control k, limitation of the keyboard and operating systems not this library ''' hk_ref = {} keybinds = {} def __init__(self, consumer='callback', check_queue_interval=0.0001, use_xlib=False, conn=None, verbose=False, unite_kp=True): ''' if the consumer param = 'callback', -> All hotkeys will require a callback function Otherwise set consumer to a function to hanlde the event. the function signature: event, hotkey, args event is the xwindow/microsoft keyboard event hotkey is a tuple, args is a list of any ars parsed in at the time of registering check_queue_interval is in seconds and sets the sleep time on checking the queue for hotkey presses set use_xlib to true to use the xlib python bindings (GPL) instead of the xcb ones (BSD) You can pass an exisiting X display or connection using the conn keyword, otherwise one will be created for you. keybinds will work regardless if numlock/capslock are on/off. so kp_3 will also bind to kp_page_down If you do not want numpad keys to have the same function when numlock is on or off set unite_kp to False (only windows) TODO This is still under development, triggering the key with other modifyers such as shift or fn keys may or maynot work ''' # Changes the class methods to point to differenct functions # Depening on the operating system and library used # Consumer can be set to a function also, which will be sent the event # as well as the key and mask already broken out # Last option for consumer is False, then you have to listen to the queue yourself # data_queue self.verbose = verbose self.use_xlib = use_xlib self.consumer = consumer self.check_queue_interval = check_queue_interval self.unite_kp = unite_kp if os.name == 'posix' and not unite_kp: # see _get_keysym raise NotImplementedError def mark_event_type(event): # event gets an event_type attribute so the user has a portiabble way # actually on windows as far as i know you dont have the option of binding on keypress or release so... # anyway ahve to check it but for now u dont! if os.name == 'posix': if self.use_xlib: if event.type == X.KeyPress: event.event_type = 'keypress' elif event.type == X.KeyRelease: event.event_type = 'keyrelease' else: if isinstance(event, xproto.KeyPressEvent): event.event_type = 'keypress' if isinstance(event, xproto.KeyReleaseEvent): event.event_type = 'keyrelease' else: event.event_type = 'keypress' return event self.data_queue = queue.Queue() if os.name == 'nt': self.hk_action_queue = queue.Queue() self.modders = win_modders self.trivial_mods = win_trivial_mods self._the_grab = self._nt_the_grab self._get_keycode = self._nt_get_keycode self._get_keysym = self._nt_get_keysym thread.start_new_thread( self._nt_wait, (), ) elif use_xlib: # Use the python-xlib library bindings, GPL License self.modders = xlib_modifiers self.trivial_mods = xlib_trivial_mods self._the_grab = self._xlib_the_grab self._get_keycode = self._xlib_get_keycode self._get_keysym = self._xlib_get_keysym if not conn: self.disp = Display() else: self.disp = conn self.xRoot = self.disp.screen().root self.xRoot.change_attributes(event_mask=X.KeyPressMask) thread.start_new_thread( self._xlib_wait, (), ) else: # Using xcb and the xcffib python bindings Apache 2 http://stackoverflow.com/questions/40100/apache-license-vs-bsd-vs-mit self.modders = xcb_modifiers self.trivial_mods = xcb_trivial_mods self._the_grab = self._xcb_the_grab self._get_keycode = self._xcb_get_keycode self._get_keysym = self._xcb_get_keysym if not conn: self.conn = xcffib.connect() else: self.conn = conn self.root = self.conn.get_setup().roots[0].root thread.start_new_thread( self._xcb_wait, (), ) if consumer == 'callback': if self.verbose: print('In Callback') def thread_me(): while 1: time.sleep(self.check_queue_interval) try: event = self.data_queue.get(block=False) except queue.Empty: pass else: event = mark_event_type(event) hotkey = self.parse_event(event) if not hotkey: continue #~ for cb in self.get_callback(hotkey, event.event_type): #when i was using the keypress / keyrelease shit for cb in self.get_callback(hotkey): if event.event_type == 'keypress': if self.verbose: print('calling ', repr(cb)) cb( event ) # TODO either throw these up in a thread, or pass in a queue to be put onto thread.start_new_thread( thread_me, (), ) elif callable(consumer): def thread_me(): while 1: time.sleep(self.check_queue_interval) try: event = self.data_queue.get(block=False) except queue.Empty: pass else: hotkey = self.parse_event(mark_event_type(event)) if not hotkey: continue if event.event_type == 'keypress': args = [cb for cb in self.get_callback(hotkey)] #~ callbacks = [cb for cb in self.get_callback(hotkey, event.event_type)] consumer(event, hotkey, args) thread.start_new_thread( thread_me, (), ) else: print('You need to handle grabbing events yourself!') def _xlib_wait(self): # Pushes Event onto queue while 1: event = self.xRoot.display.next_event() self.data_queue.put(event) def _xcb_wait(self): # Pushes Event onto queue while 1: event = self.conn.wait_for_event() self.data_queue.put(event) def _nt_wait(self): # Pushes Event onto queue # I don't understand the windows msg system # I can only get hotkeys to work if they are registeed in the # Thread that is listening for them. # So any changes to the hotkeys have to be signaled to be done # By the thread. (including unregistering) # A new queue is checked and runs functions, either adding # or removing new hotkeys, then the windows msg queue is checked msg = ctypes.wintypes.MSG() while 1: try: remove_or_add = self.hk_action_queue.get(block=False) except queue.Empty: pass else: remove_or_add() # Checking the windows message Queue if user32.PeekMessageA(byref(msg), 0, 0, 0, PM_REMOVE): if msg.message == win32con.WM_HOTKEY: self.data_queue.put(msg) else: print('some other message') time.sleep(self.check_queue_interval) def _nt_get_keycode(self, key, disp=None): return vk_codes.get(key) def _nt_get_keysym(self, keycode): for key, value in vk_codes.items(): if value == keycode: return key def _nt_the_grab(self, keycode, masks, id, root=None): keysym = self._get_keysym(keycode) aliases = NUMPAD_ALIASES.get(keysym) # register numpad aliases for the keypad if aliases and self.unite_kp: for alias in aliases: if alias != keysym and self._get_keycode(alias): # Hack to avoid entering this control flow again.. self.unite_kp = False self._the_grab(self._get_keycode(alias), masks, id) self.unite_kp = True if not user32.RegisterHotKey(None, id, masks, keycode): keysym = self._nt_get_keysym(keycode) msg = 'The bind could be in use elsewhere: ' + keysym raise SystemRegisterError(msg) def _xlib_get_keycode(self, key): keysym = XK.string_to_keysym(key) if keysym == 0: try: keysym = XK.string_to_keysym(special_X_keysyms[key]) except KeyError: return None keycode = self.disp.keysym_to_keycode(keysym) return keycode def _xlib_get_keysym(self, keycode, i=0): keysym = self.disp.keycode_to_keysym(keycode, i) # https://lists.gnu.org/archive/html/stumpwm-devel/2006-04/msg00033.html return keybind.keysym_strings.get(keysym, [None])[0] def _xlib_the_grab(self, keycode, masks): # TODO error handlig http://tronche.com/gui/x/xlib/event-handling/protocol-errors/default-handlers.html # try: for triv_mod in self.trivial_mods: self.xRoot.grab_key(keycode, triv_mod | masks, 1, X.GrabModeAsync, X.GrabModeAsync) # except Xlib.error.BadAccess: # raise SystemRegisterError('The bind is probably already in use elsewhere on the system') def _xcb_the_grab(self, keycode, masks): try: for triv_mod in self.trivial_mods: try: self.conn.core.GrabKeyChecked( True, self.root, triv_mod | masks, keycode, xproto.GrabMode.Async, xproto.GrabMode.Async).check() except struct.error as e: msg = 'Unable to Register, Key not understood by systemhotkey' raise InvalidKeyError(msg) from e except xproto.AccessError as e: keysym = self._xcb_get_keysym(keycode) msg = 'The bind could be in use elsewhere: ' + keysym raise SystemRegisterError(msg) from e def _xcb_get_keycode(self, key): return keybind.lookup_string(key) def _xcb_get_keysym(self, keycode, i=0): keysym = keybind.get_keysym(keycode, i) return keybind.keysym_strings.get(keysym, [None])[0]
class KnoX: Geometry = namedtuple("Geometry", "x y width height") FrameExtents = namedtuple("FrameExtents", "left right top bottom") def __init__(self): #self.display = Display(os.environ.get("DISPLAY", ":0.0")) self.display = Display() print("Connected to X DISPLAY %r" % self.display.get_display_name()) self.display.set_error_handler(self.knox_error_handler) self.screen = self.display.screen() self.root = self.screen.root self.atoms = dict() self.atom_names = dict() self.keysyms = Keysyms() self.modifiers = Modifiers(self) self._supported_properties = None self._acceptable_error_sequence = 0 self._acceptable_errors = dict() self._silenced_errors = set() def fileno(self): """This function is here to make select work with this object""" return self.display.fileno() @contextmanager def silenced_error(self, error): silencer = self.silence_error(error) try: yield silencer finally: self.remove_silencer(silencer) def silence_error(self, error): k = self._acceptable_error_sequence self._acceptable_errors[k] = error self._acceptable_error_sequence += 1 self._silenced_errors = set(self._acceptable_errors.values()) return k def remove_silencer(self, key): if key in self._acceptable_errors: del self._acceptable_errors[key] self._silenced_errors = set(self._acceptable_errors.values()) def knox_error_handler(self, err, *args): if type(err) not in self._silenced_errors: print("X protocol error: %s" % err) traceback.print_stack() # def wait_for_event(self, timeout_seconds): # """ Wait up to `timeout_seconds` seconds for an event to be queued. # Return True, if a xevent is available. # Return False, if the timeout was reached. # from https://gist.github.com/fphammerle/d81ca3ff0a169f062a9f28e57b18f04d""" # rlist = select.select( # [self.display], # rlist # [], # wlist # [], # xlist # timeout_seconds, # timeout [seconds] # )[0] # return len(rlist) > 0 def next_event(self, wait=True): if (wait or self.display.pending_events()): return self.display.next_event() else: return None # def next_event(self, event_loop): # event_loop.register_reader(self.display, def atom(self, name, only_if_exists=False): if isinstance(name, int): a = name elif name not in self.atoms: a = self.display.get_atom(name, only_if_exists=only_if_exists) self.atoms[name] = a else: a = self.atoms[name] return a def atom_name(self, atom): if atom in self.atom_names: return self.atom_names[atom] name = self.display.get_atom_name(atom) if name: self.atom_names[atom] = name if name not in self.atoms: self.atoms[name] = atom return name def get_prop(self, window, name): prop_name = self.atom(name, only_if_exists=True) if not prop_name: return None if isinstance(window, int): window = self.get_window(window) p = window.get_full_property(prop_name, X.AnyPropertyType) if p: return p.value def get_text_prop(self, window, name): prop_name = self.atom(name, only_if_exists=True) if not prop_name: return None s = window.get_full_text_property(prop_name, Xatom.STRING) if not s: t = self.atom("UTF8_STRING", only_if_exists=True) if t: s = window.get_full_text_property(prop_name, t) return s def onerror(self, *args, **kwargs): print("ERROR: something bad happened about %r and %r" % (args, kwargs)) raise Exception("Error is bad...") def set_prop(self, window, name, type_name, value): if isinstance(window, int): window = self.get_window(window) if isinstance(type_name, int): prop_type_name = type_name #type_name = self.atom_name(prop_type_name) else: prop_type_name = self.atom(type_name, only_if_exists=False) prop_name = self.atom(name, only_if_exists=False) if value is None: window.delete_property(prop_name) else: window.change_property(prop_name, prop_type_name, 32, value, mode=X.PropModeReplace, onerror=self.onerror) def send_prop_change_event( self, property_name, data, target=None, window=None, ): if target is None: target = self.root if window is None: window = target ev = protocol.event.ClientMessage(window=window, client_type=self.atom(property_name), data=data) target.send_event(ev, event_mask=X.SubstructureNotifyMask | X.SubstructureRedirectMask, propagate=False, onerror=self.onerror) def current_desktop(self, desktop=None, wait=True): prop_name = "_NET_CURRENT_DESKTOP" if desktop is None: pv = self.get_prop(self.root, prop_name) if pv: return pv[0] else: v = array('I', [desktop]) #self.set_prop(self.root, prop_name, Xatom.CARDINAL, v) self.send_prop_change_event( prop_name, (32, [desktop, X.CurrentTime, 0, 0, 0])) self.flush() w = Waiter(wait) while w.wait(): print("DESKTOPCHECK", hex(desktop)) if self.current_desktop() == desktop: print("DESKTOP OK") break def get_wm_pid(self, window): pid_prop = self.get_prop(window, "_NET_WM_PID") if pid_prop: return pid_prop[0] return None def get_wm_name(self, window): if isinstance(window, int): window = self.get_window(window) # window.get_wm_name gets only STRING property and returns nothing # if it's UTF8_STRING return self.get_text_prop(window, Xatom.WM_NAME) def active_window(self, window=None, wait=3, id_only=False): prop_name = "_NET_ACTIVE_WINDOW" if window is None: pv = self.get_prop(self.root, prop_name) if pv and pv[0]: window = self.get_window(pv[0]) if window and window.get_wm_name() != 'Desktop': if id_only: return window.id else: return window else: if isinstance(window, int): window = self.get_window(window) desktop = self.get_desktop_for_window(window) self.current_desktop(desktop) #v = array('I', [ window.id, 0 ]) #self.set_prop(self.root, prop_name, Xatom.WINDOW, v) # data[0]: source indication # 1: when the request comes from an application # 2: from a pager # 0: no spec. self.send_prop_change_event(prop_name, (32, [2, X.CurrentTime, 0, 0, 0]), window=window) self.flush() #self.raise_window(window) # it won't become active until it's focused focused = self.set_focused_window(window, wait=1) w = Waiter(wait) while w.wait(): a = self.active_window() self.flush() if not focused: focused = self.set_focused_window(window, wait=1) self.flush() if a and a.id == window.id: print("Activated %r!" % window.id) return True self.send_prop_change_event(prop_name, (32, [2, X.CurrentTime, 0, 0, 0]), window=window) self.flush() print("Can't activate %d" % window.id) return False def get_focused_window(self, toplevel=True): f = self.display.get_input_focus() #f = protocol.request.GetInputFocus(display=self.display.display) if f.focus in [X.NONE, X.PointerRoot]: return None if toplevel: w = self.get_client_window(f.focus) if w is not None: return w.id return f.focus.id def raise_window(self, window): if isinstance(window, int): window = self.get_window(window) elif window is None: return window.raise_window() def focus_error(self, *args, **kwargs): print("Cannot set_input_focus: %r %r" % (args, kwargs)) def set_focused_window(self, window, wait=3): if window is None: self.display.set_input_focus(X.NONE, X.RevertToParent, X.CurrentTime, onerror=self.focus_error) return True elif not wait: self.display.set_input_focus(window, X.RevertToParent, X.CurrentTime) return True else: with self.silenced_error(error.BadMatch): if isinstance(window, int): window = self.get_window(window) self.display.set_input_focus(window, X.RevertToParent, X.CurrentTime) self.flush() w = Waiter(wait) while w.wait(): if w.timeout: if w.progressed: print("WAITING %.3f seconds more for focus on %r" % (w.remaining, window.id)) else: print( "READY TO WAIT %.3f seconds for focus on %r" % (w.remaining, window.id)) focused_win_id = self.get_focused_window() if focused_win_id == window.id: print("FOCUSED %r" % window.id) return True # many times it's needed to repeat the command, esp. when mouse is # not inside the target window self.display.set_input_focus(window, X.RevertToParent, X.CurrentTime) self.flush() #self.display.set_input_focus(window, X.RevertToParent, X.CurrentTime) #self.display.flush() return False def get_desktop_for_window(self, window): pv = self.get_prop(window, "_NET_WM_DESKTOP") if pv: return pv[0] def set_desktop_for_window(self, window, desktop): if desktop is None: return name = self.atom("_NET_WM_DESKTOP", only_if_exists=True) if name in self.supported_properties: pv = self.set_prop(window, name, Xatom.CARDINAL, array('I', [desktop])) def save_state(self): state = { "Current Desktop": self.current_desktop(), "Active Window": self.active_window(id_only=True), "Focused Window": self.get_focused_window() } return state def restore_state(self, state): a = self.supported_properties self.current_desktop(state["Current Desktop"]) self.flush() try: self.set_focused_window(state["Focused Window"]) except error.BadWindow: print("Sorry, the old focused window went away...") # self.active_window(state["Active Window"]) def keysym_to_string(self, keysym, friendly=False, very_friendly=False): if keysym not in self.keysyms.keysyms: return chr(keysym) if very_friendly: return self.keysyms.friendly_name(keysym, simplest=True) if friendly: return self.keysyms.friendly_name(keysym, simplest=False) else: return self.keysyms[keysym] def keycode_to_keysym(self, keycode, idx=None): if idx is None: syms = set() for i in range(4): keysym = self.display.keycode_to_keysym(keycode, i) if keysym: syms.add(keysym) return syms else: return self.display.keycode_to_keysym(event.detail, i) def keysym_to_keycode(self, keysym): return self.display.keysym_to_keycode(keysym) def string_to_keysym(self, s): k = self.keysyms[s] if not k: k = self.keysyms["XK_" + s] if k: return k k = XK.string_to_keysym(s) return k # allow simpler names, like AudioRaiseVolume? # if s.startswith("XF86_"): # s = "XF86" + s[5:] # return XK.string_to_keysym(s) def error_handler(self, fn, *args, **kwargs): return functools.partial(fn, *args, **kwargs) def toggle_frame(self, window, frame=None, wait=1): """Set window frame. Value should be True or False for on and off, or None for toggle.""" # flags - set bit for every iteresting value # 0 functions => integer bits # 1 decorations => integer bits # 2 input_mode => enum string or integer # 3 status => integer bits # # functions: # bit actions offered # --- --------------- # 1 all functions # 2 resize window # 4 move window # 8 minimize, to iconify # 16 maximize, to full-screen (with a frame still) # 32 close window # # decorations: # bit decorations displayed # --- --------------------- # 1 all decorations # 2 border around the window # 4 resizeh, handles to resize by dragging # 8 title bar, showing WM_NAME # 16 menu, drop-down menu of the "functions" above # 32 minimize button, to iconify # 64 maximize button, to full-screen # # input mode: # string integer # "modeless" 0 not modal (the default) # "primary_application_modal" 1 modal to its "transient for" # "system_modal" 2 modal to the whole display # "full_application_modal" 3 modal to the current client # # status: # # bit # 1 tearoff menu window name = self.atom("_MOTIF_WM_HINTS", only_if_exists=True) # If does not exist, probably not supported, though should check # root for _NET_SUPPORTED list return assert prop != 0 pv = pv = self.get_prop(window, name) fe = self.get_frame_extents(window) if pv and len(pv) == 5: hints = array(pv.typecode, pv) if frame is None: hints[2] = 0 if hints[2] else 1 elif frame: hints[2] = 1 else: hints[2] = 0 else: # reasonable default hints = array('I', [2, 0, 0, 0, 0]) self.set_prop(window, name, name, hints) w = Waiter(wait) while w.wait(): pv = self.get_prop(window, name) if pv and array(pv.typecode, pv) == hints: new_fe = self.get_frame_extents(window) # make sure frame extents changed # this seems to take a while once the hints change if new_fe != fe: break def set_opacity(self, window, value): """value is a number between 0 and 1""" v = int(((1 << 32) - 1) * value) self.set_prop(window, "_NET_WM_WINDOW_OPACITY", Xatom.CARDINAL, array('I', [v])) def get_opacity(self, window): pv = self.get_prop(window, "_NET_WM_WINDOW_OPACITY") if pv: value = int(pv[0] / ((1 << 32) - 1)) return value return 1 @property def supported_properties(self): if self._supported_properties is None: self._supported_properties = self.get_prop(self.root, "_NET_SUPPORTED") or [] return self._supported_properties def get_window(self, win_id): if isinstance(win_id, int): return self.display.create_resource_object('window', win_id) else: return win_id def get_client_window(self, window): win_id = window.id for tlw in self.toplevel_windows(): for (_, parent, _) in self.window_tree( tlw, filter=lambda w, parent, level: w.id == win_id): return tlw return None def toplevel_windows(self, id_only=False): name = self.atom("_NET_CLIENT_LIST", only_if_exists=True) if name in self.supported_properties: lst = self.get_prop(self.root, name) if id_only: return lst else: return list(map(lambda win_id: self.get_window(win_id), lst)) else: print("BELGENGOC") if id_only: return list( map(lambda w: w.id, self.root.query_tree().children)) else: return list(self.root.query_tree().children) def window_tree(self, parent=None, level=1, filter=None): if parent is None: parent = self.root if filter is None or filter(parent, None, 0): yield (parent, None, 0) for w in parent.query_tree().children: if filter is None or filter(w, parent, level): yield (w, parent, level) yield from self.window_tree(parent=w, level=level + 1, filter=filter) def close_window(self, window): self.send_prop_change_event("_NET_CLOSE_WINDOW", (32, [0, 0, 0, 0, 0]), window=self.get_window(window)) # https://specifications.freedesktop.org/wm-spec/wm-spec-1.3.html # window = the respective client window # message_type = _NET_WM_STATE # format = 32 # data.l[0] = the action, as listed below # data.l[1] = first property to alter # data.l[2] = second property to alter # data.l[3] = source indication # other data.l[] elements = 0 # This message allows two prop # _NET_WM_STATE_REMOVE = 0 # remove/unset property _NET_WM_STATE_ADD = 1 #add/set property _NET_WM_STATE_TOGGLE = 2 # toggle property def set_wm_states(self, window, names, action=None): if action is None: action = self._NET_WM_STATE_TOGGLE elif action is True: action = self._NET_WM_STATE_ADD elif action is False: action = self._NET_WM_STATE_REMOVE window = self.get_window(window) values = list() for name in names: value = self.atom("_NET_WM_STATE_%s" % name.upper()) values.append(value) data = [action, *values] while len(data) < 5: data.append(0) self.send_prop_change_event("_NET_WM_STATE", (32, data), window=self.get_window(window)) def set_wm_state(self, window, name, action=None): if action is None: action = self._NET_WM_STATE_TOGGLE elif action is True: action = self._NET_WM_STATE_ADD elif action is False: action = self._NET_WM_STATE_REMOVE window = self.get_window(window) value = self.atom("_NET_WM_STATE_%s" % name.upper()) self.send_prop_change_event("_NET_WM_STATE", (32, [action, value, 0, 0, 0]), window=self.get_window(window)) def below_window(self, window, action=None): self.set_wm_state(window, name="below", action=action) def fullscreen_window(self, window, action=None): self.set_wm_state(window, name="fullscreen", action=action) def above_window(self, window, action=None): self.set_wm_state(window, name="above", action=action) def sticky_window(self, window, action=None): self.set_wm_state(window, name="sticky", action=action) def skip_pager(self, window, action=None): self.set_wm_state(window, name="skip_pager", action=action) def skip_taskbar(self, window, action=None): self.set_wm_state(window, name="skip_taskbar", action=action) def maximize_window(self, window, horizontal=True, vertical=True, action=None): if horizontal: self.set_wm_state(window, name="maximized_horz", action=action) if vertical: self.set_wm_state(window, name="maximized_vert", action=action) def minimize_window(self, window): if isinstance(window, int): window = self.get_window(window) self.send_prop_change_event("WM_CHANGE_STATE", (32, [Xutil.IconicState, 0, 0, 0, 0]), window=self.get_window(window)) def get_attributes(self, window): if isinstance(window, int): window = self.get_window(window) return window.get_attributes() def get_window_type(self, window): e = self.get_prop(window, "_NET_WM_WINDOW_TYPE") if e is None: return None type_details = set() prefix = "_NET_WM_WINDOW_TYPE_" for t in e: if not t: continue s = self.atom_name(t) if s.startswith(prefix): s = s[len(prefix):] type_details.add(s) return type_details def get_frame_extents(self, window): # x, y, width, height if isinstance(window, int): window = self.get_window(window) e = self.get_prop(window, "_NET_FRAME_EXTENTS") if e: return self.FrameExtents(*e) else: return self.FrameExtents(0, 0, 0, 0) def get_geometry(self, window): # x, y, width, height if isinstance(window, int): window = self.get_window(window) return window.get_geometry() def set_geometry(self, window, **data): # x, y, width, height if isinstance(window, int): window = self.get_window(window) if any(map(lambda v: v < 0, data.values())): gw = self.get_geometry(window) f = self.get_frame_extents(window) wa = self.usable_workarea() if 'x' in data and data['x'] < 0: data['x'] = wa.width - gw.width - (f.left + f.right) + data['x'] + 1 else: data['x'] += wa.x if 'y' in data and data['y'] < 0: data['y'] = wa.height - gw.height - (f.top + f.bottom) + data['y'] + 1 else: data['y'] += wa.y window.configure(**data) def usable_workarea(self): a = self.get_prop(self.root, "_NET_WORKAREA") if a: p = self.current_desktop() * 4 #return (x, y, width, height) return self.Geometry(*a[p:p + 4]) else: r = self.get_geometry(self.root) return self.Geometry(0, 0, r.width, r.height) def send_key(self, window, keysym, modifiers): if isinstance(window, int): window = self.get_window(window) keycode = self.display.keysym_to_keycode(keysym) event = protocol.event.KeyPress(time=X.CurrentTime, root=self.root, window=window, child=X.NONE, same_screen=True, root_x=0, root_y=0, event_x=0, event_y=0, state=modifiers.bitmap, detail=keycode) window.send_event(event, propagate=False) event = protocol.event.KeyRelease( time=X.CurrentTime, root=self.root, window=window, child=X.NONE, same_screen=True, # same screen as the root window root_x=0, root_y=0, event_x=0, event_y=0, state=modifiers.bitmap, detail=keycode) window.send_event(event, propagate=False) def show_desktop(self, action=None): prop_name = self.atom("_NET_SHOWING_DESKTOP") if action is True: self.send_prop_change_event(prop_name, (32, [1, X.CurrentTime, 0, 0, 0])) elif action is False: self.send_prop_change_event(prop_name, (32, [0, X.CurrentTime, 0, 0, 0])) else: pv = self.get_prop(self.root, prop_name) new_val = 0 if pv and pv[0] else 1 self.send_prop_change_event( prop_name, (32, [new_val, X.CurrentTime, 0, 0, 0])) def flush(self): # send all pending events self.display.flush() def sync(self): # flush and make sure everything is handled and processed or rejected by the server self.display.sync() @property def display_count(self): res = randr.get_screen_resources(self.root) n = 0 for i in res.outputs: o = randr.get_output_info(self.root, i, config_timestamp=0) if o.modes: # has modes, empty if there's no monitor connected here n += 1 return n
class Manager(): def __init__(self, inkscape_id): self.id = inkscape_id self.disp = Display() self.screen = self.disp.screen() self.root = self.screen.root self.inkscape = self.disp.create_resource_object('window', inkscape_id) self.mode = normal_mode def event(self, name, detail, state): return name(time=X.CurrentTime, root=self.root, window=self.inkscape, same_screen=0, child=Xlib.X.NONE, root_x=0, root_y=0, event_x=0, event_y=0, state=state, detail=detail) def string_to_keycode(self, key): keysym = XK.string_to_keysym(key) keycode = self.disp.keysym_to_keycode(keysym) return keycode def press(self, key, mask=X.NONE): keycode = self.string_to_keycode(key) self.inkscape.send_event(self.event(event.KeyPress, keycode, mask), propagate=True) self.inkscape.send_event(self.event(event.KeyRelease, keycode, mask), propagate=True) self.disp.flush() self.disp.sync() def grab(self): self.inkscape.grab_key(X.AnyKey, X.AnyModifier, True, X.GrabModeAsync, X.GrabModeAsync) # Ungrab window manager shortcuts (Super + ...) self.inkscape.ungrab_key(self.string_to_keycode('Super'), X.AnyModifier, True) self.inkscape.ungrab_key(self.string_to_keycode('Alt_L'), X.AnyModifier, True) self.inkscape.ungrab_key(self.string_to_keycode('Shift_R'), X.AnyModifier, True) self.inkscape.change_attributes(event_mask=X.KeyReleaseMask | X.KeyPressMask | X.StructureNotifyMask) def ungrab(self): self.inkscape.ungrab_key(X.AnyKey, X.AnyModifier, True) def listen(self): self.grab() while True: evt = self.disp.next_event() if evt.type in [X.KeyPress, X.KeyRelease]: keycode = evt.detail keysym = self.disp.keycode_to_keysym(keycode, 0) char = XK.keysym_to_string(keysym) self.disp.allow_events(X.ReplayKeyboard, X.CurrentTime) self.mode(self, evt, char) if evt.type == X.DestroyNotify: if evt.window.id == self.id: self.ungrab() return
class SystemHotkey(MixIn): ''' Cross platform System Wide Hotkeys Modifer oder doesn't matter, e.g binding to control shift k is the same as shift control k, limitation of the keyboard and operating systems not this library ''' hk_ref = {} def __init__(self, consumer='callback', check_queue_interval=0.01, use_xlib=False, conn=None, verbose=False): ''' if the consumer param = 'callback', -> All hotkeys will require a callback function - Experimental! - Otherwise set consumer to a function to hanlde the event. parameters sent will be - event, hotkey, callbacks event is the xwindow/microsoft keyboard eventm hotkey is a tuple, callback is any info that you registerd with the hotkey check_queue_interval is in seconds and sets the sleep time on checking the queue for hotkey presses set use_xlib to true to use the xlib python bindings (GPL) instead of the xcb ones (BSD) You can pass an exisiting X display or connection using the conn keyword, otherwise one will be created for you. ''' # Changes the class methods to point to differenct functions # Depening on the operating system and library used # Consumer can be set to a function also, which will be sent the event # as well as the key and mask already broken out # Last option for consumer is False, then you have to listen to the queue yourself # data_queue self.verbose = verbose self.use_xlib = use_xlib self.consumer = consumer self.check_queue_interval = check_queue_interval def mark_event_type(event): # event gets an event_type attribute so the user has a portiabble way # actually on windows as far as i know you dont have the option of binding on keypress or release so... # anyway ahve to check it but for now u dont! if os.name == 'posix': if self.use_xlib: if event.type == X.KeyPress: event.event_type = 'keypress' elif event.type == X.KeyRelease: event.event_type = 'keyrelease' else: if isinstance(event, xproto.KeyPressEvent): event.event_type = 'keypress' if isinstance(event, xproto.KeyReleaseEvent): event.event_type = 'keyrelease' else: event.event_type = 'keypress' return event self.data_queue = queue.Queue() if os.name == 'nt': self.hk_action_queue = queue.Queue() self.modders = win_modders self._the_grab = self._nt_the_grab self.get_keycode = self._nt_get_keycode self._get_keysym = self._nt_get_keysym thread.start_new_thread(self._nt_wait,(),) elif use_xlib: # Use the python-xlib library bindings, GPL License self.modders = xlib_modifiers self.trivial_mods = xlib_trivial_mods self._the_grab = self._xlib_the_grab self.get_keycode = self._xlib_get_keycode self._get_keysym = self._xlib_get_keysym if not conn: self.disp = Display() else: self.disp = conn self.xRoot = self.disp.screen().root self.xRoot.change_attributes(event_mask=X.KeyPressMask) thread.start_new_thread(self._xlib_wait,(),) else: # Using xcb and the xcffib python bindings Apache 2 http://stackoverflow.com/questions/40100/apache-license-vs-bsd-vs-mit self.modders = xcb_modifiers self.trivial_mods = xcb_trivial_mods self._the_grab = self._xcb_the_grab self.get_keycode = self._xcb_get_keycode self._get_keysym = self._xcb_get_keysym if not conn: self.conn = xcffib.connect() else: self.conn = conn self.root = self.conn.get_setup().roots[0].root thread.start_new_thread(self._xcb_wait,(),) if consumer == 'callback': if self.verbose: print('In Callback') def thread_me(): while 1: time.sleep(self.check_queue_interval) try: event = self.data_queue.get(block=False) except queue.Empty: pass else: event = mark_event_type(event) hotkey = self.parse_event(event) #~ for cb in self.get_callback(hotkey, event.event_type): #when i was using the keypress / keyrelease shit for cb in self.get_callback(hotkey): if event.event_type == 'keypress': if self.verbose: print('calling ', repr(cb)) cb(event) # TBD either throw these up in a thread, or pass in a queue to be put onto thread.start_new_thread(thread_me,(),) elif callable(consumer): def thread_me(): while 1: time.sleep(self.check_queue_interval) try: event = self.data_queue.get(block=False) except queue.Empty: pass else: hotkey = self.parse_event(mark_event_type(event)) if event.event_type == 'keypress': args = [cb for cb in self.get_callback(hotkey)] #~ callbacks = [cb for cb in self.get_callback(hotkey, event.event_type)] consumer(event, hotkey, args) thread.start_new_thread(thread_me,(),) else: print('You need to handle grabbing events yourself!') def _xlib_wait(self): # Pushes Event onto queue while 1: event = self.xRoot.display.next_event() self.data_queue.put(event) def _xcb_wait(self): # Pushes Event onto queue while 1: event = self.conn.wait_for_event() self.data_queue.put(event) def _nt_wait(self): # Pushes Event onto queue # I don't understand the windows msg system # I can only get hotkeys to work if they are registeed in the # Thread that is listening for them. # So any changes to the hotkeys have to be signaled to be done # By the thread. (including unregistering) # A new queue is checked and runs functions, either adding # or removing new hotkeys, then the windows msg queue is checked msg = ctypes.wintypes.MSG () while 1: try: remove_or_add = self.hk_action_queue.get(block=False) except queue.Empty: pass else: remove_or_add() # Checking the windows message Queue if user32.PeekMessageA(byref(msg), 0, 0, 0, PM_REMOVE): if msg.message == win32con.WM_HOTKEY: self.data_queue.put(msg) else: print('some other message') time.sleep(self.check_queue_interval) def _nt_get_keycode(self, key, disp=None): return vk_codes[key] def _nt_get_keysym(self, keycode): for key, value in vk_codes.items(): if value == keycode: return key def _nt_the_grab(self, keycode, masks, id, root=None): if not user32.RegisterHotKey(None, id, masks, keycode): raise RegisterError('The bind is probably already in use elsewhere on the system') #TBD RAISE RROR ON LINUX SYSTEMS def _xlib_get_keycode(self, key) : keysym = XK.string_to_keysym(key) if keysym == 0: keysym = XK.string_to_keysym(special_X_keysyms[key]) keycode = self.disp.keysym_to_keycode(keysym) return keycode def _xlib_get_keysym(self, keycode, i=0): keysym = self.disp.keycode_to_keysym(keycode, i) return keybind.keysym_strings.get(keysym, [None])[0] #https://lists.gnu.org/archive/html/stumpwm-devel/2006-04/msg00033.html def _xlib_the_grab(self, keycode, masks): #TBD error handlig http://tronche.com/gui/x/xlib/event-handling/protocol-errors/default-handlers.html #~ try: self.xRoot.grab_key(keycode, masks, 1, X.GrabModeAsync, X.GrabModeAsync) #~ except Xlib.error.BadAccess: #~ raise RegisterError('The bind is probably already in use elsewhere on the system') def _xcb_the_grab(self, keycode, masks): try: for triv_mod in self.trivial_mods: self.conn.core.GrabKeyChecked(True, self.root, triv_mod | masks, keycode, xproto.GrabMode.Async, xproto.GrabMode.Async).check() except xproto.AccessError: raise RegisterError('The bind is probably already in use elsewhere on the system') def _xcb_get_keycode(self, key): return keybind.lookup_string(key) def _xcb_get_keysym(self, keycode, i=0): keysym = keybind.get_keysym(keycode, i) return keybind.keysym_strings.get(keysym, [None])[0]
class KeyListener: def __init__(self): self.disp = None self.root = None self.record = None def setup(self): # get current display self.disp = Display() self.root = self.disp.screen().root # Monitor keypress and button press ctx = self.disp.record_create_context( 0, [record.AllClients], [{ 'core_requests': (0, 0), 'core_replies': (0, 0), 'ext_requests': (0, 0, 0, 0), 'ext_replies': (0, 0, 0, 0), 'delivered_events': (0, 0), 'device_events': (X.KeyReleaseMask, X.ButtonReleaseMask), 'errors': (0, 0), 'client_started': False, 'client_died': False, }]) self.disp.record_enable_context(ctx, self.handler) self.disp.record_free_context(ctx) def handler(self, reply): """ Self function is called when a xlib event is fired """ data = reply.data while len(data): event, data = rq.EventField(None).parse_binary_value(data, self.disp.display, None, None) if event.type == X.KeyPress: if event.detail == 36: self.record("enter") elif event.detail == 22: self.record("backspace") elif event.detail == 37: self.record("control") elif event.detail == 64: self.record("alt") elif event.detail == 108: self.record("alt-gr") key = XK.keysym_to_string( self.disp.keycode_to_keysym(event.detail, event.state)) if key: self.record(key) def key_listen_loop(self): while True: self.root.display.next_event() def set_recorder(self, r): self.record = r
class PyKeyboardEvent(PyKeyboardEventMeta): """ The PyKeyboardEvent implementation for X11 systems (mostly linux). This allows one to listen for keyboard input. """ def __init__(self, capture=False, display=None): self.display = Display(display) self.display2 = Display(display) self.ctx = self.display2.record_create_context( 0, [record.AllClients ], [{ 'core_requests': (0, 0), 'core_replies': (0, 0), 'ext_requests': (0, 0, 0, 0), 'ext_replies': (0, 0, 0, 0), 'delivered_events': (0, 0), 'device_events': (X.KeyPress, X.KeyRelease), 'errors': (0, 0), 'client_started': False, 'client_died': False, }]) self.lock_meaning = None #Get these dictionaries for converting keysyms and strings self.keysym_to_string, self.string_to_keysym = self.get_translation_dicts( ) #Identify and register special groups of keys self.modifier_keycodes = {} self.all_mod_keycodes = [] self.keypad_keycodes = [] #self.configure_keys() #Direct access to the display's keycode-to-keysym array logger.debug('Keycode to Keysym map') for i in range(len(self.display._keymap_codes)): logger.debug('{0}: {1}'.format(i, self.display._keymap_codes[i])) PyKeyboardEventMeta.__init__(self, capture) def run(self): """Begin listening for keyboard input events.""" self.state = True if self.capture: self.display2.screen().root.grab_keyboard( X.KeyPressMask | X.KeyReleaseMask, X.GrabModeAsync, X.GrabModeAsync, X.CurrentTime) self.display2.record_enable_context(self.ctx, self.handler) self.display2.record_free_context(self.ctx) def stop(self): """Stop listening for keyboard input events.""" self.state = False with display_manager(self.display) as d: d.record_disable_context(self.ctx) d.ungrab_keyboard(X.CurrentTime) with display_manager(self.display2): d.record_disable_context(self.ctx) d.ungrab_keyboard(X.CurrentTime) def handler(self, reply): """Upper level handler of keyboard events.""" data = reply.data while len(data): event, data = rq.EventField(None).parse_binary_value( data, self.display.display, None, None) if self.escape(event): # Quit if this returns True self.stop() else: self._tap(event) def _tap(self, event): keycode = event.detail press_bool = (event.type == X.KeyPress) #Detect modifier states from event.state for mod, bit in self.modifier_bits.items(): self.modifiers[mod] = event.state & bit if keycode in self.all_mod_keycodes: keysym = self.display.keycode_to_keysym(keycode, 0) character = self.keysym_to_string[keysym] else: character = self.lookup_char_from_keycode(keycode) #All key events get passed to self.tap() self.tap(keycode, character, press=press_bool) def lookup_char_from_keycode(self, keycode): """ This will conduct a lookup of the character or string associated with a given keycode. """ #TODO: Logic should be strictly adapted from X11's src/KeyBind.c #Right now the logic is based off of #http://tronche.com/gui/x/xlib/input/keyboard-encoding.html #Which I suspect is not the whole story and may likely cause bugs keysym_index = 0 #TODO: Display's Keysyms per keycode count? Do I need this? #If the Num_Lock is on, and the keycode corresponds to the keypad if self.modifiers['Num_Lock'] and keycode in self.keypad_keycodes: if self.modifiers['Shift'] or self.modifiers['Shift_Lock']: keysym_index = 0 else: keysym_index = 1 elif not self.modifiers['Shift'] and self.modifiers['Caps_Lock']: #Use the first keysym if uppercase or uncased #Use the uppercase keysym if the first is lowercase (second) keysym_index = 0 keysym = self.display.keycode_to_keysym(keycode, keysym_index) #TODO: Support Unicode, Greek, and special latin characters if keysym & 0x7f == keysym and chr( keysym) in 'abcdefghijklmnopqrstuvwxyz': keysym_index = 1 elif self.modifiers['Shift'] and self.modifiers['Caps_Lock']: keysym_index = 1 keysym = self.display.keycode_to_keysym(keycode, keysym_index) #TODO: Support Unicode, Greek, and special latin characters if keysym & 0x7f == keysym and chr( keysym) in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ': keysym_index = 0 elif self.modifiers['Shift'] or self.modifiers['Shift_Lock']: keysym_index = 1 if self.modifiers['Mode_switch']: keysym_index += 2 #Finally! Get the keysym keysym = self.display.keycode_to_keysym(keycode, keysym_index) #If the character is ascii printable, return that character if keysym & 0x7f == keysym and self.ascii_printable(keysym): return chr(keysym) #If the character was not printable, look for its name try: char = self.keysym_to_string[keysym] except KeyError: logger.info('Unable to determine character.') logger.info('Keycode: {0} KeySym {1}'.format(keycode, keysym)) return None else: return char def escape(self, event): if event.detail == self.lookup_character_keycode('Escape'): return True return False def configure_keys(self): """ This function locates the keycodes corresponding to special groups of keys and creates data structures of them for use by the PyKeyboardEvent instance; including the keypad keys and the modifiers. The keycodes pertaining to the keyboard modifiers are assigned by the modifier name in a dictionary. This dictionary can be accessed in the following manner: self.modifier_keycodes['Shift'] # All keycodes for Shift Masking It also assigns certain named modifiers (Alt, Num_Lock, Super), which may be dynamically assigned to Mod1 - Mod5 on different platforms. This should generally allow the user to do the following lookups on any system: self.modifier_keycodes['Alt'] # All keycodes for Alt Masking self.modifiers['Alt'] # State of Alt mask, non-zero if "ON" """ modifier_mapping = self.display.get_modifier_mapping() all_mod_keycodes = [] mod_keycodes = {} mod_index = [('Shift', X.ShiftMapIndex), ('Lock', X.LockMapIndex), ('Control', X.ControlMapIndex), ('Mod1', X.Mod1MapIndex), ('Mod2', X.Mod2MapIndex), ('Mod3', X.Mod3MapIndex), ('Mod4', X.Mod4MapIndex), ('Mod5', X.Mod5MapIndex)] #This gets the list of all keycodes per Modifier, assigns to name for name, index in mod_index: codes = [v for v in list(modifier_mapping[index]) if v] mod_keycodes[name] = codes all_mod_keycodes += codes def lookup_keycode(string): keysym = self.string_to_keysym[string] return self.display.keysym_to_keycode(keysym) #Dynamically assign Lock to Caps_Lock, Shift_Lock, Alt, Num_Lock, Super, #and mode switch. Set in both mod_keycodes and self.modifier_bits #Try to assign Lock to Caps_Lock or Shift_Lock shift_lock_keycode = lookup_keycode('Shift_Lock') caps_lock_keycode = lookup_keycode('Caps_Lock') if shift_lock_keycode in mod_keycodes['Lock']: mod_keycodes['Shift_Lock'] = [shift_lock_keycode] self.modifier_bits['Shift_Lock'] = self.modifier_bits['Lock'] self.lock_meaning = 'Shift_Lock' elif caps_lock_keycode in mod_keycodes['Lock']: mod_keycodes['Caps_Lock'] = [caps_lock_keycode] self.modifier_bits['Caps_Lock'] = self.modifier_bits['Lock'] self.lock_meaning = 'Caps_Lock' else: self.lock_meaning = None logger.debug('Lock is bound to {0}'.format(self.lock_meaning)) #Need to find out which Mod# to use for Alt, Num_Lock, Super, and #Mode_switch num_lock_keycodes = [lookup_keycode('Num_Lock')] alt_keycodes = [lookup_keycode(i) for i in ['Alt_L', 'Alt_R']] super_keycodes = [lookup_keycode(i) for i in ['Super_L', 'Super_R']] mode_switch_keycodes = [lookup_keycode('Mode_switch')] #Detect Mod number for Alt, Num_Lock, and Super for name, keycodes in list(mod_keycodes.items()): for alt_key in alt_keycodes: if alt_key in keycodes: mod_keycodes['Alt'] = keycodes self.modifier_bits['Alt'] = self.modifier_bits[name] for num_lock_key in num_lock_keycodes: if num_lock_key in keycodes: mod_keycodes['Num_Lock'] = keycodes self.modifier_bits['Num_Lock'] = self.modifier_bits[name] for super_key in super_keycodes: if super_key in keycodes: mod_keycodes['Super'] = keycodes self.modifier_bits['Super'] = self.modifier_bits[name] for mode_switch_key in mode_switch_keycodes: if mode_switch_key in keycodes: mod_keycodes['Mode_switch'] = keycodes self.modifier_bits['Mode_switch'] = self.modifier_bits[ name] #Assign the mod_keycodes to a local variable for access self.modifier_keycodes = mod_keycodes self.all_mod_keycodes = all_mod_keycodes #TODO: Determine if this might fail, perhaps iterate through the mapping #and identify all keycodes with registered keypad keysyms? #Acquire the full list of keypad keycodes self.keypad_keycodes = [] keypad = [ 'Space', 'Tab', 'Enter', 'F1', 'F2', 'F3', 'F4', 'Home', 'Left', 'Up', 'Right', 'Down', 'Prior', 'Page_Up', 'Next', 'Page_Down', 'End', 'Begin', 'Insert', 'Delete', 'Equal', 'Multiply', 'Add', 'Separator', 'Subtract', 'Decimal', 'Divide', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' ] for keyname in keypad: keypad_keycode = self.lookup_character_keycode('KP_' + keyname) self.keypad_keycodes.append(keypad_keycode) def lookup_character_keycode(self, character): """ Looks up the keysym for the character then returns the keycode mapping for that keysym. """ keysym = self.string_to_keysym.get(character, 0) if keysym == 0: keysym = self.string_to_keysym.get(KEYSYMS[character], 0) return self.display.keysym_to_keycode(keysym) def get_translation_dicts(self): """ Returns dictionaries for the translation of keysyms to strings and from strings to keysyms. """ keysym_to_string_dict = {} string_to_keysym_dict = {} #XK loads latin1 and miscellany on its own; load latin2-4 and greek Xlib.XK.load_keysym_group('latin2') Xlib.XK.load_keysym_group('latin3') Xlib.XK.load_keysym_group('latin4') Xlib.XK.load_keysym_group('greek') #Make a standard dict and the inverted dict for string, keysym in Xlib.XK.__dict__.items(): if string.startswith('XK_'): string_to_keysym_dict[string[3:]] = keysym keysym_to_string_dict[keysym] = string[3:] return keysym_to_string_dict, string_to_keysym_dict def ascii_printable(self, keysym): """ If the keysym corresponds to a non-printable ascii character this will return False. If it is printable, then True will be returned. ascii 11 (vertical tab) and ascii 12 are printable, chr(11) and chr(12) will return '\x0b' and '\x0c' respectively. """ if 0 <= keysym < 9: return False elif 13 < keysym < 32: return False elif keysym > 126: return False else: return True
def main(argv): display = Display() # Xinput extension_info = display.query_extension("XInputExtension") xinput_major = extension_info.major_opcode typing_tracker = TypingTracker(None) aliasing_map = AliasMap(open("halfquerty-v2.bin", "rb").read()) version_info = display.xinput_query_version() print("Found XInput version %u.%u" % ( version_info.major_version, version_info.minor_version, )) xscreen = display.screen() xscreen.root.xinput_select_events([ ( xinput.AllDevices, xinput.KeyPressMask | xinput.KeyReleaseMask | xinput.ButtonPressMask | xinput.RawButtonPressMask, ), ]) # Window win = tk.Tk() win.geometry("+100+100") win.wm_attributes("-topmost", 1) win.attributes("-alpha", 0.5) win.config(bg=BG_NOHL) win.overrideredirect(True) container = tk.Frame(win, cnf={"bg": BG_NOHL}) container.pack() before_txt_var = tk.StringVar() before_txt_var.set("") before_label = tk.Label( container, textvariable=before_txt_var, cnf={ "font": "monospace 12", "fg": FG_NOHL, "bg": BG_NOHL, "height": 1 }, ) sel_txt_var = tk.StringVar() sel_txt_var.set("") sel_label = tk.Label( container, textvariable=sel_txt_var, cnf={ "font": "monospace 12", "fg": FG_SEL, "bg": BG_SEL, "height": 1 }, ) after_txt_var = tk.StringVar() after_txt_var.set("") after_label = tk.Label( container, textvariable=after_txt_var, cnf={ "font": "monospace 12", "fg": FG_NOHL, "bg": BG_NOHL, "height": 1 }, ) suggestion_container = tk.Frame(win, cnf={"bg": BG_NOHL}) suggestion_labels = [] suggestion_idx = 0 before_label.pack(side=tk.LEFT) sel_label.pack(side=tk.LEFT) after_label.pack(side=tk.LEFT) suggestion_container.pack(side=tk.BOTTOM) vocab = Vocab.loads(open("vocab.json", "r").read()) ui = UInput() suggester = Suggester(vocab, edit_sequence_config=EditSequencesConfig()) try: while True: i = 0 while display.pending_events() > 0 and i < 20: i += 1 event = display.next_event() # print(event) if not event or event.type != 35: continue if event.evtype == xinput.KeyPress: keycode = event.data.detail keysym = display.keycode_to_keysym(keycode, 0) if keycode == STEP_NEXT: suggestion_idx = min(suggestion_idx + 1, len(suggestion_labels) - 1) elif keycode == STEP_PREV: suggestion_idx = max(suggestion_idx - 1, 0) elif keycode == SELECT_ACTIVE: # correct the word to the suggestion before_text, after_text = typing_tracker.get_text() last_word = get_last_word(before_text) suggestions = suggester.get_prefix_suggestions( lastword) if suggestion_idx < len(suggestions): correct_typing_buffer(display, ui, last_word, suggestions[suggestion_idx]) else: typing_tracker.handle_keypress_sym(keysym) suggestion_idx = 0 elif event.evtype == xinput.KeyRelease: keycode = event.data.detail keysym = display.keycode_to_keysym(keycode, 0) typing_tracker.handle_keyrelease_sym(keysym) elif (event.evtype == xinput.ButtonPress or event.evtype == xinput.RawButtonPress): typing_tracker.reset_buff() if i >= 1: before_text, after_text = typing_tracker.get_text() before_txt_var.set(before_text) h = 1 + before_text.count("\r") before_label.configure({"height": h}) after_txt_var.set(after_text) lastword = get_last_word(before_text) suggestions = suggester.get_prefix_suggestions(lastword) while len(suggestions) > len(suggestion_labels): l = SuggestionLabel(suggestion_container, ) suggestion_labels.append(l) while len(suggestions) < len(suggestion_labels): l = suggestion_labels.pop() l.dispose() for i, (sugg, label) in enumerate(zip(suggestions, suggestion_labels)): label.update(sugg, i == suggestion_idx) win.update_idletasks() win.update() except KeyboardInterrupt as e: return 0 except Exception as e: print(e) print(full_stack()) finally: win.destroy() return 1