Skip to content

Instantly share code, notes, and snippets.

@Versatilus
Last active May 20, 2018 18:44
Show Gist options
  • Save Versatilus/10b5e37ec411e2679b6bf3851318b489 to your computer and use it in GitHub Desktop.
Save Versatilus/10b5e37ec411e2679b6bf3851318b489 to your computer and use it in GitHub Desktop.
Adaptation of Dragonfly's Text action to use Unicode keyboard emulation for use with Caster.
# -*- coding: utf-8 -*-
#
# This file uses code from and is intended to replace the Text Action class
# distributed as part of Dragonfly.
# (c) Copyright 2018 by Eric Lewis Paulson
# with portions
# (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
# <http://www.gnu.org/licenses/>.
#
"""
Text action
============================================================================
This section describes the :class:`Text` action object. This type of
action is used for typing text into the foreground application.
It differs from the :class:`Key` action in that :class:`Text` is used for
typing literal text, while :class:`dragonfly.actions.action_key.Key`
emulates pressing keys on the keyboard. An example of this is that the
arrow-keys are not part of a text and so cannot be typed using the
:class:`Text` action, but can be sent by the
:class:`dragonfly.actions.action_key.Key` action.
"""
from ctypes import *
import dragonfly
from caster.lib import settings, utilities
from dragonfly import ActionBase, Keyboard, typeables
#---------------------------------------------------------------------------
if "require_scancodes" not in settings.SETTINGS["miscellaneous"]:
settings.SETTINGS["miscellaneous"]["require_scancodes"] = [
"tvnviewer.exe", "vncviewer.exe", "mstsc.exe", "virtualbox.exe"
]
settings.save_config()
require_scancodes = settings.SETTINGS["miscellaneous"]["require_scancodes"]
def needs_scancodes():
""" Does the current foreground window require keyboard scancodes? """
found = False
foreground_executable = dragonfly.Window.get_foreground().executable.lower()
for program in require_scancodes:
if program.lower() in foreground_executable:
found = True
break
return found
# The following code is based on https://msdn.microsoft.com/en-us/library/windows/desktop/ms646310(v=vs.85).aspx
class KEYBDINPUT(Structure):
_fields_ = [("wVk", c_ushort), ("wScan", c_ushort), ("dwFlags", c_ulong),
("time", c_ulong), ("dwExtraInfo", POINTER(c_ulong))]
class MOUSEINPUT(Structure):
_fields_ = [("dx", c_long), ("dy", c_long), ("mouseData", c_ulong),
("dwFlags", c_ulong), ("time", c_ulong), ("dwExtraInfo",
POINTER(c_ulong))]
class HARDWAREINPUT(Structure):
_fields_ = [("uMsg", c_ulong), ("wParamL", c_ushort), ("wParamH", c_ushort)]
class _INPUTUNION(Union):
_fields_ = [("mi", MOUSEINPUT), ("ki", KEYBDINPUT), ("hi", HARDWAREINPUT)]
class INPUT(Structure):
_anonymous_ = ("u", )
_fields_ = [("type", c_ulong), ("u", _INPUTUNION)]
INPUT_MOUSE = 0
INPUT_KEYBOARD = 1
INPUT_HARDWARE = 2
SendInput = windll.LoadLibrary("User32").SendInput
SendInput.argtypes = [c_uint, POINTER(INPUT), c_int]
def key_press(character, key_up=False):
""" Creates an INPUT structure appropriate to send a Unicode keystroke.
Arguments:
- *character* (16-bit unsigned integer) -- a UTF-16LE encoded
character, possibly part of a surrogate pair
- *key_up* (boolean) -- state of the KEYEVENTF_KEYUP flag
"""
ip = INPUT(type=INPUT_KEYBOARD)
ip.ki.wVk = 0
ip.ki.wScan = c_ushort(character)
ip.ki.dwFlags = 4 # KEYEVENTF_UNICODE
if key_up:
ip.ki.dwFlags |= 2 # KEYEVENTF_KEYUP
ip.ki.time = 0
ip.ki.dwExtraInfo = pointer(c_ulong(0))
return ip
class Text(ActionBase):
"""
Action that sends keyboard events to type text.
Arguments:
- *spec* (*str*) -- the text to type
- *static* (boolean) --
no op used to maintain the legacy call signature
- *pause* (*float*) --
the time to pause between each keystroke, given
in seconds
- *autofmt* (boolean) --
no op used to maintain the legacy call signature
"""
_pause_default = 0.03
_keyboard = Keyboard()
_specials = {
"\n": typeables["enter"],
"\t": typeables["tab"],
}
def __init__(self, spec=None, static=False, pause=_pause_default, autofmt=False):
self._pause = pause
self._spec = spec
ActionBase.__init__(self)
def _execute(self, data=None):
""" Convert and execute the given *spec* as keyboard events. """
from struct import unpack
from time import sleep
if not needs_scancodes():
for character in unicode(self._spec):
if character in self._specials:
typeable = self._specials[character]
self._keyboard.send_keyboard_events(typeable.events(self._pause))
else:
byte_stream = character.encode("utf-16-le")
for short in unpack("<" + str(len(byte_stream) // 2) + "H",
byte_stream):
SendInput(1, byref(key_press(short)), sizeof(INPUT))
SendInput(1, byref(key_press(short, key_up=True)), sizeof(INPUT))
sleep(self._pause)
else:
events = []
for character in self._spec:
try:
if character in self._specials:
typeable = self._specials[character]
else:
typeable = Keyboard.get_typeable(character)
events.extend(typeable.events(self._pause))
except ValueError, e:
utilities.simple_log()
# Send keyboard events.
self._keyboard.send_keyboard_events(events)
return True
dragonfly.actions.Text = Text
dragonfly.actions.action_text.Text = Text
dragonfly.Text = Text
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment