Source code for autokey.scripting.keyboard

# Copyright (C) 2011 Chris Dekter
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
"""Keyboard Functions"""

import typing

import autokey.model.phrase
import autokey.iomediator.waiter
from autokey import iomediator, model
from typing import Callable

[docs]class Keyboard: """ Provides access to the keyboard for event generation. """ SendMode = autokey.model.phrase.SendMode def __init__(self, mediator): """Initialize the Keyboard""" self.mediator = mediator # type: iomediator.IoMediator """See C{IoMediator} documentation"""
[docs] def send_keys(self, key_string, send_mode: typing.Union[ autokey.model.phrase.SendMode, int] = autokey.model.phrase.SendMode.KEYBOARD): """ Send a sequence of keys via keyboard events as the default or via clipboard pasting. Because the clipboard can only contain printable characters, special keys and embedded key combinations can only be sent in keyboard mode. Trying to send special keys using a clipboard pasting method will paste the literal representation (e.g. "<ctrl>+<f11>") instead of the actual special key or key combination. Usage: C{keyboard.send_keys(keyString)} @param key_string: string of keys to send. Special keys are only possible in keyboard mode. @param send_mode: Determines how the string is sent. """ if not isinstance(key_string, str): raise TypeError("Only strings can be sent using this function") send_mode = _validate_send_mode(send_mode) self.mediator.interface.begin_send() try: if send_mode is autokey.model.phrase.SendMode.KEYBOARD: self.mediator.send_string(key_string) else: self.mediator.paste_string(key_string, send_mode) finally: self.mediator.interface.finish_send()
[docs] def send_key(self, key, repeat=1): """ Send a keyboard event Usage: C{keyboard.send_key(key, repeat=1)} @param key: the key to be sent (e.g. "s" or "<enter>") @param repeat: number of times to repeat the key event """ for _ in range(repeat): self.mediator.send_key(key) self.mediator.flush()
[docs] def press_key(self, key): """ Send a key down event Usage: C{keyboard.press_key(key)} The key will be treated as down until a matching release_key() is sent. @param key: the key to be pressed (e.g. "s" or "<enter>") """ self.mediator.press_key(key)
[docs] def release_key(self, key): """ Send a key up event Usage: C{keyboard.release_key(key)} If the specified key was not made down using press_key(), the event will be ignored. @param key: the key to be released (e.g. "s" or "<enter>") """ self.mediator.release_key(key)
[docs] def fake_keypress(self, key, repeat=1): """ Fake a keypress Usage: C{keyboard.fake_keypress(key, repeat=1)} Uses XTest to 'fake' a keypress. This is useful to send keypresses to some applications which won't respond to keyboard.send_key() @param key: the key to be sent (e.g. "s" or "<enter>") @param repeat: number of times to repeat the key event """ for _ in range(repeat): self.mediator.fake_keypress(key)
[docs] def wait_for_keypress(self, key, modifiers: list=None, timeOut=10.0): """ Wait for a keypress or key combination Usage: C{keyboard.wait_for_keypress(self, key, modifiers=[], timeOut=10.0)} Note: this function cannot be used to wait for modifier keys on their own @param key: the key to wait for @param modifiers: list of modifiers that should be pressed with the key @param timeOut: maximum time, in seconds, to wait for the keypress to occur """ if modifiers is None: modifiers = [] w = self.mediator.waiter(key, modifiers, None, None, None, timeOut) self.mediator.listeners.append(w) rtn = w.wait() self.mediator.listeners.remove(w) return rtn
[docs] def wait_for_keyevent(self, check: Callable[[any,str,list,str], bool], name: str = None, timeOut=10.0): """ Wait for a key event, potentially accumulating the intervening characters Usage: C{keyboard.wait_for_keypress(self, check, name=None, timeOut=10.0)} @param check: a function that returns True or False to signify we've finished waiting @param name: only one waiter can have this name. Used to prevent more threads waiting on this. @param timeOut: maximum time, in seconds, to wait for the keypress to occur Example: # Accumulate the traditional emacs C-u prefix arguments # See https://www.gnu.org/software/emacs/manual/html_node/elisp/Prefix-Command-Arguments.html def check(waiter,rawKey,modifiers,key,*args): isCtrlU = (key == 'u' and len(modifiers) == 1 and modifiers[0] == '<ctrl>') if isCtrlU: # If we get here, they've already pressed C-u at least 2x try: val = int(waiter.result) * 4 waiter.result = str(val) except ValueError: waiter.result = "16" return False elif any(m == "<ctrl>" or m == "<alt>" or m == "<meta>" or m == "<super>" or m == "<hyper>" for m in modifiers): # Some other control character is an indication we're done. if waiter.result is None or waiter.result == "": waiter.result = "4" store.set_global_value("emacs-prefix-arg", waiter.result) return True else: # accumulate as a string waiter.result = waiter.result + key return False keyboard.wait_for_keyevent(check, "emacs-prefix") """ if name is None or not any(elem.name == name for elem in self.mediator.listeners): w = self.mediator.waiter(None, None, None, check, name, timeOut) self.mediator.listeners.append(w) rtn = w.wait() self.mediator.listeners.remove(w) return rtn return False
def _validate_send_mode(send_mode): permissible_values = "\n".join("keyboard.{}".format(mode) for mode in map(str, autokey.model.phrase.SendMode)) if isinstance(send_mode, int): if send_mode in range(len(autokey.model.phrase.SendMode)): send_mode = tuple(autokey.model.phrase.SendMode)[send_mode] # type: model.SendMode else: permissible_values = "\n".join( "{}: keyboard.{}".format( number, str(constant)) for number, constant in enumerate(autokey.model.phrase.SendMode) ) raise ValueError( "send_mode out of range for index-based access. " "Permissible values are:\n{}".format(permissible_values)) elif isinstance(send_mode, str): try: send_mode = autokey.model.phrase.SendMode(send_mode) except ValueError as v: raise ValueError("Permissible values are: " + permissible_values) from v elif send_mode is None: # Selection has value None send_mode = autokey.model.phrase.SendMode.SELECTION elif not isinstance(send_mode, autokey.model.phrase.SendMode): raise TypeError("Unsupported type for send_mode parameter: {} Use one of: {}".format(send_mode, permissible_values)) return send_mode