diff --git a/dragonfly/actions/__init__.py b/dragonfly/actions/__init__.py index abfd0610..d9710855 100644 --- a/dragonfly/actions/__init__.py +++ b/dragonfly/actions/__init__.py @@ -29,30 +29,28 @@ from .action_mimic import Mimic from .action_cmd import RunCommand from .action_context import ContextAction +from .keyboard import Keyboard, Typeable +from .typeables import typeables +from .action_key import Key +from .action_text import Text +from .action_paste import Paste -# Import Windows OS dependent classes only for Windows if sys.platform.startswith("win"): - from .action_key import Key - from .action_text import Text + # Import Windows only classes and functions. from .action_mouse import Mouse - from .action_paste import Paste from .action_waitwindow import WaitWindow from .action_focuswindow import FocusWindow from .action_startapp import StartApp, BringApp from .action_playsound import PlaySound - from .keyboard import Typeable, Keyboard - from .typeables import typeables from .sendinput import (KeyboardInput, MouseInput, HardwareInput, make_input_array, send_input_array) else: - from ..os_dependent_mock import Key - from ..os_dependent_mock import Text + # Import mocked classes and functions for other platforms. from ..os_dependent_mock import Mouse - from ..os_dependent_mock import Paste from ..os_dependent_mock import WaitWindow from ..os_dependent_mock import FocusWindow from ..os_dependent_mock import StartApp, BringApp from ..os_dependent_mock import PlaySound - from ..os_dependent_mock import (Typeable, Keyboard, typeables, KeyboardInput, - MouseInput, HardwareInput, make_input_array, + from ..os_dependent_mock import (KeyboardInput, MouseInput, + HardwareInput, make_input_array, send_input_array) diff --git a/dragonfly/actions/_generate_typeables.py b/dragonfly/actions/_generate_typeables.py index 921b8a7d..4fd9a1ae 100644 --- a/dragonfly/actions/_generate_typeables.py +++ b/dragonfly/actions/_generate_typeables.py @@ -127,91 +127,91 @@ (lookup, "?", "? question"), (lookup, "=", "= equal equals")]), ("Whitespace and editing keys", 1, [ - (vkey, "win32con.VK_RETURN", "enter"), - (vkey, "win32con.VK_TAB", "tab"), - (vkey, "win32con.VK_SPACE", "space"), - (vkey, "win32con.VK_BACK", "backspace"), - (vkey, "win32con.VK_DELETE", "delete del")]), + (vkey, "key_symbols.RETURN", "enter"), + (vkey, "key_symbols.TAB", "tab"), + (vkey, "key_symbols.SPACE", "space"), + (vkey, "key_symbols.BACK", "backspace"), + (vkey, "key_symbols.DELETE", "delete del")]), ("Main modifier keys", 1, [ - (vkey, "win32con.VK_SHIFT", "shift"), - (vkey, "win32con.VK_CONTROL", "control ctrl"), - (vkey, "win32con.VK_MENU", "alt")]), + (vkey, "key_symbols.SHIFT", "shift"), + (vkey, "key_symbols.CONTROL", "control ctrl"), + (vkey, "key_symbols.ALT", "alt")]), ("Right modifier keys", 1, [ - (vkey, "win32con.VK_RSHIFT", "rshift"), - (vkey, "win32con.VK_RCONTROL", "rcontrol rctrl"), - (vkey, "win32con.VK_RMENU", "ralt")]), + (vkey, "key_symbols.RSHIFT", "rshift"), + (vkey, "key_symbols.RCONTROL", "rcontrol rctrl"), + (vkey, "key_symbols.RALT", "ralt")]), ("Special keys", 1, [ - (vkey, "win32con.VK_ESCAPE", "escape"), - (vkey, "win32con.VK_INSERT", "insert"), - (vkey, "win32con.VK_PAUSE", "pause"), - (vkey, "win32con.VK_LWIN", "win"), - (vkey, "win32con.VK_RWIN", "rwin"), - (vkey, "win32con.VK_APPS", "apps popup"), - (vkey, "win32con.VK_SNAPSHOT", "snapshot printscreen")]), + (vkey, "key_symbols.ESCAPE", "escape"), + (vkey, "key_symbols.INSERT", "insert"), + (vkey, "key_symbols.PAUSE", "pause"), + (vkey, "key_symbols.LSUPER", "win"), + (vkey, "key_symbols.RSUPER", "rwin"), + (vkey, "key_symbols.APPS", "apps popup"), + (vkey, "key_symbols.SNAPSHOT", "snapshot printscreen")]), ("Lock keys", 1, [ - (vkey, "win32con.VK_SCROLL", "scrolllock"), - (vkey, "win32con.VK_NUMLOCK", "numlock"), - (vkey, "win32con.VK_CAPITAL", "capslock")]), + (vkey, "key_symbols.SCROLL_LOCK", "scrolllock"), + (vkey, "key_symbols.NUM_LOCK", "numlock"), + (vkey, "key_symbols.CAPS_LOCK", "capslock")]), ("Navigation keys", 1, [ - (vkey, "win32con.VK_UP", "up"), - (vkey, "win32con.VK_DOWN", "down"), - (vkey, "win32con.VK_LEFT", "left"), - (vkey, "win32con.VK_RIGHT", "right"), - (vkey, "win32con.VK_PRIOR", "pageup pgup"), - (vkey, "win32con.VK_NEXT", "pagedown pgdown"), - (vkey, "win32con.VK_HOME", "home"), - (vkey, "win32con.VK_END", "end")]), + (vkey, "key_symbols.UP", "up"), + (vkey, "key_symbols.DOWN", "down"), + (vkey, "key_symbols.LEFT", "left"), + (vkey, "key_symbols.RIGHT", "right"), + (vkey, "key_symbols.PAGE_UP", "pageup pgup"), + (vkey, "key_symbols.PAGE_DOWN", "pagedown pgdown"), + (vkey, "key_symbols.HOME", "home"), + (vkey, "key_symbols.END", "end")]), ("Number pad keys", 1, [ - (vkey, "win32con.VK_MULTIPLY", "npmul"), - (vkey, "win32con.VK_ADD", "npadd"), - (vkey, "win32con.VK_SEPARATOR", "npsep"), - (vkey, "win32con.VK_SUBTRACT", "npsub"), - (vkey, "win32con.VK_DECIMAL", "npdec"), - (vkey, "win32con.VK_DIVIDE", "npdiv"), - (vkey, "win32con.VK_NUMPAD0", "numpad0 np0"), - (vkey, "win32con.VK_NUMPAD1", "numpad1 np1"), - (vkey, "win32con.VK_NUMPAD2", "numpad2 np2"), - (vkey, "win32con.VK_NUMPAD3", "numpad3 np3"), - (vkey, "win32con.VK_NUMPAD4", "numpad4 np4"), - (vkey, "win32con.VK_NUMPAD5", "numpad5 np5"), - (vkey, "win32con.VK_NUMPAD6", "numpad6 np6"), - (vkey, "win32con.VK_NUMPAD7", "numpad7 np7"), - (vkey, "win32con.VK_NUMPAD8", "numpad8 np8"), - (vkey, "win32con.VK_NUMPAD9", "numpad9 np9")]), + (vkey, "key_symbols.MULTIPLY", "npmul"), + (vkey, "key_symbols.ADD", "npadd"), + (vkey, "key_symbols.SEPARATOR", "npsep"), + (vkey, "key_symbols.SUBTRACT", "npsub"), + (vkey, "key_symbols.DECIMAL", "npdec"), + (vkey, "key_symbols.DIVIDE", "npdiv"), + (vkey, "key_symbols.NUMPAD0", "numpad0 np0"), + (vkey, "key_symbols.NUMPAD1", "numpad1 np1"), + (vkey, "key_symbols.NUMPAD2", "numpad2 np2"), + (vkey, "key_symbols.NUMPAD3", "numpad3 np3"), + (vkey, "key_symbols.NUMPAD4", "numpad4 np4"), + (vkey, "key_symbols.NUMPAD5", "numpad5 np5"), + (vkey, "key_symbols.NUMPAD6", "numpad6 np6"), + (vkey, "key_symbols.NUMPAD7", "numpad7 np7"), + (vkey, "key_symbols.NUMPAD8", "numpad8 np8"), + (vkey, "key_symbols.NUMPAD9", "numpad9 np9")]), ("Function keys", 1, [ - (vkey, "win32con.VK_F1", "f1"), - (vkey, "win32con.VK_F2", "f2"), - (vkey, "win32con.VK_F3", "f3"), - (vkey, "win32con.VK_F4", "f4"), - (vkey, "win32con.VK_F5", "f5"), - (vkey, "win32con.VK_F6", "f6"), - (vkey, "win32con.VK_F7", "f7"), - (vkey, "win32con.VK_F8", "f8"), - (vkey, "win32con.VK_F9", "f9"), - (vkey, "win32con.VK_F10", "f10"), - (vkey, "win32con.VK_F11", "f11"), - (vkey, "win32con.VK_F12", "f12"), - (vkey, "win32con.VK_F13", "f13"), - (vkey, "win32con.VK_F14", "f14"), - (vkey, "win32con.VK_F15", "f15"), - (vkey, "win32con.VK_F16", "f16"), - (vkey, "win32con.VK_F17", "f17"), - (vkey, "win32con.VK_F18", "f18"), - (vkey, "win32con.VK_F19", "f19"), - (vkey, "win32con.VK_F20", "f20"), - (vkey, "win32con.VK_F21", "f21"), - (vkey, "win32con.VK_F22", "f22"), - (vkey, "win32con.VK_F23", "f23"), - (vkey, "win32con.VK_F24", "f24")]), + (vkey, "key_symbols.F1", "f1"), + (vkey, "key_symbols.F2", "f2"), + (vkey, "key_symbols.F3", "f3"), + (vkey, "key_symbols.F4", "f4"), + (vkey, "key_symbols.F5", "f5"), + (vkey, "key_symbols.F6", "f6"), + (vkey, "key_symbols.F7", "f7"), + (vkey, "key_symbols.F8", "f8"), + (vkey, "key_symbols.F9", "f9"), + (vkey, "key_symbols.F10", "f10"), + (vkey, "key_symbols.F11", "f11"), + (vkey, "key_symbols.F12", "f12"), + (vkey, "key_symbols.F13", "f13"), + (vkey, "key_symbols.F14", "f14"), + (vkey, "key_symbols.F15", "f15"), + (vkey, "key_symbols.F16", "f16"), + (vkey, "key_symbols.F17", "f17"), + (vkey, "key_symbols.F18", "f18"), + (vkey, "key_symbols.F19", "f19"), + (vkey, "key_symbols.F20", "f20"), + (vkey, "key_symbols.F21", "f21"), + (vkey, "key_symbols.F22", "f22"), + (vkey, "key_symbols.F23", "f23"), + (vkey, "key_symbols.F24", "f24")]), ("Multimedia keys", 1, [ - (vkey, "win32con.VK_VOLUME_UP", "volumeup volup"), - (vkey, "win32con.VK_VOLUME_DOWN", "volumedown voldown"), - (vkey, "win32con.VK_VOLUME_MUTE", "volumemute volmute"), - (vkey, "win32con.VK_MEDIA_NEXT_TRACK", "tracknext"), - (vkey, "win32con.VK_MEDIA_PREV_TRACK", "trackprev"), - (vkey, "win32con.VK_MEDIA_PLAY_PAUSE", "playpause"), - (vkey, "win32con.VK_BROWSER_BACK", "browserback"), - (vkey, "win32con.VK_BROWSER_FORWARD", "browserforward")]), + (vkey, "key_symbols.VOLUME_UP", "volumeup volup"), + (vkey, "key_symbols.VOLUME_DOWN", "volumedown voldown"), + (vkey, "key_symbols.VOLUME_MUTE", "volumemute volmute"), + (vkey, "key_symbols.MEDIA_NEXT_TRACK", "tracknext"), + (vkey, "key_symbols.MEDIA_PREV_TRACK", "trackprev"), + (vkey, "key_symbols.MEDIA_PLAY_PAUSE", "playpause"), + (vkey, "key_symbols.BROWSER_BACK", "browserback"), + (vkey, "key_symbols.BROWSER_FORWARD", "browserforward")]), ) diff --git a/dragonfly/actions/_test_x11_text_key.py b/dragonfly/actions/_test_x11_text_key.py new file mode 100644 index 00000000..3bc7cfbe --- /dev/null +++ b/dragonfly/actions/_test_x11_text_key.py @@ -0,0 +1,127 @@ +#!/usr/bin/python + +""" +Script to test the Key and Text action on X11 using the "xev" program. + +There should be two lines printed for each key press: a down event and +an up event. Each line will contain the key symbol (e.g. Return). +There will be a small delay per key press for both test actions to help view +the output from "xev" as the keys are pressed. + +X11Errors may be logged for keys remapped or not mapped by xkb. +""" + +from __future__ import print_function + +import subprocess +import sys +import threading +import time + +from dragonfly import Key, Text + +XEV_PATH = "/usr/bin/xev" + + +def main(): + # Define keys to type. + keys = [] + alphas = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + digits = "0123456789" + keys.extend(alphas) + keys.extend(digits) + + # Add common symbol characters. Use long names for reserved characters. + symbols = "!@#%^&*()_+`~[]{}|\\;'\".<>?=" + keys.extend(symbols) + keys.extend(["comma", "colon", "minus", "slash", "space"]) + + # Add virtual keys. Some keys are repeated purposefully to maintain key + # state (e.g. caps lock). + keys.extend([ + # Whitespace and editing keys + "enter", "tab", "space", "backspace", "delete", + + # Special keys + "escape", "insert", "insert", "pause", "apps", + + # Lock keys + "scrolllock", "scrolllock", "numlock", "numlock", "capslock", + "capslock", + + # Navigation keys + "up", "down", "left", "right", "pageup", "pagedown", "home", "end", + + # Number pad keys (except npsep) + "npmul", "npadd", "npsub", "npdec", "npdiv", "np0", "np1", + "np2", "np3", "np4", "np5", "np6", "np7", "np8", "np9", + + # Function keys (not including F13-24) + "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "f10", "f11", + "f11", "f12", + + # Multimedia keys (press toggle keys twice) + "volumeup", "volup", "volumedown", "voldown", "volumemute", + "volmute", "tracknext", "trackprev", "playpause", "playpause", + "browserback", "browserforward", + + # Modifiers. + "shift:down", "shift:up", + "ctrl:down", "ctrl:up", + "alt:down", "alt:up", + "rshift:down", "rshift:up", + "rctrl:down", "rctrl:up", + "ralt:down", "ralt:up", + "win:down", "win:up", "win:down", "win:up", + "rwin:down", "rwin:up", "rwin:down", "rwin:up", + + # Some common key combinations. + "s-insert", + "c-left", + "c-home", + "a-f", + "c-a", + ]) + + # Define text to type using all alphanumeric characters and valid + # symbols. + text = alphas + digits + symbols + ",:-/ \n\t" + + # Start xev and selectively print lines from it in the background. + # Exit if the command fails. + try: + proc = subprocess.Popen(XEV_PATH, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + stdin=subprocess.PIPE) + def print_key_events(): + for line in iter(proc.stdout.readline, b''): + line = line.decode() + if "keysym" in line: + print(line.strip()) + + t = threading.Thread(target=print_key_events) + t.setDaemon(True) # make this thread terminate with the main thread + t.start() + except Exception as e: + print("Couldn't start xev: %s" % e, file=sys.stderr) + exit(1) + + # Wait a few seconds before beginning. + print("Please ensure xev is focused...") + time.sleep(5) + + print("--------------------Starting Key tests--------------------") + Key("/10,".join(keys)).execute() + print("Done.") + + print("--------------------Starting Text tests-------------------") + Text(text, pause=0.1).execute() + print("Done.") + + print("Stopping xev.") + proc.terminate() + + +if __name__ == '__main__': + main() diff --git a/dragonfly/actions/action_key.py b/dragonfly/actions/action_key.py index 80c02d55..28446ceb 100644 --- a/dragonfly/actions/action_key.py +++ b/dragonfly/actions/action_key.py @@ -26,8 +26,8 @@ This section describes the :class:`Key` action object. This type of action is used for sending keystrokes to the foreground -application. Examples of how to use this class are given in -:ref:`RefKeySpecExamples`. +application. This works on Windows, Mac OS and with X11 (e.g. on Linux). +Examples of how to use this class are given in :ref:`RefKeySpecExamples`. .. _RefKeySpec: @@ -206,8 +206,10 @@ class Key(DynStrActionBase): This class emulates keyboard activity by sending keystrokes to the foreground application. It does this using Dragonfly's keyboard - interface implemented in the :mod:`keyboard` and :mod:`sendinput` - modules. These use the ``sendinput()`` function of the Win32 API. + interface for the current platform. The implementation for Windows + uses the ``sendinput()`` Win32 API function. The implementations + for X11 and Mac OS use + `pynput `__. """ @@ -292,12 +294,10 @@ def _parse_single(self, spec): else: raise ActionError("Invalid key spec: %s" % spec) - try: - code = typeables[keyname] - except KeyError: + code = typeables.get(keyname) + if code is None: raise ActionError("Invalid key name: %r" % keyname) - if inner_pause is not None: s = inner_pause try: diff --git a/dragonfly/actions/action_paste.py b/dragonfly/actions/action_paste.py index c6e2e801..ca44f820 100644 --- a/dragonfly/actions/action_paste.py +++ b/dragonfly/actions/action_paste.py @@ -24,15 +24,22 @@ """ +import sys + from six import text_type, string_types, PY2 from ..actions.action_base import DynStrActionBase from ..actions.action_key import Key -from ..windows.clipboard import Clipboard -from win32con import CF_UNICODETEXT, CF_TEXT -import pywintypes +if sys.platform.startswith("win"): + from ..windows.clipboard import Clipboard +else: + from ..util import Clipboard + +# Define some win32 constants so that this module can work on other +# platforms. +CF_UNICODETEXT, CF_TEXT = 13, 1 #--------------------------------------------------------------------------- @@ -54,20 +61,28 @@ class Paste(DynStrActionBase): This action inserts the given *contents* into the Windows system clipboard, and then performs the *paste* action to paste it into the foreground application. By default, the *paste* action is the - :kbd:`Shift-insert` keystroke. The default clipboard format to use - is the *Unicode* text format. + :kbd:`Ctrl-v` keystroke or :kbd`Super-v` on a mac. The default + clipboard format to use is the *Unicode* text format. + + Clipboard formats are not used if not running on Windows. """ _default_format = CF_UNICODETEXT # Default paste action. - _default_paste = Key("s-insert/20") + # Fallback on Shift-insert if 'v' isn't available. Use Super-v on macs. + try: + _default_paste = (Key("w-v/20") if sys.platform == "darwin" + else Key("c-v/20")) + except: + print("Falling back to Shift+insert for Paste's action.") + _default_paste = Key("s-insert/20") def __init__(self, contents, format=None, paste=None, static=False): if not format: format = self._default_format - if not paste: + if paste is None: paste = self._default_paste if isinstance(contents, string_types): spec = contents @@ -89,13 +104,16 @@ def _execute_events(self, events): original = Clipboard() try: original.copy_from_system() - except pywintypes.error as e: + except Exception as e: self._log.warning("Failed to store original clipboard contents:" - " %s" % e) + " %s", e) if (self.format == CF_UNICODETEXT and not isinstance(events, text_type)): if PY2: - events = text_type(events, encoding='windows-1252', + # Use a Unicode object with the correct encoding. + on_windows = sys.platform.startswith("win32") + encoding = 'windows-1252' if on_windows else 'utf-8' + events = text_type(events, encoding=encoding, errors='ignore') else: events = text_type(events) diff --git a/dragonfly/actions/action_text.py b/dragonfly/actions/action_text.py index df399f50..cdf90ff6 100644 --- a/dragonfly/actions/action_text.py +++ b/dragonfly/actions/action_text.py @@ -23,7 +23,8 @@ ============================================================================ This section describes the :class:`Text` action object. This type of -action is used for typing text into the foreground application. +action is used for typing text into the foreground application. This works +on Windows, Mac OS and with X11 (e.g. on Linux). It differs from the :class:`Key` action in that :class:`Text` is used for typing literal text, while :class:`dragonfly.actions.action_key.Key` @@ -62,18 +63,21 @@ ``hardware_apps`` list in the configuration file mentioned above to make dragonfly always use hardware emulation for them. +These settings and parameters have no effect on other platforms. + Text class reference ............................................................................ """ +import sys from six import text_type from ..engines import get_engine -from ..windows.clipboard import Clipboard -from ..windows.window import Window +from ..util.clipboard import Clipboard +from ..windows import Window from .action_base import ActionError, DynStrActionBase from .action_key import Key from .keyboard import Keyboard @@ -204,7 +208,7 @@ def _parse_spec(self, spec): else: # Add hardware events. try: - typeable = Keyboard.get_typeable(character) + typeable = self._keyboard.get_typeable(character) hardware_events.extend(typeable.events(self._pause)) except ValueError: hardware_error_message = ("Keyboard interface cannot type this" @@ -215,8 +219,8 @@ def _parse_spec(self, spec): for short in unpack("<" + str(len(byte_stream) // 2) + "H", byte_stream): try: - typeable = Keyboard.get_typeable(short, - is_text=True) + typeable = self._keyboard.get_typeable(short, + is_text=True) unicode_events.extend(typeable.events(self._pause * 0.5)) except ValueError: unicode_error_message = ("Keyboard interface cannot type " @@ -264,7 +268,13 @@ def _execute_events(self, events): events = self._parse_spec(prefix + text + suffix) # Send keyboard events. - if self._use_hardware or require_hardware_emulation(): + use_hardware_events = ( + self._use_hardware or require_hardware_emulation() or + + # Always use hardware_events for non-Windows platforms. + not sys.platform.startswith("win") + ) + if use_hardware_events: error_message = events.hardware_error_message keyboard_events = events.hardware_events else: diff --git a/dragonfly/actions/actions.py b/dragonfly/actions/actions.py index a9885b97..d99de622 100644 --- a/dragonfly/actions/actions.py +++ b/dragonfly/actions/actions.py @@ -24,7 +24,6 @@ """ import sys -# Import OS-agnostic classes from .action_pause import Pause from .action_function import Function from .action_playback import Playback @@ -33,25 +32,22 @@ from .action_mimic import Mimic from .action_cmd import RunCommand from .action_context import ContextAction +from .keyboard import Keyboard, Typeable +from .action_key import Key +from .action_text import Text +from .action_paste import Paste -# Import Windows OS dependent classes only for Windows if sys.platform.startswith("win"): - from .action_key import Key - from .action_text import Text + # Import Windows only classes and functions. from .action_mouse import Mouse - from .action_paste import Paste from .action_waitwindow import WaitWindow from .action_focuswindow import FocusWindow from .action_startapp import StartApp, BringApp from .action_playsound import PlaySound - from .keyboard import Keyboard, Typeable else: - from ..os_dependent_mock import Key - from ..os_dependent_mock import Text + # Import mocked classes and functions for other platforms. from ..os_dependent_mock import Mouse - from ..os_dependent_mock import Paste from ..os_dependent_mock import WaitWindow from ..os_dependent_mock import FocusWindow from ..os_dependent_mock import StartApp, BringApp from ..os_dependent_mock import PlaySound - from ..os_dependent_mock import Keyboard, Typeable diff --git a/dragonfly/actions/keyboard/__init__.py b/dragonfly/actions/keyboard/__init__.py new file mode 100644 index 00000000..f2aba5a0 --- /dev/null +++ b/dragonfly/actions/keyboard/__init__.py @@ -0,0 +1,58 @@ +# +# This file is part of Dragonfly. +# (c) Copyright 2007, 2008 by Christo Butcher +# Licensed under the LGPL. +# +# Dragonfly is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Dragonfly 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with Dragonfly. If not, see +# . +# + +""" +This module initializes the correct keyboard interface for the current +platform. +""" + +import logging +import os +import sys + +_logger = logging.getLogger("keyboard") + +# TODO Implement classes for Wayland (XDG_SESSION_TYPE == "wayland"). + +# Import the Keyboard, KeySymbols and Typeable classes for the current +# platform. +if sys.platform.startswith("win"): + # Import classes for Windows. + from ._win32 import Keyboard, Typeable, Win32KeySymbols as KeySymbols +elif sys.platform == "darwin": + # Import classes for Mac OS. + from ._pynput import Keyboard, Typeable, DarwinKeySymbols as KeySymbols +elif os.environ.get("XDG_SESSION_TYPE") == "x11": + # Import classes for X11 (typically used on Linux systems). + # The XDG_SESSION_TYPE environment variable may not be set in some + # circumstances, in which case it can be set manually in ~/.profile. + from ._pynput import Keyboard, Typeable, X11KeySymbols as KeySymbols +else: + from ._base import (BaseKeyboard as Keyboard, Typeable, + MockKeySymbols as KeySymbols) + + # Warn that no keyboard implementation is available. + # Don't raise an error because this will break continuous integration + # tests. Most of dragonfly can still be used anyway. + _logger.warning("There is no keyboard implementation for this " + "platform!") + +# Initialize a Keyboard instance. +keyboard = Keyboard() diff --git a/dragonfly/actions/keyboard/_base.py b/dragonfly/actions/keyboard/_base.py new file mode 100644 index 00000000..27e14468 --- /dev/null +++ b/dragonfly/actions/keyboard/_base.py @@ -0,0 +1,91 @@ +# +# This file is part of Dragonfly. +# (c) Copyright 2007, 2008 by Christo Butcher +# Licensed under the LGPL. +# +# Dragonfly is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Dragonfly 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with Dragonfly. If not, see +# . +# + +""" This file defines the base keyboard interface and Typeables class. """ + + +class MockKeySymbols(object): + def __getattribute__(self, _): + # Always return -1 because no keys can be typed. + return -1 + + +class BaseKeyboard(object): + """ Base keyboard interface. """ + + @classmethod + def send_keyboard_events(cls, events): + """ Send a sequence of keyboard events. """ + raise NotImplementedError("Keyboard support is not implemented for " + "this platform!") + + @classmethod + def get_typeable(cls, char, is_text=False): + """ Get a Typeable object. """ + return Typeable(cls, char, is_text=is_text) + + +class Typeable(object): + """Container for keypress events.""" + + __slots__ = ("_code", "_modifiers", "_name", "_is_text") + + def __init__(self, code, modifiers=(), name=None, is_text=False): + """Set keypress information.""" + self._code = code + self._modifiers = modifiers + self._name = name + self._is_text = is_text + + def __str__(self): + """Return information useful for debugging.""" + return ("%s(%s)" % (self.__class__.__name__, self._name) + + repr(self.events())) + + def on_events(self, timeout=0): + """Return events for pressing this key down.""" + if self._is_text: + events = [(self._code, True, timeout, True)] + else: + events = [(m, True, 0) for m in self._modifiers] + events.append((self._code, True, timeout)) + return events + + def off_events(self, timeout=0): + """Return events for releasing this key.""" + if self._is_text: + events = [(self._code, False, timeout, True)] + else: + events = [(m, False, 0) for m in self._modifiers] + events.append((self._code, False, timeout)) + events.reverse() + return events + + def events(self, timeout=0): + """Return events for pressing and then releasing this key.""" + if self._is_text: + events = [(self._code, True, timeout, True), + (self._code, False, timeout, True)] + else: + events = [(self._code, True, 0), (self._code, False, timeout)] + for m in self._modifiers[-1::-1]: + events.insert(0, (m, True, 0)) + events.append((m, False, 0)) + return events diff --git a/dragonfly/actions/keyboard/_pynput.py b/dragonfly/actions/keyboard/_pynput.py new file mode 100644 index 00000000..69c198df --- /dev/null +++ b/dragonfly/actions/keyboard/_pynput.py @@ -0,0 +1,277 @@ +# +# This file is part of Dragonfly. +# (c) Copyright 2007, 2008 by Christo Butcher +# Licensed under the LGPL. +# +# Dragonfly is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Dragonfly 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with Dragonfly. If not, see +# . +# + +""" +This file implements the a keyboard interface using the *pynput* Python +package. This implementation is used for Linux (X11) and Mac OS (Darwin). + +""" + +import logging +import sys +import time + +from pynput.keyboard import Controller, KeyCode, Key + +from ._base import BaseKeyboard, Typeable as BaseTypeable + + +class Typeable(BaseTypeable): + """ Typeable class for pynput. """ + _log = logging.getLogger("keyboard") + + def __init__(self, code, modifiers=(), name=None, is_text=False): + # Warn about unsupported keys. + if isinstance(code, KeyCode) and code.vk == -1: + self._log.warning("Unknown key '%s' cannot be typed with " + "pynput on %s", code.char, sys.platform) + BaseTypeable.__init__(self, code, modifiers, name, is_text) + + +class SafeKeyCode(object): + """ + Class to safely get key codes from pynput. + """ + + def __getattr__(self, name): + # Get the key code from pynput, returning KeyCode(vk=-1, char=name) + # if the key name isn't present. + # Keys are undefined on some platforms, e.g. "pause" on Darwin. + return getattr(Key, name, KeyCode(vk=-1, char=name)) + + +virtual_keys = SafeKeyCode() + + +class BaseKeySymbols(object): + """ Base key symbols for pynput. """ + + # Whitespace and editing keys + RETURN = virtual_keys.enter + TAB = virtual_keys.tab + SPACE = virtual_keys.space + BACK = virtual_keys.backspace + DELETE = virtual_keys.delete + + # Main modifier keys + SHIFT = virtual_keys.shift + CONTROL = virtual_keys.ctrl + ALT = virtual_keys.alt + + # Right modifier keys + RSHIFT = virtual_keys.shift_r + RCONTROL = virtual_keys.ctrl_r + RALT = virtual_keys.alt_r + + # Special keys + ESCAPE = virtual_keys.esc + INSERT = virtual_keys.insert + PAUSE = virtual_keys.pause + LSUPER = virtual_keys.cmd_l + RSUPER = virtual_keys.cmd_r + APPS = virtual_keys.menu + SNAPSHOT = virtual_keys.print_screen + + # Lock keys + SCROLL_LOCK = virtual_keys.scroll_lock + NUM_LOCK = virtual_keys.num_lock + CAPS_LOCK = virtual_keys.caps_lock + + # Navigation keys + UP = virtual_keys.up + DOWN = virtual_keys.down + LEFT = virtual_keys.left + RIGHT = virtual_keys.right + PAGE_UP = virtual_keys.page_up + PAGE_DOWN = virtual_keys.page_down + HOME = virtual_keys.home + END = virtual_keys.end + + # Number pad keys + # pynput currently only exposes these for Windows, so we'll map them to + # equivalent characters and numbers instead. + MULTIPLY = KeyCode(char="*") + ADD = KeyCode(char="+") + SEPARATOR = KeyCode(char=".") # this is locale-dependent. + SUBTRACT = KeyCode(char="-") + DECIMAL = KeyCode(char=".") + DIVIDE = KeyCode(char="/") + NUMPAD0 = KeyCode(char="0") + NUMPAD1 = KeyCode(char="1") + NUMPAD2 = KeyCode(char="2") + NUMPAD3 = KeyCode(char="3") + NUMPAD4 = KeyCode(char="4") + NUMPAD5 = KeyCode(char="5") + NUMPAD6 = KeyCode(char="6") + NUMPAD7 = KeyCode(char="7") + NUMPAD8 = KeyCode(char="8") + NUMPAD9 = KeyCode(char="9") + + # Function keys + # F13-20 don't work on X11 with pynput because they are not usually + # part of the keyboard map. + F1 = virtual_keys.f1 + F2 = virtual_keys.f2 + F3 = virtual_keys.f3 + F4 = virtual_keys.f4 + F5 = virtual_keys.f5 + F6 = virtual_keys.f6 + F7 = virtual_keys.f7 + F8 = virtual_keys.f8 + F9 = virtual_keys.f9 + F10 = virtual_keys.f10 + F11 = virtual_keys.f11 + F12 = virtual_keys.f12 + F13 = virtual_keys.f13 + F14 = virtual_keys.f14 + F15 = virtual_keys.f15 + F16 = virtual_keys.f16 + F17 = virtual_keys.f17 + F18 = virtual_keys.f18 + F19 = virtual_keys.f19 + F20 = virtual_keys.f20 + + +class X11KeySymbols(BaseKeySymbols): + """ + Symbols for X11 from pynput. + + This class includes extra symbols matching those that dragonfly's Win32 + keyboard interface provides. + """ + + # Number pad keys + # Retrieved from /usr/include/X11/keysymdef.h on Debian 9. + MULTIPLY = KeyCode.from_vk(0xffaa) + ADD = KeyCode.from_vk(0xffab) + SEPARATOR = KeyCode.from_vk(0xffac) + SUBTRACT = KeyCode.from_vk(0xffad) + DECIMAL = KeyCode.from_vk(0xffae) + DIVIDE = KeyCode.from_vk(0xffaf) + NUMPAD0 = KeyCode.from_vk(0xffb0) + NUMPAD1 = KeyCode.from_vk(0xffb1) + NUMPAD2 = KeyCode.from_vk(0xffb2) + NUMPAD3 = KeyCode.from_vk(0xffb3) + NUMPAD4 = KeyCode.from_vk(0xffb4) + NUMPAD5 = KeyCode.from_vk(0xffb5) + NUMPAD6 = KeyCode.from_vk(0xffb6) + NUMPAD7 = KeyCode.from_vk(0xffb7) + NUMPAD8 = KeyCode.from_vk(0xffb8) + NUMPAD9 = KeyCode.from_vk(0xffb9) + + # Function keys F21-F24. + # Retrieved from /usr/include/X11/keysymdef.h on Debian 9. + # These keys don't work on X11 with pynput because they are not usually + # part of the keyboard map. They are set here to avoid some warnings + # and because the Windows keyboard supports them. + F21 = KeyCode.from_vk(0xffd1) + F22 = KeyCode.from_vk(0xffd2) + F23 = KeyCode.from_vk(0xffd3) + F24 = KeyCode.from_vk(0xffd4) + + # Multimedia keys + # Retrieved from /usr/include/X11/XF86keysym.h on Debian 9. + # These should work on Debian-based distributions like Ubunutu, but + # might not work using different X11 server implementations because the + # symbols are vendor-specific. + # Any errors raised when typing these or any other keys will be caught + # and logged. + VOLUME_UP = KeyCode.from_vk(0x1008FF13) + VOLUME_DOWN = KeyCode.from_vk(0x1008FF11) + VOLUME_MUTE = KeyCode.from_vk(0x1008FF12) + MEDIA_NEXT_TRACK = KeyCode.from_vk(0x1008FF17) + MEDIA_PREV_TRACK = KeyCode.from_vk(0x1008FF16) + MEDIA_PLAY_PAUSE = KeyCode.from_vk(0x1008FF14) + BROWSER_BACK = KeyCode.from_vk(0x1008FF26) + BROWSER_FORWARD = KeyCode.from_vk(0x1008FF27) + + +class DarwinKeySymbols(BaseKeySymbols): + """ + Symbols for Darwin from pynput. + + This class includes some extra symbols to prevent errors in + typeables.py. + + All extras will be disabled (key code of -1). + """ + + # Extra function keys. + F21 = virtual_keys.f21 + F22 = virtual_keys.f22 + F23 = virtual_keys.f23 + F24 = virtual_keys.f24 + + # Multimedia keys. + VOLUME_UP = virtual_keys.volume_up + VOLUME_DOWN = virtual_keys.volume_down + VOLUME_MUTE = virtual_keys.volume_mute + MEDIA_NEXT_TRACK = virtual_keys.media_next_track + MEDIA_PREV_TRACK = virtual_keys.media_prev_track + MEDIA_PLAY_PAUSE = virtual_keys.media_play_pause + BROWSER_BACK = virtual_keys.browser_back + BROWSER_FORWARD = virtual_keys.browser_forward + + +class Keyboard(BaseKeyboard): + """Static class wrapper around pynput.keyboard.""" + + _controller = Controller() + _log = logging.getLogger("keyboard") + + @classmethod + def send_keyboard_events(cls, events): + """ + Send a sequence of keyboard events. + + Positional arguments: + events -- a sequence of tuples of the form + (keycode, down, timeout), where + keycode (str|KeyCode): pynput key code. + down (boolean): True means the key will be pressed down, + False means the key will be released. + timeout (int): number of seconds to sleep after + the keyboard event. + + """ + cls._log.debug("Keyboard.send_keyboard_events %r", events) + for event in events: + (key, down, timeout) = event + + # Raise an error if the key is unsupported. 'key' can also be a + # string, e.g. "a", "b", "/", etc, but we don't check if those + # are valid. + if isinstance(key, KeyCode) and key.vk == -1: + raise ValueError("Unsupported key: %r" % key.char) + + # Press/release the key, catching any errors. + try: + cls._controller.touch(key, down) + except Exception as e: + cls._log.exception("Failed to type key code %s: %s", + key, e) + + # Sleep after the keyboard event if necessary. + if timeout: + time.sleep(timeout) + + @classmethod + def get_typeable(cls, char, is_text=False): + return Typeable(char, is_text=is_text) diff --git a/dragonfly/actions/keyboard.py b/dragonfly/actions/keyboard/_win32.py similarity index 60% rename from dragonfly/actions/keyboard.py rename to dragonfly/actions/keyboard/_win32.py index faee5540..03974719 100644 --- a/dragonfly/actions/keyboard.py +++ b/dragonfly/actions/keyboard/_win32.py @@ -18,69 +18,118 @@ # . # -"""This file implements a Win32 keyboard interface using sendinput.""" - +"""This file implements the Win32 keyboard interface using sendinput.""" import time -from six import text_type, PY2 - import win32con from ctypes import windll, c_char, c_wchar -from dragonfly.actions.sendinput import (KeyboardInput, make_input_array, - send_input_array) - - -class Typeable(object): - """Container for keypress events.""" - - __slots__ = ("_code", "_modifiers", "_name", "_is_text") - - def __init__(self, code, modifiers=(), name=None, is_text=False): - """Set keypress information.""" - self._code = code - self._modifiers = modifiers - self._name = name - self._is_text = is_text - - def __str__(self): - """Return information useful for debugging.""" - return ("%s(%s)" % (self.__class__.__name__, self._name) + - repr(self.events())) - - def on_events(self, timeout=0): - """Return events for pressing this key down.""" - if self._is_text: - events = [(self._code, True, timeout, True)] - else: - events = [(m, True, 0) for m in self._modifiers] - events.append((self._code, True, timeout)) - return events - - def off_events(self, timeout=0): - """Return events for releasing this key.""" - if self._is_text: - events = [(self._code, False, timeout, True)] - else: - events = [(m, False, 0) for m in self._modifiers] - events.append((self._code, False, timeout)) - events.reverse() - return events - - def events(self, timeout=0): - """Return events for pressing and then releasing this key.""" - if self._is_text: - events = [(self._code, True, timeout, True), - (self._code, False, timeout, True)] - else: - events = [(self._code, True, 0), (self._code, False, timeout)] - for m in self._modifiers[-1::-1]: - events.insert(0, (m, True, 0)) - events.append((m, False, 0)) - return events - +from six import text_type, PY2 -class Keyboard(object): +from ._base import BaseKeyboard, Typeable +from ..sendinput import KeyboardInput, make_input_array, send_input_array + + +class Win32KeySymbols(object): + """ Key symbols for win32. """ + + # Whitespace and editing keys + RETURN = win32con.VK_RETURN + TAB = win32con.VK_TAB + SPACE = win32con.VK_SPACE + BACK = win32con.VK_BACK + DELETE = win32con.VK_DELETE + + # Main modifier keys + SHIFT = win32con.VK_SHIFT + CONTROL = win32con.VK_CONTROL + ALT = win32con.VK_MENU + + # Right modifier keys + RSHIFT = win32con.VK_RSHIFT + RCONTROL = win32con.VK_RCONTROL + RALT = win32con.VK_RMENU + + # Special keys + ESCAPE = win32con.VK_ESCAPE + INSERT = win32con.VK_INSERT + PAUSE = win32con.VK_PAUSE + LSUPER = win32con.VK_LWIN + RSUPER = win32con.VK_RWIN + APPS = win32con.VK_APPS + SNAPSHOT = win32con.VK_SNAPSHOT + + # Lock keys + SCROLL_LOCK = win32con.VK_SCROLL + NUM_LOCK = win32con.VK_NUMLOCK + CAPS_LOCK = win32con.VK_CAPITAL + + # Navigation keys + UP = win32con.VK_UP + DOWN = win32con.VK_DOWN + LEFT = win32con.VK_LEFT + RIGHT = win32con.VK_RIGHT + PAGE_UP = win32con.VK_PRIOR + PAGE_DOWN = win32con.VK_NEXT + HOME = win32con.VK_HOME + END = win32con.VK_END + + # Number pad keys + MULTIPLY = win32con.VK_MULTIPLY + ADD = win32con.VK_ADD + SEPARATOR = win32con.VK_SEPARATOR + SUBTRACT = win32con.VK_SUBTRACT + DECIMAL = win32con.VK_DECIMAL + DIVIDE = win32con.VK_DIVIDE + NUMPAD0 = win32con.VK_NUMPAD0 + NUMPAD1 = win32con.VK_NUMPAD1 + NUMPAD2 = win32con.VK_NUMPAD2 + NUMPAD3 = win32con.VK_NUMPAD3 + NUMPAD4 = win32con.VK_NUMPAD4 + NUMPAD5 = win32con.VK_NUMPAD5 + NUMPAD6 = win32con.VK_NUMPAD6 + NUMPAD7 = win32con.VK_NUMPAD7 + NUMPAD8 = win32con.VK_NUMPAD8 + NUMPAD9 = win32con.VK_NUMPAD9 + + # Function keys + F1 = win32con.VK_F1 + F2 = win32con.VK_F2 + F3 = win32con.VK_F3 + F4 = win32con.VK_F4 + F5 = win32con.VK_F5 + F6 = win32con.VK_F6 + F7 = win32con.VK_F7 + F8 = win32con.VK_F8 + F9 = win32con.VK_F9 + F10 = win32con.VK_F10 + F11 = win32con.VK_F11 + F12 = win32con.VK_F12 + F13 = win32con.VK_F13 + F14 = win32con.VK_F14 + F15 = win32con.VK_F15 + F16 = win32con.VK_F16 + F17 = win32con.VK_F17 + F18 = win32con.VK_F18 + F19 = win32con.VK_F19 + F20 = win32con.VK_F20 + F21 = win32con.VK_F21 + F22 = win32con.VK_F22 + F23 = win32con.VK_F23 + F24 = win32con.VK_F24 + + # Multimedia keys + VOLUME_UP = win32con.VK_VOLUME_UP + VOLUME_DOWN = win32con.VK_VOLUME_DOWN + VOLUME_MUTE = win32con.VK_VOLUME_MUTE + MEDIA_NEXT_TRACK = win32con.VK_MEDIA_NEXT_TRACK + MEDIA_PREV_TRACK = win32con.VK_MEDIA_PREV_TRACK + MEDIA_PLAY_PAUSE = win32con.VK_MEDIA_PLAY_PAUSE + BROWSER_BACK = win32con.VK_BROWSER_BACK + BROWSER_FORWARD = win32con.VK_BROWSER_FORWARD + + +class Keyboard(BaseKeyboard): """Static class wrapper around SendInput.""" shift_code = win32con.VK_SHIFT @@ -171,6 +220,3 @@ def get_typeable(cls, char, is_text=False): return Typeable(char, is_text=True) code, modifiers = cls.get_keycode_and_modifiers(char) return Typeable(code, modifiers) - - -keyboard = Keyboard() diff --git a/dragonfly/actions/typeables.py b/dragonfly/actions/typeables.py index 47d49bb8..53fafe90 100644 --- a/dragonfly/actions/typeables.py +++ b/dragonfly/actions/typeables.py @@ -30,17 +30,18 @@ """ import logging -import win32con -from dragonfly.actions.keyboard import keyboard, Typeable -logging.basicConfig() -_log = logging.getLogger("actions.typeables") +from .keyboard import keyboard, Typeable, KeySymbols + + +_log = logging.getLogger("typeables") # -------------------------------------------------------------------------- # Mapping of name -> typeable. typeables = {} +key_symbols = KeySymbols() def _add_typeable(name, char): @@ -184,7 +185,7 @@ def _add_typeable(name, char): # Symbol keys # All symbols can be referred to by their printable representation. -# Reserved characters for the Key action spec -,:/ are still typeables, +# Reserved characters for the Key action spec -,:/ are Typeable objects, # but Key requires use of the longer character names. _add_typeable(name="!", char='!') _add_typeable(name="bang", char='!') @@ -272,119 +273,119 @@ def _add_typeable(name, char): typeables.update({ # Whitespace and editing keys - "enter": Typeable(code=win32con.VK_RETURN, name='enter'), - "tab": Typeable(code=win32con.VK_TAB, name='tab'), - "space": Typeable(code=win32con.VK_SPACE, name='space'), - "backspace": Typeable(code=win32con.VK_BACK, name='backspace'), - "delete": Typeable(code=win32con.VK_DELETE, name='delete'), - "del": Typeable(code=win32con.VK_DELETE, name='del'), + "enter": Typeable(code=key_symbols.RETURN, name='enter'), + "tab": Typeable(code=key_symbols.TAB, name='tab'), + "space": Typeable(code=key_symbols.SPACE, name='space'), + "backspace": Typeable(code=key_symbols.BACK, name='backspace'), + "delete": Typeable(code=key_symbols.DELETE, name='delete'), + "del": Typeable(code=key_symbols.DELETE, name='del'), # Main modifier keys - "shift": Typeable(code=win32con.VK_SHIFT, name='shift'), - "control": Typeable(code=win32con.VK_CONTROL, name='control'), - "ctrl": Typeable(code=win32con.VK_CONTROL, name='ctrl'), - "alt": Typeable(code=win32con.VK_MENU, name='alt'), + "shift": Typeable(code=key_symbols.SHIFT, name='shift'), + "control": Typeable(code=key_symbols.CONTROL, name='control'), + "ctrl": Typeable(code=key_symbols.CONTROL, name='ctrl'), + "alt": Typeable(code=key_symbols.ALT, name='alt'), # Right modifier keys - "rshift": Typeable(code=win32con.VK_RSHIFT, name='rshift'), - "rcontrol": Typeable(code=win32con.VK_RCONTROL, name='rcontrol'), - "rctrl": Typeable(code=win32con.VK_RCONTROL, name='rctrl'), - "ralt": Typeable(code=win32con.VK_RMENU, name='ralt'), + "rshift": Typeable(code=key_symbols.RSHIFT, name='rshift'), + "rcontrol": Typeable(code=key_symbols.RCONTROL, name='rcontrol'), + "rctrl": Typeable(code=key_symbols.RCONTROL, name='rctrl'), + "ralt": Typeable(code=key_symbols.RALT, name='ralt'), # Special keys - "escape": Typeable(code=win32con.VK_ESCAPE, name='escape'), - "insert": Typeable(code=win32con.VK_INSERT, name='insert'), - "pause": Typeable(code=win32con.VK_PAUSE, name='pause'), - "win": Typeable(code=win32con.VK_LWIN, name='win'), - "rwin": Typeable(code=win32con.VK_RWIN, name='rwin'), - "apps": Typeable(code=win32con.VK_APPS, name='apps'), - "popup": Typeable(code=win32con.VK_APPS, name='popup'), - "snapshot": Typeable(code=win32con.VK_SNAPSHOT, name='snapshot'), - "printscreen": Typeable(code=win32con.VK_SNAPSHOT, name='printscreen'), + "escape": Typeable(code=key_symbols.ESCAPE, name='escape'), + "insert": Typeable(code=key_symbols.INSERT, name='insert'), + "pause": Typeable(code=key_symbols.PAUSE, name='pause'), + "win": Typeable(code=key_symbols.LSUPER, name='win'), + "rwin": Typeable(code=key_symbols.RSUPER, name='rwin'), + "apps": Typeable(code=key_symbols.APPS, name='apps'), + "popup": Typeable(code=key_symbols.APPS, name='popup'), + "snapshot": Typeable(code=key_symbols.SNAPSHOT, name='snapshot'), + "printscreen": Typeable(code=key_symbols.SNAPSHOT, name='printscreen'), # Lock keys # win32api.GetKeyState(code) could be used to toggle lock keys sensibly # instead of using the up/down modifiers. - "scrolllock": Typeable(code=win32con.VK_SCROLL, name='scrolllock'), - "numlock": Typeable(code=win32con.VK_NUMLOCK, name='numlock'), - "capslock": Typeable(code=win32con.VK_CAPITAL, name='capslock'), + "scrolllock": Typeable(code=key_symbols.SCROLL_LOCK, name='scrolllock'), + "numlock": Typeable(code=key_symbols.NUM_LOCK, name='numlock'), + "capslock": Typeable(code=key_symbols.CAPS_LOCK, name='capslock'), # Navigation keys - "up": Typeable(code=win32con.VK_UP, name='up'), - "down": Typeable(code=win32con.VK_DOWN, name='down'), - "left": Typeable(code=win32con.VK_LEFT, name='left'), - "right": Typeable(code=win32con.VK_RIGHT, name='right'), - "pageup": Typeable(code=win32con.VK_PRIOR, name='pageup'), - "pgup": Typeable(code=win32con.VK_PRIOR, name='pgup'), - "pagedown": Typeable(code=win32con.VK_NEXT, name='pagedown'), - "pgdown": Typeable(code=win32con.VK_NEXT, name='pgdown'), - "home": Typeable(code=win32con.VK_HOME, name='home'), - "end": Typeable(code=win32con.VK_END, name='end'), + "up": Typeable(code=key_symbols.UP, name='up'), + "down": Typeable(code=key_symbols.DOWN, name='down'), + "left": Typeable(code=key_symbols.LEFT, name='left'), + "right": Typeable(code=key_symbols.RIGHT, name='right'), + "pageup": Typeable(code=key_symbols.PAGE_UP, name='pageup'), + "pgup": Typeable(code=key_symbols.PAGE_UP, name='pgup'), + "pagedown": Typeable(code=key_symbols.PAGE_DOWN, name='pagedown'), + "pgdown": Typeable(code=key_symbols.PAGE_DOWN, name='pgdown'), + "home": Typeable(code=key_symbols.HOME, name='home'), + "end": Typeable(code=key_symbols.END, name='end'), # Number pad keys - "npmul": Typeable(code=win32con.VK_MULTIPLY, name='npmul'), - "npadd": Typeable(code=win32con.VK_ADD, name='npadd'), - "npsep": Typeable(code=win32con.VK_SEPARATOR, name='npsep'), - "npsub": Typeable(code=win32con.VK_SUBTRACT, name='npsub'), - "npdec": Typeable(code=win32con.VK_DECIMAL, name='npdec'), - "npdiv": Typeable(code=win32con.VK_DIVIDE, name='npdiv'), - "numpad0": Typeable(code=win32con.VK_NUMPAD0, name='numpad0'), - "np0": Typeable(code=win32con.VK_NUMPAD0, name='np0'), - "numpad1": Typeable(code=win32con.VK_NUMPAD1, name='numpad1'), - "np1": Typeable(code=win32con.VK_NUMPAD1, name='np1'), - "numpad2": Typeable(code=win32con.VK_NUMPAD2, name='numpad2'), - "np2": Typeable(code=win32con.VK_NUMPAD2, name='np2'), - "numpad3": Typeable(code=win32con.VK_NUMPAD3, name='numpad3'), - "np3": Typeable(code=win32con.VK_NUMPAD3, name='np3'), - "numpad4": Typeable(code=win32con.VK_NUMPAD4, name='numpad4'), - "np4": Typeable(code=win32con.VK_NUMPAD4, name='np4'), - "numpad5": Typeable(code=win32con.VK_NUMPAD5, name='numpad5'), - "np5": Typeable(code=win32con.VK_NUMPAD5, name='np5'), - "numpad6": Typeable(code=win32con.VK_NUMPAD6, name='numpad6'), - "np6": Typeable(code=win32con.VK_NUMPAD6, name='np6'), - "numpad7": Typeable(code=win32con.VK_NUMPAD7, name='numpad7'), - "np7": Typeable(code=win32con.VK_NUMPAD7, name='np7'), - "numpad8": Typeable(code=win32con.VK_NUMPAD8, name='numpad8'), - "np8": Typeable(code=win32con.VK_NUMPAD8, name='np8'), - "numpad9": Typeable(code=win32con.VK_NUMPAD9, name='numpad9'), - "np9": Typeable(code=win32con.VK_NUMPAD9, name='np9'), + "npmul": Typeable(code=key_symbols.MULTIPLY, name='npmul'), + "npadd": Typeable(code=key_symbols.ADD, name='npadd'), + "npsep": Typeable(code=key_symbols.SEPARATOR, name='npsep'), + "npsub": Typeable(code=key_symbols.SUBTRACT, name='npsub'), + "npdec": Typeable(code=key_symbols.DECIMAL, name='npdec'), + "npdiv": Typeable(code=key_symbols.DIVIDE, name='npdiv'), + "numpad0": Typeable(code=key_symbols.NUMPAD0, name='numpad0'), + "np0": Typeable(code=key_symbols.NUMPAD0, name='np0'), + "numpad1": Typeable(code=key_symbols.NUMPAD1, name='numpad1'), + "np1": Typeable(code=key_symbols.NUMPAD1, name='np1'), + "numpad2": Typeable(code=key_symbols.NUMPAD2, name='numpad2'), + "np2": Typeable(code=key_symbols.NUMPAD2, name='np2'), + "numpad3": Typeable(code=key_symbols.NUMPAD3, name='numpad3'), + "np3": Typeable(code=key_symbols.NUMPAD3, name='np3'), + "numpad4": Typeable(code=key_symbols.NUMPAD4, name='numpad4'), + "np4": Typeable(code=key_symbols.NUMPAD4, name='np4'), + "numpad5": Typeable(code=key_symbols.NUMPAD5, name='numpad5'), + "np5": Typeable(code=key_symbols.NUMPAD5, name='np5'), + "numpad6": Typeable(code=key_symbols.NUMPAD6, name='numpad6'), + "np6": Typeable(code=key_symbols.NUMPAD6, name='np6'), + "numpad7": Typeable(code=key_symbols.NUMPAD7, name='numpad7'), + "np7": Typeable(code=key_symbols.NUMPAD7, name='np7'), + "numpad8": Typeable(code=key_symbols.NUMPAD8, name='numpad8'), + "np8": Typeable(code=key_symbols.NUMPAD8, name='np8'), + "numpad9": Typeable(code=key_symbols.NUMPAD9, name='numpad9'), + "np9": Typeable(code=key_symbols.NUMPAD9, name='np9'), # Function keys - "f1": Typeable(code=win32con.VK_F1, name='f1'), - "f2": Typeable(code=win32con.VK_F2, name='f2'), - "f3": Typeable(code=win32con.VK_F3, name='f3'), - "f4": Typeable(code=win32con.VK_F4, name='f4'), - "f5": Typeable(code=win32con.VK_F5, name='f5'), - "f6": Typeable(code=win32con.VK_F6, name='f6'), - "f7": Typeable(code=win32con.VK_F7, name='f7'), - "f8": Typeable(code=win32con.VK_F8, name='f8'), - "f9": Typeable(code=win32con.VK_F9, name='f9'), - "f10": Typeable(code=win32con.VK_F10, name='f10'), - "f11": Typeable(code=win32con.VK_F11, name='f11'), - "f12": Typeable(code=win32con.VK_F12, name='f12'), - "f13": Typeable(code=win32con.VK_F13, name='f13'), - "f14": Typeable(code=win32con.VK_F14, name='f14'), - "f15": Typeable(code=win32con.VK_F15, name='f15'), - "f16": Typeable(code=win32con.VK_F16, name='f16'), - "f17": Typeable(code=win32con.VK_F17, name='f17'), - "f18": Typeable(code=win32con.VK_F18, name='f18'), - "f19": Typeable(code=win32con.VK_F19, name='f19'), - "f20": Typeable(code=win32con.VK_F20, name='f20'), - "f21": Typeable(code=win32con.VK_F21, name='f21'), - "f22": Typeable(code=win32con.VK_F22, name='f22'), - "f23": Typeable(code=win32con.VK_F23, name='f23'), - "f24": Typeable(code=win32con.VK_F24, name='f24'), + "f1": Typeable(code=key_symbols.F1, name='f1'), + "f2": Typeable(code=key_symbols.F2, name='f2'), + "f3": Typeable(code=key_symbols.F3, name='f3'), + "f4": Typeable(code=key_symbols.F4, name='f4'), + "f5": Typeable(code=key_symbols.F5, name='f5'), + "f6": Typeable(code=key_symbols.F6, name='f6'), + "f7": Typeable(code=key_symbols.F7, name='f7'), + "f8": Typeable(code=key_symbols.F8, name='f8'), + "f9": Typeable(code=key_symbols.F9, name='f9'), + "f10": Typeable(code=key_symbols.F10, name='f10'), + "f11": Typeable(code=key_symbols.F11, name='f11'), + "f12": Typeable(code=key_symbols.F12, name='f12'), + "f13": Typeable(code=key_symbols.F13, name='f13'), + "f14": Typeable(code=key_symbols.F14, name='f14'), + "f15": Typeable(code=key_symbols.F15, name='f15'), + "f16": Typeable(code=key_symbols.F16, name='f16'), + "f17": Typeable(code=key_symbols.F17, name='f17'), + "f18": Typeable(code=key_symbols.F18, name='f18'), + "f19": Typeable(code=key_symbols.F19, name='f19'), + "f20": Typeable(code=key_symbols.F20, name='f20'), + "f21": Typeable(code=key_symbols.F21, name='f21'), + "f22": Typeable(code=key_symbols.F22, name='f22'), + "f23": Typeable(code=key_symbols.F23, name='f23'), + "f24": Typeable(code=key_symbols.F24, name='f24'), # Multimedia keys - "volumeup": Typeable(code=win32con.VK_VOLUME_UP, name='volumeup'), - "volup": Typeable(code=win32con.VK_VOLUME_UP, name='volup'), - "volumedown": Typeable(code=win32con.VK_VOLUME_DOWN, name='volumedown'), - "voldown": Typeable(code=win32con.VK_VOLUME_DOWN, name='voldown'), - "volumemute": Typeable(code=win32con.VK_VOLUME_MUTE, name='volumemute'), - "volmute": Typeable(code=win32con.VK_VOLUME_MUTE, name='volmute'), - "tracknext": Typeable(code=win32con.VK_MEDIA_NEXT_TRACK, name='tracknext'), - "trackprev": Typeable(code=win32con.VK_MEDIA_PREV_TRACK, name='trackprev'), - "playpause": Typeable(code=win32con.VK_MEDIA_PLAY_PAUSE, name='playpause'), - "browserback": Typeable(code=win32con.VK_BROWSER_BACK, name='browserback'), - "browserforward": Typeable(code=win32con.VK_BROWSER_FORWARD, name='browserforward'), + "volumeup": Typeable(code=key_symbols.VOLUME_UP, name='volumeup'), + "volup": Typeable(code=key_symbols.VOLUME_UP, name='volup'), + "volumedown": Typeable(code=key_symbols.VOLUME_DOWN, name='volumedown'), + "voldown": Typeable(code=key_symbols.VOLUME_DOWN, name='voldown'), + "volumemute": Typeable(code=key_symbols.VOLUME_MUTE, name='volumemute'), + "volmute": Typeable(code=key_symbols.VOLUME_MUTE, name='volmute'), + "tracknext": Typeable(code=key_symbols.MEDIA_NEXT_TRACK, name='tracknext'), + "trackprev": Typeable(code=key_symbols.MEDIA_PREV_TRACK, name='trackprev'), + "playpause": Typeable(code=key_symbols.MEDIA_PLAY_PAUSE, name='playpause'), + "browserback": Typeable(code=key_symbols.BROWSER_BACK, name='browserback'), + "browserforward": Typeable(code=key_symbols.BROWSER_FORWARD, name='browserforward'), }) diff --git a/dragonfly/log.py b/dragonfly/log.py index dc3e625e..cffbac2e 100644 --- a/dragonfly/log.py +++ b/dragonfly/log.py @@ -63,6 +63,8 @@ "monitor.init": (_warning, _info), "dfly.test": (_debug, _debug), "accessibility": (_info, _info), + "keyboard": (_warning, _warning), + "typeables": (_warning, _warning), } diff --git a/dragonfly/os_dependent_mock.py b/dragonfly/os_dependent_mock.py index 535bdfc3..828f898f 100755 --- a/dragonfly/os_dependent_mock.py +++ b/dragonfly/os_dependent_mock.py @@ -20,31 +20,35 @@ Heavily modified to allow more dragonfly functionality to work regardless of operating system. """ + from .actions import ActionBase, DynStrActionBase -# Mock ActionBase and DynStrActionBase classes +class MockBase(object): + def __init__(self, *args, **kwargs): + pass + + +class MockAction(ActionBase): + """ Mock class for dragonfly actions. """ + def __init__(self, *args, **kwargs): + ActionBase.__init__(self) -def mock_action(*args, **kwargs): - return ActionBase() +class Mouse(DynStrActionBase): + """ Mock Mouse action class. """ + def __init__(self, spec=None, static=False): + DynStrActionBase.__init__(self, spec, static) -def mock_dyn_str_action(*args, **kwargs): - return DynStrActionBase(*args, **kwargs) -Text = mock_dyn_str_action -Key = mock_dyn_str_action -Mouse = mock_dyn_str_action -Paste = mock_dyn_str_action -WaitWindow = mock_action -FocusWindow = mock_action -StartApp = mock_action -BringApp = mock_action -PlaySound = mock_action +WaitWindow = MockAction +FocusWindow = MockAction +StartApp = MockAction +BringApp = MockAction +PlaySound = MockAction class _WindowInfo(object): - # TODO Use proxy contexts instead executable = "" title = "" handle = "" @@ -56,19 +60,10 @@ def get_foreground(): return _WindowInfo -class MockBase(object): - def __init__(self, *args, **kwargs): - pass - - class HardwareInput(MockBase): pass -class Keyboard(MockBase): - pass - - class KeyboardInput(MockBase): pass @@ -84,12 +79,6 @@ class MouseInput(MockBase): pass -class Typeable(object): - pass - -typeables = {} - - def make_input_array(inputs): return inputs diff --git a/dragonfly/util/clipboard.py b/dragonfly/util/clipboard.py index 716078b3..9a4375db 100644 --- a/dragonfly/util/clipboard.py +++ b/dragonfly/util/clipboard.py @@ -69,6 +69,8 @@ def get_system_text(cls): @classmethod def set_system_text(cls, content): + if not content: + content = "" pyperclip.copy(content) @classmethod diff --git a/dragonfly/windows/__init__.py b/dragonfly/windows/__init__.py index ad6fc011..df8d885e 100644 --- a/dragonfly/windows/__init__.py +++ b/dragonfly/windows/__init__.py @@ -20,7 +20,6 @@ import sys -# OS agnostic imports from .rectangle import Rectangle, unit from .point import Point diff --git a/setup.py b/setup.py index f1dfdf22..fa38d57f 100644 --- a/setup.py +++ b/setup.py @@ -62,6 +62,7 @@ def read(*names): "setuptools >= 0.6c7", "comtypes;platform_system=='Windows'", "pywin32;platform_system=='Windows'", + "pynput >= 1.4.2;platform_system!='Windows'", "six", "pyperclip >= 1.7.0", "enum34;python_version<'3.4'",