mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-13 12:19:25 +00:00
445 lines
14 KiB
Python
445 lines
14 KiB
Python
"""
|
|
Keypress actions.
|
|
|
|
@author: Eitan Isaacson <eitan@ascender.com>
|
|
@author: Nagappan Alagappan <nagappan@gmail.com>
|
|
@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,
|
|
"ctrlr" : 105, "prtscrn" : 107, "ctrll" : 37}
|
|
|
|
def _get_keyboard_keycodes():
|
|
result = subprocess.run('xmodmap -pke', stdout = subprocess.PIPE,
|
|
stderr = subprocess.PIPE,
|
|
shell = True, close_fds = True)
|
|
if (result.returncode == 0 and result.stdout != ""):
|
|
output = result.stdout.decode("utf-8", "strict")
|
|
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: <alt><tab> - Here release both alt and tab
|
|
# <alt>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: <alt><tab> - 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
|