Esempio n. 1
0
def x11_setup():
    global _x11, xdisplay, modifier_keycodes, NET_ACTIVE_WINDOW, NET_WM_PID, WM_CLASS, xtest_available
    if _x11 is not None:
        return _x11
    try:
        from Xlib.display import Display
        xdisplay = Display()
        modifier_keycodes = xdisplay.get_modifier_mapping(
        )  # there should be a way to do this in Gdk
        NET_ACTIVE_WINDOW = xdisplay.intern_atom('_NET_ACTIVE_WINDOW')
        NET_WM_PID = xdisplay.intern_atom('_NET_WM_PID')
        WM_CLASS = xdisplay.intern_atom('WM_CLASS')
        _x11 = True  # X11 available
        if _log.isEnabledFor(_INFO):
            _log.info('X11 library loaded and display set up')
    except Exception:
        _log.warn('X11 not available - some rule capabilities inoperable: %s',
                  exc_info=_sys.exc_info())
        _x11 = False
        xtest_available = False
    return _x11
Esempio n. 2
0
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
Esempio n. 3
0
if x11:
    display = Display()
    context = 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.KeyPress, X.KeyRelease),
            'errors': (0, 0),
            'client_started': False,
            'client_died': False,
        }])
    modifier_keycodes = display.get_modifier_mapping()
    current_key_modifiers = 0


def modifier_code(keycode):
    if keycode == 0:
        return None
    for m in range(0, len(modifier_keycodes)):
        if keycode in modifier_keycodes[m]:
            return m


def key_press_handler(reply):
    global current_key_modifiers
    data = reply.data
    while len(data):
Esempio n. 4
0
class KeyboardListener:

	def __init__(self, callback=None, on_error=None):
		self.keys = []
		self.grabbed: List = []
		self.temporary_grab: List = []
		self.on_error = on_error
		self.callback = callback
		# XLib errors are received asynchronously, thus the need for a running state flag
		self.stopped = False

		self.well_thread = threading.Thread(target=self.x_client_loop, daemon=True, name='hotkey well thread')
		self.well_connection = Display()
		self.well_connection.set_error_handler(self._local_display_error_handler)

		self.mod_keys_set = set()
		for mods in self.well_connection.get_modifier_mapping():
			for mod in mods:
				self.mod_keys_set.add(mod)

		self.root: Xlib.display.Window = self.well_connection.screen().root
		self.root.change_attributes(event_mask=X.KeyPressMask | X.KeyReleaseMask)

		self.accelerators_root = {'level': 0, 'children': []}
		self.contextual_accelerators = self.accelerators_root

	#
	# API
	#
	def add(self, key):
		self.keys.append(key)

	def start(self):
		for key in self.keys:
			self._bind_to_root(key)
		self.well_thread.start()

	def _bind_to_root(self, key):
		self._bind(key, self.accelerators_root)

	def _bind(self, key: Key, node):
		gdk_key_val, code, mask = parse_accelerator(key.accelerator)
		node['has_children'] = True

		if (code, mask) in node:
			raise Exception('key ({}) already mapped'.format(', '.join(key.accelerator)))

		we = {'code': code, 'mask': mask, 'has_children': False, 'children': [], 'level': node['level'] + 1, 'key': key}
		node[(code, mask)] = we
		node['children'].append(we)

		if we['level'] == 1:
			self._grab_keys(code, mask)
			self.well_connection.sync()
			if self.stopped:
				raise Exception('Unable to bind: {}'.format(', '.join(key.accelerator)))

		for combination in key.combinations:
			self._bind(combination, we)

	def stop(self):
		self.stopped = True
		self.well_connection.close()

	#
	# xlib plugs
	#
	def _local_display_error_handler(self, exception, *args):
		print('Error at local display: {}'.format(exception), file=sys.stderr)
		if not self.stopped:
			self.stopped = True
			self.on_error()

	#
	# Internal API
	#
	def _grab_keys(self, code, mask):
		self.root.grab_key(code, mask, True, X.GrabModeAsync, X.GrabModeAsync)
		self.root.grab_key(code, mask | X.Mod2Mask, True, X.GrabModeAsync, X.GrabModeAsync)
		self.root.grab_key(code, mask | X.LockMask, True, X.GrabModeAsync, X.GrabModeAsync)
		self.root.grab_key(code, mask | X.Mod2Mask | X.LockMask, True, X.GrabModeAsync, X.GrabModeAsync)
		self.grabbed.append((code, mask))

	def _ungrab_keys(self, code, mask):
		self.root.ungrab_key(code, mask)
		self.root.ungrab_key(code, mask | X.Mod2Mask)
		self.root.ungrab_key(code, mask | X.LockMask)
		self.root.ungrab_key(code, mask | X.Mod2Mask | X.LockMask)
		self.grabbed.remove((code, mask))

	#
	# Event handling
	#
	def x_client_loop(self):
		while not self.stopped:
			event = self.well_connection.next_event()

			if event.type == X.KeyPress and event.detail not in self.mod_keys_set:
				self.handle_keypress(event)

	# http://python-xlib.sourceforge.net/doc/html/python-xlib_13.html
	def handle_keypress(self, event: Xlib.protocol.event.KeyPress):
		_wasmapped, keyval, egroup, level, consumed = Gdk.Keymap.get_default().translate_keyboard_state(
			event.detail, Gdk.ModifierType(event.state), 0)

		code = event.detail
		mask = normalize_state(event.state)
		event.keyval = keyval
		event.keymod = Gdk.ModifierType(mask)  # TODO: explain
		key_name = Gdk.keyval_name(event.keyval)
		# print('key: {} wid: {} root_x: {} event_x: {}'.format(key_name, event.window.id, event.root_x, event.event_x))

		if (code, mask) not in self.contextual_accelerators:
			self.reset_key_streak(event.time)

		if (code, mask) in self.contextual_accelerators:

			self.callback(self.contextual_accelerators[(code, mask)]['key'], event)

			if self.contextual_accelerators[(code, mask)]['has_children']:
				self.contextual_accelerators = self.contextual_accelerators[(code, mask)]
				self.root.grab_keyboard(True, X.GrabModeAsync, X.GrabModeAsync, event.time)
				self.temporary_grab = True
			else:
				self.reset_key_streak(event.time)

	def reset_key_streak(self, time):
		self.contextual_accelerators = self.accelerators_root
		if self.temporary_grab:
			self.well_connection.ungrab_keyboard(time)
			self.temporary_grab = False
