diff --git a/pynput_service.py b/pynput_service.py index 14a8f1793..3aa4f6175 100644 --- a/pynput_service.py +++ b/pynput_service.py @@ -2,14 +2,114 @@ from pynput.keyboard import Key, Controller from pynput.keyboard._xorg import KeyCode from pynput._util.xorg import display_manager import Xlib +from pynput._util.xorg import * +import Xlib import os import sys import socket KeyCode._from_symbol("\0") # test +DEAD_KEYS = { + '`': 65104, + '´': 65105, + '^': 65106, + '~': 65107, + '¯': 65108, + '˘': 65109, + '˙': 65110, + '¨': 65111, + '˚': 65112, + '˝': 65113, + 'ˇ': 65114, + '¸': 65115, + '˛': 65116, + '℩': 65117, # ? + '゛': 65118, # ? + '゚ ': 65119, + 'ٜ': 65120, + '↪': 65121, + ' ̛': 65122, +} + + +def my_keyboard_mapping(display): + """Generates a mapping from *keysyms* to *key codes* and required + modifier shift states. + + :param Xlib.display.Display display: The display for which to retrieve the + keyboard mapping. + + :return: the keyboard mapping + """ + mapping = {} + + shift_mask = 1 << 0 + group_mask = alt_gr_mask(display) + + # Iterate over all keysym lists in the keyboard mapping + min_keycode = display.display.info.min_keycode + keycode_count = display.display.info.max_keycode - min_keycode + 1 + for index, keysyms in enumerate(display.get_keyboard_mapping( + min_keycode, keycode_count)): + key_code = index + min_keycode + + # Normalise the keysym list to yield a tuple containing the two groups + normalized = keysym_normalize(keysyms) + if not normalized: + continue + + # Iterate over the groups to extract the shift and modifier state + for groups, group in zip(normalized, (False, True)): + for keysym, shift in zip(groups, (False, True)): + + if not keysym: + continue + shift_state = 0 \ + | (shift_mask if shift else 0) \ + | (group_mask if group else 0) + + # !!!: Save all keycode combinations of keysym + if keysym in mapping: + mapping[keysym].append((key_code, shift_state)) + else: + mapping[keysym] = [(key_code, shift_state)] + return mapping + class MyController(Controller): + def _update_keyboard_mapping(self): + """Updates the keyboard mapping. + """ + with display_manager(self._display) as dm: + self._keyboard_mapping = my_keyboard_mapping(dm) + + def send_event(self, event, keycode, shift_state): + with display_manager(self._display) as dm, self.modifiers as modifiers: + # Under certain cimcumstances, such as when running under Xephyr, + # the value returned by dm.get_input_focus is an int + window = dm.get_input_focus().focus + send_event = getattr( + window, + 'send_event', + lambda event: dm.send_event(window, event)) + send_event(event( + detail=keycode, + state=shift_state | self._shift_mask(modifiers), + time=0, + root=dm.screen().root, + window=window, + same_screen=0, + child=Xlib.X.NONE, + root_x=0, root_y=0, event_x=0, event_y=0)) + + def fake_input(self, keycode, is_press): + with display_manager(self._display) as dm: + Xlib.ext.xtest.fake_input( + dm, + Xlib.X.KeyPress if is_press else Xlib.X.KeyRelease, + keycode) + def _handle(self, key, is_press): """Resolves a key identifier and sends a keyboard event. :param event: The *X* keyboard event. @@ -19,45 +119,53 @@ class MyController(Controller): else Xlib.display.event.KeyRelease keysym = self._keysym(key) + if key.vk is not None: + keycode = self._display.keysym_to_keycode(key.vk) + self.fake_input(keycode, is_press) + # Otherwise use XSendEvent; we need to use this in the general case to + # work around problems with keyboard layouts + self._emit('_on_fake_event', key, is_press) + return + # Make sure to verify that the key was resolved if keysym is None: raise self.InvalidKeyException(key) + # There may be multiple keycodes for keysym in keyboard_mapping + keycode_flag = len(self.keyboard_mapping[keysym]) == 1 + if keycode_flag: + keycode, shift_state = self.keyboard_mapping[keysym][0] + else: + keycode, shift_state = self._display.keysym_to_keycode(keysym), 0 + + # The keycode of the dead key is inconsistent + if keycode != self._display.keysym_to_keycode(keysym): + deakkey_chr = str(key).replace("'", '') + keysym = DEAD_KEYS[deakkey_chr] + keycode, shift_state = self.keyboard_mapping[keysym][0] + # If the key has a virtual key code, use that immediately with # fake_input; fake input,being an X server extension, has access to # more internal state that we do - if key.vk is not None: - with display_manager(self._display) as dm: - Xlib.ext.xtest.fake_input( - dm, - Xlib.X.KeyPress if is_press else Xlib.X.KeyRelease, - dm.keysym_to_keycode(key.vk)) - # Otherwise use XSendEvent; we need to use this in the general case to - # work around problems with keyboard layouts - else: - try: - keycode = self._display.keysym_to_keycode(keysym) - with self.modifiers as modifiers: - alt_gr = Key.alt_gr in modifiers - if alt_gr: - keycode, shift_state = self.keyboard_mapping[keysym] - self._send_key(event, keycode, shift_state) - else: - with display_manager(self._display) as dm: - Xlib.ext.xtest.fake_input( - dm, - Xlib.X.KeyPress if is_press else Xlib.X.KeyRelease, - keycode) - except KeyError: - with self._borrow_lock: - keycode, index, count = self._borrows[keysym] - self._send_key( - event, - keycode, - index_to_shift(self._display, index)) - count += 1 if is_press else -1 - self._borrows[keysym] = (keycode, index, count) + try: + with self.modifiers as modifiers: + alt_gr = Key.alt_gr in modifiers + + if alt_gr or keycode_flag: + self.send_event( + event, keycode, shift_state) + else: + self.fake_input(keycode, is_press) + except KeyError: + with self._borrow_lock: + keycode, index, count = self._borrows[keysym] + self._send_key( + event, + keycode, + index_to_shift(self._display, index)) + count += 1 if is_press else -1 + self._borrows[keysym] = (keycode, index, count) # Notify any running listeners self._emit('_on_fake_event', key, is_press)