""" Keypress actions. @author: Eitan Isaacson @author: Nagappan Alagappan @copyright: Copyright (c) 2007 - 2009 Eitan Isaacson @copyright: Copyright (c) 2009-13 Nagappan Alagappan @license: LGPL http://ldtp.freedesktop.org This file may be distributed and/or modified under the terms of the GNU Lesser General Public License version 2 as published by the Free Software Foundation. This file is distributed without any warranty; without even the implied warranty of merchantability or fitness for a particular purpose. See 'COPYING' in the source distribution for more information. Headers in this file shall remain intact. """ import re import time try: from gi.repository import GObject as gobject except: import gobject import pyatspi import subprocess from sequence_step import AtomicAction _ = lambda x: x # Highest granularity, define timing for every single press and release # Minimum delta time min_delta = 50 # Maximum time before a key release release_max = 400 _non_print_key_val = {"escape" : 9, "esc" : 9, "backspace" : 22, "bksp" : 22, "ctrl" : 37, "windowskey" : 115, "tab" : 23, "return" : 36, "enter" : 36, "shift" : 50, "shiftl" : 50, "shiftr" : 62, "home" : 110, "end" : 115, "window" : 115, "alt" : 64, "altl" : 64, "altr" : 108, "up" : 111, "down" : 116, "right" : 114, "left" : 113, "space" : 65, "capslock" : 66, "caps" : 66, "menu" : 135, "ins" : 106, "del" : 119, "insert" : 106, "delete" : 119, "pageup" : 112, "pagedown" : 117, "pgup" : 112, "pgdown" : 117, "numlock" : 77, "scrolllock" : 78, "F1" : 67, "F2" : 68, "F3" : 69, "F4" : 70, "F5" : 71, "F6" : 72, "F7" : 73, "F8" : 74, "F9" : 75, "F10" : 76, "F11" : 95, "F12" : 96, "prtscrn" : 107, "ctrll" : 37} def _get_keyboard_keycodes(): output = subprocess.Popen('xmodmap -pke', stdout = subprocess.PIPE, stderr = subprocess.PIPE, shell = True, close_fds = True).communicate() if output[0] != '': output = output[0] for line in output.split('\n'): if line.strip() == '': continue split = re.split('=', line, maxsplit = 2) keycode = int(re.split(" ", split[0].strip())[-1]) if len(split[1]): key = re.split(" ", split[1], 3)[1].lower() _non_print_key_val[key] = keycode _get_keyboard_keycodes() class KeyCombo: def __init__(self): self.shift = False self.capslck = False self.non_print_key = False self.value = None class KeyboardOp: def __init__(self): self._downchar = 12 self._upchar = 21 self._undefined_key = -1 self._max_tokens = 256 self._max_tok_size = 15 def _get_key_value(self, keyval): # A - Z / a - z _char_key = {'a' : 38, 'b' : 56, 'c' : 54, 'd' : 40, 'e' : 26, 'f' : 41, 'g' : 42, 'h' : 43, 'i' : 31, 'j' : 44, 'k' : 45, 'l' : 46, 'm' : 58, 'n' : 57, 'o' : 32, 'p' : 33, 'q' : 24, 'r' : 27, 's' : 39, 't' : 28, 'u' : 30, 'v' : 55, 'w' : 25, 'x' : 53, 'y' : 29, 'z' : 52} # 0 - 9 _digit_key = {'0' : 19, '1' : 10, '2' : 11, '3' : 12, '4' : 13, '5' : 14, '6' : 15, '7' : 16, '8' : 17, '9' : 18} # Symbols _symbol_key_val = {'-' : 20, '=' : 21, '[' : 34, ']' : 35, ';' : 47, '\'' : 48, '`' : 49, '\\' : 51, ',' : 59, '.' : 60, '/' : 61, ' ' : 65} _symbol_shift_key_val = {'!' : 10, '@' : 11, '#' : 12, '$' : 13, '%' : 14, '^' : 15, '&' : 16, '*' : 17, '(' : 18, ')' : 19, '_' : 20, '+' : 21, '{' : 34, '}' : 35, ':' : 47, '"' :48, '~' : 49, '|' : 51, '<' : 59, '>' : 60, '?' : 61} return_val = KeyCombo() if len(keyval) == 1: # This will identify small characters if keyval in _char_key: return_val.shift = False return_val.capslck = False return_val.non_print_key = False return_val.value = _char_key[keyval] return return_val # This will identify Capital Charaters i.e. Shift+Small # Character if keyval.lower() in _char_key: return_val.shift = False return_val.capslck = True return_val.non_print_key = False return_val.value = _char_key[keyval.lower()] return return_val # This will identify Digits if keyval in _digit_key: return_val.shift = False return_val.capslck = False return_val.non_print_key = False return_val.value = _digit_key[keyval] return return_val # This will identify Symbols # Symbols obtained without using Shift Key if keyval in _symbol_key_val: return_val.shift = False return_val.capslck = False return_val.non_print_key = False return_val.value = _symbol_key_val[keyval] return return_val # Symbols produced with a key combination # including Shift Key if keyval in _symbol_shift_key_val: return_val.shift = True return_val.capslck = False return_val.non_print_key = False return_val.value = _symbol_shift_key_val[keyval] return return_val else: # This is for identifying non printing keys like numlock, # capslock, etc if keyval.lower() in _non_print_key_val: return_val.shift = False return_val.capslck = False return_val.non_print_key = True return_val.value = _non_print_key_val[keyval.lower()] return return_val # Key Undefined return_val.shift = False return_val.capslck = False return_val.non_print_key = False return_val.value = self._undefined_key return return_val def get_keyval_id(self, input_str): index = 0 key_vals = [] while index < len(input_str): token = '' # Identified a Non Printing Key if input_str[index] == '<': index += 1 i = 0 while input_str[index] != '>' and i < self._max_tok_size: token += input_str[index] index += 1 i += 1 if input_str[index] != '>': # Premature end of string without an opening '<' return None index += 1 else: token = input_str[index] index += 1 key_val = self._get_key_value(token) if key_val.value == self._undefined_key: # Invalid key return None key_vals.append(key_val) return key_vals class KeyPressAction(AtomicAction): """ A key press step. Emulates a keyboard key press. """ def __init__(self, key_code=None, key_name=None, delta_time=0): """ Initialize L{KeyPressAction}. Could use either a hardware keycode, a key name, or both. @param delta_time: Time to wait before performing this step. @type delta_time: integer @param key_code: Hardware keycode. @type key_code: integer. @param key_name: Key name. @type key_name: string. """ if (None, None) == (key_name, key_code): raise TypeError("Need either a key code or a key name") if delta_time > release_max: delta_time = release_max self._key_name = key_name if key_code is None: self.key_op = KeyboardOp() key_vals = self.key_op.get_keyval_id(key_name) if not key_vals: return for key_val in key_vals: AtomicAction.__init__(self, delta_time, self._keyPress, key_val.value) else: AtomicAction.__init__(self, delta_time, self._keyPress, key_code) def _keyPress(self, key_code): """ Perform actual key press. @param key_code: Hardware key code. @type key_code: integer """ pyatspi.Registry.generateKeyboardEvent(key_code, None, pyatspi.KEY_PRESS) def __str__(self): """ String representation of instance. @return: String representation of instance. @rtype: string """ return _('Key press %s') % self._key_name or 'a key' class KeyReleaseAction(AtomicAction): """ A key release step. Emulates a keyboard key release. """ def __init__(self, key_code=None, key_name=None, delta_time=0): """ Initialize L{KeyReleaseAction}. Could use either a hardware keycode, a key name, or both. @param delta_time: Time to wait before performing this step. @type delta_time: integer @param key_code: Hardware keycode. @type key_code: integer. @param key_name: Key name. @type key_name: string. """ if (None, None) == (key_name, key_code): raise TypeError("Need either a key code or a key name") if delta_time > release_max: delta_time = release_max self._key_name = key_name if key_code is None: self.key_op = KeyboardOp() key_vals = self.key_op.get_keyval_id(key_name) if not key_vals: return for key_val in key_vals: AtomicAction.__init__(self, delta_time, self._keyRelease, key_val.value) else: AtomicAction.__init__(self, delta_time, self._keyRelease, key_code) def _keyRelease(self, key_code): """ Perform actual key release. @param key_code: Hardware key code. @type key_code: integer """ pyatspi.Registry.generateKeyboardEvent(key_code, None, pyatspi.KEY_RELEASE) def __str__(self): """ String representation of instance. @return: String representation of instance. @rtype: string """ return _('Key release %s') % self._key_name or 'a key' # A bit smarter about common interactions. keystroke_interval = 10 mod_key_code_mappings = { 'GDK_CONTROL_MASK' : 37, 'GDK_MOD1_MASK' : 64, 'GDK_LOCK_MASK' : 66, 'GDK_SHIFT_MASK' : 50 } class KeyComboAction(AtomicAction): """ Key combo action. Performs a press and release of a single or key combination. @ivar _key_combo: Name of key combination or single key press-release. @type _key_combo: string """ def __init__(self, key_combo, delta_time=0): """ Initialize L{KeyComboAction}. @param key_combo: Name of key combination or single key press-release. @type key_combo: string @param delta_time: Time to wait before performing step. @type delta_time: integer """ self.key_op = KeyboardOp() key_vals = self.key_op.get_keyval_id(key_combo) if not key_vals: return self._key_combo = key_vals if delta_time < min_delta: delta_time = min_delta AtomicAction.__init__(self, delta_time, self._doCombo) def __call__(self): """ Perform step. Overridden to omit L{SequenceStep.stepDone}. """ self._func(*self._args) def _doCombo(self): """ Perform combo operation. @param key_code: Key code to press. @type key_code: integer @param modifiers: Modifier mask to press with. @type modifiers: integer """ interval = 0 for key_val in self._key_combo: if key_val.non_print_key == True: _type = pyatspi.KEY_PRESS else: _type = pyatspi.KEY_PRESSRELEASE if key_val.shift: # press shift pyatspi.Registry.generateKeyboardEvent(50, None, pyatspi.KEY_PRESS) if key_val.capslck: # press / release capslck pyatspi.Registry.generateKeyboardEvent(66, None, pyatspi.KEY_PRESSRELEASE) time.sleep(0.01) pyatspi.Registry.generateKeyboardEvent(key_val.value, None, _type) if key_val.shift: # release shift pyatspi.Registry.generateKeyboardEvent(50, None, pyatspi.KEY_RELEASE) if key_val.capslck: # press / release capslck pyatspi.Registry.generateKeyboardEvent(66, None, pyatspi.KEY_PRESSRELEASE) if key_val.non_print_key == False: # If NOT found non_print_key, then release all # non_print_key index = self._key_combo.index(key_val) while index >= 0: index -= 1 if index == -1: break tmp_key_val = self._key_combo[index] # EX: - Here release both alt and tab # m - if tmp_key_val.non_print_key == False: break # Release all non_print_key pyatspi.Registry.generateKeyboardEvent(tmp_key_val.value, None, pyatspi.KEY_RELEASE) index = self._key_combo.index(key_val) if key_val.non_print_key == False: index -= 1 while index >= 0: tmp_key_val = self._key_combo[index] index -= 1 # EX: - Here release both alt and tab if tmp_key_val.non_print_key == False: break # Release all non_print_key pyatspi.Registry.generateKeyboardEvent(tmp_key_val.value, None, pyatspi.KEY_RELEASE) gobject.timeout_add(interval, self.stepDone) def _keyPress(self, hw_code): """ Press modifier key. @param hw_code: Hardware code for key. @type hw_code: integer """ pyatspi.Registry.generateKeyboardEvent(hw_code, None, pyatspi.KEY_PRESS) return False def _keyRelease(self, hw_code): """ Release modifier key. @param hw_code: Hardware code for key. @type hw_code: integer """ pyatspi.Registry.generateKeyboardEvent(hw_code, None, pyatspi.KEY_RELEASE) return False def _keyPressRelease(self, keyval): """ Press and release non-modifier key. @param key_code: Key code. @type key_code: integer """ pyatspi.Registry.generateKeyboardEvent(keyval, None, pyatspi.KEY_PRESSRELEASE) return False def __str__(self): """ String representation of instance. @return: String representation of instance. @rtype: string """ return _('Press %s') % self._key_combo if __name__ == "__main__": type_action = KeyComboAction("Hello world!") type_action() try: gobject.MainLoop().run() except KeyboardInterrupt: pass