Esempio n. 5
0
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
Esempio n. 6
0
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
Esempio n. 7
0
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
Esempio n. 8
0
class KeyboardListener:
    def __init__(self, callback=None, on_error=None):
        self.on_error = on_error
        self.callback = callback
        # XLib errors are received asynchronously, thus the need for a running state flag
        self.stopped = False

        self.record_thread = threading.Thread(
            target=self._record, name='x keyboard listener thread')
        self.well_thread = threading.Thread(target=self._drop_key,
                                            daemon=True,
                                            name='hotkey well thread')

        self.recording_connection = Display()
        self.well_connection = Display()
        self.recording_connection.set_error_handler(
            self._record_display_error_handler)
        self.well_connection.set_error_handler(
            self._local_display_error_handler)

        if not self.recording_connection.has_extension("RECORD"):
            raise Exception("RECORD extension not found")

        r = self.recording_connection.record_get_version(0, 0)
        print("RECORD extension version %d.%d" %
              (r.major_version, r.minor_version))
        self.context = self.recording_connection.record_create_context(
            0, [record.AllClients], CONTEXT_FILTER)

        self.mod_keys_set = set()
        for mods in self.well_connection.get_modifier_mapping():
            for mod in mods:
                self.mod_keys_set.add(mod)

        self.root = self.well_connection.screen().root
        self.root.change_attributes(event_mask=X.KeyPressMask
                                    | X.KeyReleaseMask)
        self.modifiers_count = self.modified_count = 0
        self.code_map = {}
        self.composed_code_map = {}
        self.composed_mapping_first_code = None
        self.multiplier = ''

    #
    # API
    #
    def bind(self, key):
        if self.stopped:
            return
        if len(key.accelerators) == 1:
            self._bind_single_accelerator(key)
        elif len(key.accelerators) == 2:
            self._bind_composed_accelerator(key)
        self.well_connection.sync()
        if self.stopped:
            print('Unable to bind: {}'.format(', '.join(key.accelerators)),
                  file=sys.stderr)

    def start(self):
        self.well_connection.sync()
        self.recording_connection.sync()
        if self.stopped:
            return
        self.well_thread.start()
        self.record_thread.start()

    def stop(self):
        self.stopped = True
        if self.record_thread.is_alive():
            self.well_connection.record_disable_context(self.context)
            self.well_connection.close()
            print('display stopped recording')
            self.record_thread.join()
        print('recording thread ended')

    #
    # Thread targets
    #
    def _drop_key(self):
        while not self.stopped:
            self.well_connection.next_event()

    def _record(self):
        self.recording_connection.record_enable_context(
            self.context, self.handler)
        self.recording_connection.record_free_context(self.context)
        self.recording_connection.close()

    def _record_display_error_handler(self, exception, *args):
        print('Error at record display: {}'.format(exception), file=sys.stderr)
        if not self.stopped:
            self.stopped = True
            self.on_error()

    def _local_display_error_handler(self, exception, *args):
        print('Error at local display: {}'.format(exception), file=sys.stderr)
        if not self.stopped:
            self.stopped = True
            self.on_error()

    #
    # Internal API
    #
    def _grab_keys(self, code, mask):
        self.root.grab_key(code, mask, True, X.GrabModeAsync, X.GrabModeAsync)
        self.root.grab_key(code, mask | X.Mod2Mask, True, X.GrabModeAsync,
                           X.GrabModeAsync)

    def _bind_single_accelerator(self, key):
        gdk_keyval, code, mask = parse_accelerator(key.accelerators[0])

        self._grab_keys(code, mask)

        if code not in self.code_map:
            self.code_map[code] = {}

        self.code_map[code][mask] = key

    def _bind_composed_accelerator(self, key):
        gdk_keyval, code, mask = parse_accelerator(key.accelerators[0])
        second_gdk_keyval, second_code, second_mask = parse_accelerator(
            key.accelerators[1])

        self._grab_keys(code, mask)

        if code not in self.composed_code_map:
            self.composed_code_map[code] = {}
        if mask not in self.composed_code_map[code]:
            self.composed_code_map[code][mask] = {}
        if second_code not in self.composed_code_map[code][mask]:
            self.composed_code_map[code][mask][second_code] = {}

        if second_mask in self.composed_code_map[code][mask][second_code]:
            raise Exception('key ({}) already mapped'.format(', '.join(
                key.accelerators)))

        self.composed_code_map[code][mask][second_code][second_mask] = key

    #
    # Event handling
    #
    def handler(self, reply):
        data = reply.data
        while len(data):
            event, data = rq.EventField(None).parse_binary_value(
                data, self.recording_connection.display, None, None)

            if event.detail in self.mod_keys_set:
                self.modifiers_count += 1 if event.type == X.KeyPress else -1
                self.modified_count = 0
                continue

            if self.modifiers_count:
                self.modified_count += 1 if event.type == X.KeyPress else -1

            if event.type == X.KeyPress:
                self.handle_keypress(event)

    def handle_keypress(self, event):
        _wasmapped, keyval, egroup, level, consumed = Gdk.Keymap.get_default(
        ).translate_keyboard_state(event.detail, Gdk.ModifierType(event.state),
                                   0)

        code = event.detail
        event.keyval = keyval  # TODO: explain
        mask = normalize_state(event.state)

        if self.composed_mapping_first_code and self.composed_mapping_first_code != (
                code, mask):

            key_name = Gdk.keyval_name(event.keyval)
            if not mask and key_name and key_name.isdigit():
                self.multiplier = self.multiplier + key_name
                return

            second_code_map = self.composed_code_map[
                self.composed_mapping_first_code[0]][
                    self.composed_mapping_first_code[1]]
            if code in second_code_map and mask in second_code_map[code]:
                multiplier_int = int(self.multiplier) if self.multiplier else 1
                self.callback(second_code_map[code][mask],
                              event,
                              multiplier=multiplier_int)

            self.composed_mapping_first_code = None

        elif self.modified_count == 1:

            if code in self.code_map and mask in self.code_map[code]:
                self.callback(self.code_map[code][mask], event)

            if code in self.composed_code_map and mask in self.composed_code_map[
                    code]:
                self.composed_mapping_first_code = (code, mask)

        self.multiplier = ''
