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'",