Skip to content

Commit

Permalink
Merge pull request #72 from dictation-toolbox/action-pynput
Browse files Browse the repository at this point in the history
Add Key, Text and Paste action support for X11 and Mac OS using
pynput
  • Loading branch information
drmfinlay committed Apr 23, 2019
2 parents d68ec37 + 0fadf16 commit 2c4754d
Show file tree
Hide file tree
Showing 17 changed files with 931 additions and 316 deletions.
20 changes: 9 additions & 11 deletions dragonfly/actions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
154 changes: 77 additions & 77 deletions dragonfly/actions/_generate_typeables.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")]),
)


Expand Down
127 changes: 127 additions & 0 deletions dragonfly/actions/_test_x11_text_key.py
Original file line number Diff line number Diff line change
@@ -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()
16 changes: 8 additions & 8 deletions dragonfly/actions/action_key.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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 <https://pynput.readthedocs.io/en/latest/>`__.
"""

Expand Down Expand Up @@ -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:
Expand Down
Loading

0 comments on commit 2c4754d

Please sign in to comment.