Esempio n. 9
0
# KeyPress, MouseScroll, and MouseClick actions use XTest (under X11) or uinput.
# For uinput to work the user must have write access for /dev/uinput.
# To get this access run  sudo setfacl -m u:${user}:rw /dev/uinput
#
# Rule GUI keyname determination uses a local file generated
#   from http://cgit.freedesktop.org/xorg/proto/x11proto/plain/keysymdef.h
#   and http://cgit.freedesktop.org/xorg/proto/x11proto/plain/XF86keysym.h
# because there does not seem to be a non-X11 file for this set of key names

XK_KEYS = _keysymdef.keysymdef

try:
    import Xlib
    from Xlib.display import Display
    xdisplay = Display()
    modifier_keycodes = xdisplay.get_modifier_mapping(
    )  # there should be a way to do this in Gdk
    x11 = True

    NET_ACTIVE_WINDOW = xdisplay.intern_atom('_NET_ACTIVE_WINDOW')
    NET_WM_PID = xdisplay.intern_atom('_NET_WM_PID')
    WM_CLASS = xdisplay.intern_atom('WM_CLASS')

    # set up to get keyboard state using ctypes interface to libx11
    import ctypes

    class XkbDisplay(ctypes.Structure):
        """ opaque struct """

    class XkbStateRec(ctypes.Structure):
        _fields_ = [
            ('group', ctypes.c_ubyte), ('locked_group', ctypes.c_ubyte),
Esempio n. 10
0
class KeyTools:
    KEY_PRESS = X.KeyPress
    KEY_RELEASE = X.KeyRelease
    def __init__(self):
        self._xdisplay = Display()
        self._xroot = self._xdisplay.screen().root
        self._clipboard = gtk.clipboard_get()
        self._clipPrimay = gtk.clipboard_get("PRIMARY")
        self._entryForPaste = 118, X.ShiftMask
        self._group = 0
        self.loadModifiers()
        self._keymap = gdk.keymap_get_default()  # @UndefinedVariable
        
    def loadModifiers(self):
        self._modifiers = []
        self._modifierList = []
        for key in self._xdisplay.get_modifier_mapping():
            li = [k for k in key if k]
            #for altgr key
            if 92 in li:
                li.append(108)
            self._modifierList += li
            self._modifiers.append(li)
    
    
    def filterGroup(self, entries):
        if entries:
            return [e for e in entries if e[-2] == self._group]
        return []
    
    
    def remapKey(self, keycode, keysyms):
        allKeysyms = list(self._xdisplay.get_keyboard_mapping(keycode, 1)[0])
        keysyms = keysyms + [0]*(4 - len(keysyms))
        allKeysyms[:2] = keysyms[:2]
        allKeysyms[4:6] = keysyms[2:]
        self._xdisplay.change_keyboard_mapping(keycode, [allKeysyms])
        self._xdisplay.sync()
    
    
    
    def resetMapping(self):
        try:
            process = Popen('setxkbmap -print -verbose 7'.split(), stdout=PIPE, stderr=PIPE)
        except OSError:
            print 'install setxkbmap'
        
        for line in process.stderr:
            print 'setxkbmap error: {}'.format(line)
        
        layout = variant = ''
        
        for line in process.stdout:
            line = line.rstrip()
            if line == '':
                break
            
            if line.startswith('layout:'):
                layout = line.split()[1]
            elif line.startswith('variant:'):
                variant = line.split()[1]
                break
                
        command = ['setxkbmap']
        if layout:
            command += ['-layout', layout]
                   
        if variant:
            command += ['-variant', variant]
        if layout or command:
            try:
                process = Popen(command, stdout=PIPE, stderr=PIPE)
            except OSError:
                print 'install setxkbmap'
                
            for line in process.stderr:
                print 'setxkbmap error: {}'.format(line)
    
    def isModifier(self, keycode):
        return keycode in self._modifierList
    
    def getModMask(self, keycode):
        for i, mods in enumerate(self._modifiers):
            if keycode in mods:
                return 2**i
            
        return 0
    
    def modifiersKeycodeList(self):
        return self._modifierList
    
    def numMask(self):
        return X.Mod2Mask
    
    def keycode2char(self, keycode, mods, group=0):
        char = ''
        name = ''
        info = self._keymap.translate_keyboard_state(keycode, mods, group)
        if info:
            keysym = info[0]
            char = gdk.keyval_to_unicode(keysym)  # @UndefinedVariable
            if char:
                char = unichr(char)
            name = gdk.keyval_name(keysym)  # @UndefinedVariable
            
        return char or '', name or ''
    
    def removeNumLockMask(self, keycode, mod):
        if not self.isKeypadKey(keycode) and mod & X.Mod2Mask:
            return mod ^ X.Mod2Mask
        
        return mod
     
    def entry2keysym(self, keycode, modMask):
        info = self._keymap.translate_keyboard_state(keycode, modMask, self._group)
        if info:
            return info[0]
        
        return None
        
    def entry2name(self, keycode, modMask):
        keysym = self.entry2keysym(keycode, modMask)
        if keysym is not None:
            return gdk.keyval_name(keysym)  # @UndefinedVariable
        
        return None
    
    def keycode2entries(self, keycode):
        return self.filterGroup(self._keymap.get_entries_for_keycode(keycode))
    
    def keysym2entry(self, keysym):
        if not keysym:
            return None
        
        infos = self._keymap.get_entries_for_keyval(keysym)  # @UndefinedVariable
        if infos:
            for info in infos:
                keycode, group, level = info
                if group == self._group:
                    if level < len(LEVEL_MOD):
                        mod = LEVEL_MOD[level]
                        return keycode, mod
            
        return None
    
    def keysym2deadEntries(self, keysym):
        resp = ()
        entry = self.keysym2entry(keysym)
        if entry:
            keycode, mod = entry
            resp = ((keycode, mod), )
            
        if not resp:
            deadKeys = self.findWithDeadKey(keysym)
            if deadKeys:
                keyKeysym, deadKeysym = deadKeys
                keyKeycodes = self.keysym2entry(keyKeysym)
                deadKeycodes = self.keysym2entry(deadKeysym)
                if keyKeycodes and deadKeycodes:
                    keyKeycode, keyMod = keyKeycodes
                    deadKeycode, deadMod = deadKeycodes
                    resp = ((deadKeycode, deadMod), (keyKeycode, keyMod))
              
        return resp
        
    
    def keycode2charsAndNames(self, keycode):
        entries = self.keycode2entries(keycode)
        chars = []
        names = []
        for entry in entries:
            chars.append(keysym2char(entry[0]))
            names.append(keysym2name(entry[0]))
            if len(chars) >= 4:
                break
        
        while not names[-1]:
            chars.pop(-1)
            names.pop(-1)
        return chars, names
    
    def keycode2keysyms(self, keycode):
        entries = self.keycode2entries(keycode)
        return [e[0] for e in entries][:4]
                
    def char2entries(self, char):
        keysym = gdk.unicode_to_keyval(ord(char))  # @UndefinedVariable
        if keysym:
            return self.keysym2deadEntries(keysym)
        
        return ()
    
    def findWithDeadKey(self, keysym):
        name = gdk.keyval_name(keysym)  # @UndefinedVariable
        for deadName in DEAD_KEYS:
            if name.endswith(deadName):
                keyName = name[:-len(deadName)]
                deadName = {'ring': 'abovering', 
                           'schwa': 'small_schwa', 
                           'SCHWA': 'capital_schwa'}.get(deadName, deadName)
                deadName = 'dead_' + deadName
                keyKeysym = gdk.keyval_from_name(keyName)  # @UndefinedVariable
                deadSym = gdk.keyval_from_name(deadName)  # @UndefinedVariable
                return keyKeysym, deadSym
        return None
            
    
    def isKeypadKey(self, keycode):
        entry = self._keymap.get_entries_for_keycode(keycode)
        if entry:
            for info in entry:
                if info[2] == self._group:
                    name = gdk.keyval_name(info[0])  # @UndefinedVariable
                    if name and name.startswith('KP_'):
                        return True
                    
        return False
    
        
    def grabKey(self, keycode, modMask):
        self._xroot.grab_key(keycode, modMask, 0, X.GrabModeAsync, X.GrabModeAsync)
        if not self.isKeypadKey(keycode) and not modMask & X.Mod2Mask:
            self._xroot.grab_key(keycode, modMask | X.Mod2Mask, 0, X.GrabModeAsync, X.GrabModeAsync)
            
    def ungrabKey(self, keycode, modMask):
        self._xroot.ungrab_key(keycode, modMask)
        if not self.isKeypadKey(keycode) and not modMask & X.Mod2Mask:
            self._xroot.ungrab_key(keycode, modMask | X.Mod2Mask)
    
    def nextKeyEvent(self, typ=KEY_PRESS):
        if isinstance(typ, int):
            typ = (typ,)
        num = self._xdisplay.pending_events()
        if num:
            for _ in range(num):
                event = self._xdisplay.next_event()
                if event.type in typ:
                    return keyEvent(event.type, event.detail, event.state)
                self._xdisplay.allow_events(X.AsyncKeyboard, X.CurrentTime)
        return None

    
    def slotClipboard(self, clipboard, text, backup):
        self.sendEntry(*self._entryForPaste)
        t = Timer(0.1, self.restoreClipboard, (backup,))
        t.start()
        
        
    def restoreClipboard(self, backup):
        self._clipboard.request_text(lambda a, b, c: None)
        if backup:
            self._clipboard.set_text(backup or '')
            self._clipPrimay.clear()
            self._clipboard.store()
     
    def sendText(self, text):
        backup = self._clipboard.wait_for_text()
        self._clipboard.set_text(text)
        self._clipPrimay.set_text(text)
        self._clipboard.request_text(self.slotClipboard, backup)
        self._clipboard.store()
        self._clipPrimay.store()
                
    def sendKeysym(self, keysym):
        entries = self.keysym2deadEntries(keysym)
        for entry in entries:
            self.sendEntry(*entry)
    
    def sendEntry(self, keycode, mod):
        self.pressKey(keycode, mod)
        self.releaseKey(keycode, mod)
    
    def pressKey(self, keycode, modMask):
        window = self._xdisplay.get_input_focus()._data["focus"]
        evt = Xlib.protocol.event.KeyPress(  # @UndefinedVariable
            time = X.CurrentTime,
            root = self._xroot,
            window = window,
            same_screen = 0, child = Xlib.X.NONE,
            root_x = 0, root_y = 0, event_x = 0, event_y = 0,
            state = modMask,
            detail = keycode
            )
        window.send_event(evt, propagate = True)
        
    def releaseKey(self, keycode, modMask):
        window = self._xdisplay.get_input_focus()._data["focus"]
        evt = Xlib.protocol.event.KeyRelease(  # @UndefinedVariable
            time = X.CurrentTime,
            root = self._xroot,
            window = window,
            same_screen = 0, child = Xlib.X.NONE,
            root_x = 0, root_y = 0, event_x = 0, event_y = 0,
            state = modMask,
            detail = keycode
            )
        window.send_event(evt, propagate = True)
Esempio n. 11
0
class Keyboard(KeyboardMeta):
    """
    The PyKeyboard implementation for X11 systems (mostly linux). This
    allows one to simulate keyboard input.
    """
    def __init__(self, display=None):
        PyKeyboardMeta.__init__(self)
        self.display = Display(display)
        self.root = self.display.screen().root
        
        XK.load_keysym_group('xkb')
        
        altList = self.display.keysym_to_keycodes(XK.XK_ISO_Level3_Shift)
        self.__usable_modifiers = (0, 1)
        for code, offset in altList:
            if code == 108 and offset == 0:
                self.__usable_modifiers += (4, 5)
                break
            
        mapping = self.display.get_modifier_mapping()
        self.modmasks = {}
        for keyname in MODIFIERS:
            keysym = XK.string_to_keysym(keyname)
            keycodes = self.display.keysym_to_keycodes(keysym)
            
            found = False
            for keycode, lvl in keycodes:
                for index, mask in MASK_INDEXES:
                    if keycode in mapping[index]:
                        self.modmasks[keycode] = mask
                        found = True
                    
                if found:
                    break
 
        self.flags = {        
            'Shift': X.ShiftMask,
            'Lock': X.LockMask,
            'Ctrl': X.ControlMask,
            'Alt': 0,
            'AltGr': self.modmasks[altList[0][0]],
            'Hankaku': 0}
        
        self.special_key_assignment()

    def __findUsableKeycode(self, keycodes):
        for code, mask in keycodes:
            if mask in self.__usable_modifiers:
                return code, mask

        return None, None

    def press_key(self, character='', modifier=0):
        """
        Press a given character key. Also works with character keycodes as
        integers, but not keysyms.
        """
        window = self.display.get_input_focus().focus
        
        char_val, char_mask = self.lookup_character_value(character)
        if char_val == None or char_mask == None:
            return False
        char_mask ^= modifier
        
        print character, char_mask, modifier
        
        event = protocol.event.KeyPress(
            detail = char_val,
            time = X.CurrentTime,
            root = self.root,
            window = window,
            child = X.NONE,
            root_x = 0,
            root_y = 0,
            event_x = 0,
            event_y = 0,
            state = char_mask,
            same_screen = 0)
        window.send_event(event)
        self.display.sync()

    def release_key(self, character='', modifier=0):
        """
        Release a given character key. Also works with character keycodes as
        integers, but not keysyms.
        """
        window = self.display.get_input_focus().focus
        
        char_val, char_mask = self.lookup_character_value(character)
        if char_val == None or char_mask == None:
            return False
        char_mask ^= modifier
        
        event = protocol.event.KeyRelease(
            detail = char_val,
            time = X.CurrentTime,
            root = self.root,
            window = window,
            child = X.NONE,
            root_x = 0,
            root_y = 0,
            event_x = 0,
            event_y = 0,
            state = char_mask,
            same_screen = 0)
        window.send_event(event)
        self.display.sync()

    def special_key_assignment(self):
        """
        Determines the keycodes for common special keys on the keyboard. These
        are integer values and can be passed to the other key methods.
        Generally speaking, these are non-printable codes.
        """
        #This set of keys compiled using the X11 keysymdef.h file as reference
        #They comprise a relatively universal set of keys, though there may be
        #exceptions which may come up for other OSes and vendors. Countless
        #special cases exist which are not handled here, but may be extended.
        #TTY Function Keys
        self.backspace_key = self.lookup_character_value('BackSpace')[0]
        self.tab_key = self.lookup_character_value('Tab')[0]
        self.linefeed_key = self.lookup_character_value('Linefeed')[0]
        self.clear_key = self.lookup_character_value('Clear')[0]
        self.return_key = self.lookup_character_value('Return')[0]
        self.enter_key = self.return_key  # Because many keyboards call it "Enter"
        self.pause_key = self.lookup_character_value('Pause')[0]
        self.scroll_lock_key = self.lookup_character_value('Scroll_Lock')[0]
        self.sys_req_key = self.lookup_character_value('Sys_Req')[0]
        self.escape_key = self.lookup_character_value('Escape')[0]
        self.delete_key = self.lookup_character_value('Delete')[0]
        #Modifier Keys
        self.shift_l_key = self.lookup_character_value('Shift_L')[0]
        self.shift_r_key = self.lookup_character_value('Shift_R')[0]
        self.shift_key = self.shift_l_key  # Default Shift is left Shift
        self.alt_l_key = self.lookup_character_value('Alt_L')[0]
        self.alt_r_key = self.lookup_character_value('Alt_R')[0]
        self.alt_key = self.alt_l_key  # Default Alt is left Alt
        self.alt_gr_key = self.lookup_character_value('ISO_Level3_Shift')[0]
        self.control_l_key = self.lookup_character_value('Control_L')[0]
        self.control_r_key = self.lookup_character_value('Control_R')[0]
        self.control_key = self.control_l_key  # Default Ctrl is left Ctrl
        self.caps_lock_key = self.lookup_character_value('Caps_Lock')[0]
        self.capital_key = self.caps_lock_key  # Some may know it as Capital
        self.shift_lock_key = self.lookup_character_value('Shift_Lock')[0]
        self.meta_l_key = self.lookup_character_value('Meta_L')[0]
        self.meta_r_key = self.lookup_character_value('Meta_R')[0]
        self.super_l_key = self.lookup_character_value('Super_L')[0]
        self.windows_l_key = self.super_l_key  # Cross-support; also it's printed there
        self.super_r_key = self.lookup_character_value('Super_R')[0]
        self.windows_r_key = self.super_r_key  # Cross-support; also it's printed there
        self.hyper_l_key = self.lookup_character_value('Hyper_L')[0]
        self.hyper_r_key = self.lookup_character_value('Hyper_R')[0]
        #Cursor Control and Motion
        self.home_key = self.lookup_character_value('Home')[0]
        self.up_key = self.lookup_character_value('Up')[0]
        self.down_key = self.lookup_character_value('Down')[0]
        self.left_key = self.lookup_character_value('Left')[0]
        self.right_key = self.lookup_character_value('Right')[0]
        self.end_key = self.lookup_character_value('End')[0]
        self.begin_key = self.lookup_character_value('Begin')[0]
        self.page_up_key = self.lookup_character_value('Page_Up')[0]
        self.page_down_key = self.lookup_character_value('Page_Down')[0]
        self.prior_key = self.lookup_character_value('Prior')[0]
        self.next_key = self.lookup_character_value('Next')[0]
        #Misc Functions
        self.select_key = self.lookup_character_value('Select')[0]
        self.print_key = self.lookup_character_value('Print')[0]
        self.print_screen_key = self.print_key  # Seems to be the same thing
        self.snapshot_key = self.print_key  # Another name for printscreen
        self.execute_key = self.lookup_character_value('Execute')[0]
        self.insert_key = self.lookup_character_value('Insert')[0]
        self.undo_key = self.lookup_character_value('Undo')[0]
        self.redo_key = self.lookup_character_value('Redo')[0]
        self.menu_key = self.lookup_character_value('Menu')[0]
        self.apps_key = self.menu_key  # Windows...
        self.find_key = self.lookup_character_value('Find')[0]
        self.cancel_key = self.lookup_character_value('Cancel')[0]
        self.help_key = self.lookup_character_value('Help')[0]
        self.break_key = self.lookup_character_value('Break')[0]
        self.mode_switch_key = self.lookup_character_value('Mode_switch')[0]
        self.script_switch_key = self.lookup_character_value('script_switch')[0]
        self.num_lock_key = self.lookup_character_value('Num_Lock')[0]
        #Keypad Keys: Dictionary structure
        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]
        self.keypad_keys = {k: self.lookup_character_value('KP_'+str(k)[0]) for k in keypad}
        self.numpad_keys = self.keypad_keys
        #Function Keys/ Auxilliary Keys
        #FKeys
        self.function_keys = [None] + [self.lookup_character_value('F'+str(i)[0]) for i in xrange(1,36)]
        #LKeys
        self.l_keys = [None] + [self.lookup_character_value('L'+str(i)[0]) for i in xrange(1,11)]
        #RKeys
        self.r_keys = [None] + [self.lookup_character_value('R'+str(i)[0]) for i in xrange(1,16)]

        #Unsupported keys from windows
        self.kana_key = None
        self.hangeul_key = None # old name - should be here for compatibility
        self.hangul_key = None
        self.junjua_key = None
        self.final_key = None
        self.hanja_key = None
        self.kanji_key = None
        self.convert_key = None
        self.nonconvert_key = None
        self.accept_key = None
        self.modechange_key = None
        self.sleep_key = None

    def lookup_character_value(self, character):
        """
        Looks up the keysym for the character then returns the keycode mapping
        and modifier for that keysym.
        """
        ch_keysym = XK.string_to_keysym(character)
        ch_mask = 0
        if ch_keysym == 0:
            if character in SPECIAL_X_KEYSYMS:
                ch_keysym = XK.string_to_keysym(SPECIAL_X_KEYSYMS[character])
            elif len(character) == 1:
                ch_keysym = ord(character)
        ch_keycodes = self.display.keysym_to_keycodes(ch_keysym)
        
        if len(ch_keycodes) == 0 and len(character) == 1:
            ch_keycodes = self.display.keysym_to_keycodes(ord(character.lower()))
            ch_mask ^= X.LockMask
            
        if len(ch_keycodes) > 0:
            ch_keycode, mask = self.__findUsableKeycode(ch_keycodes)
            if ch_keycode == None or mask == None:
                return None, None
            else:
                ch_mask ^= mask
        else:
            return None, None
        
        if ch_mask ^ 4 < 4:
            ch_mask ^= 4
            ch_mask ^= self.modmasks[self.alt_gr_key]
        
        return ch_keycode, ch_mask