You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ORPA-pyOpenRPA/Resources/Web/pywinauto/keyboard.py

636 lines
21 KiB

# -*- coding: utf-8 -*-
# GUI Application automation and testing library
# Copyright (C) 2006-2018 Mark Mc Mahon and Contributors
# https://github.com/pywinauto/pywinauto/graphs/contributors
# http://pywinauto.readthedocs.io/en/latest/credits.html
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# * Neither the name of pywinauto nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""Keyboard input emulation module
Automate typing keys to an active window by calling ``SendKeys`` method.
You can use any Unicode characters (on Windows) and some special keys listed
below. The module is also available on Linux.
**Available key codes:** ::
{SCROLLLOCK}, {VK_SPACE}, {VK_LSHIFT}, {VK_PAUSE}, {VK_MODECHANGE},
{BACK}, {VK_HOME}, {F23}, {F22}, {F21}, {F20}, {VK_HANGEUL}, {VK_KANJI},
{VK_RIGHT}, {BS}, {HOME}, {VK_F4}, {VK_ACCEPT}, {VK_F18}, {VK_SNAPSHOT},
{VK_PA1}, {VK_NONAME}, {VK_LCONTROL}, {ZOOM}, {VK_ATTN}, {VK_F10}, {VK_F22},
{VK_F23}, {VK_F20}, {VK_F21}, {VK_SCROLL}, {TAB}, {VK_F11}, {VK_END},
{LEFT}, {VK_UP}, {NUMLOCK}, {VK_APPS}, {PGUP}, {VK_F8}, {VK_CONTROL},
{VK_LEFT}, {PRTSC}, {VK_NUMPAD4}, {CAPSLOCK}, {VK_CONVERT}, {VK_PROCESSKEY},
{ENTER}, {VK_SEPARATOR}, {VK_RWIN}, {VK_LMENU}, {VK_NEXT}, {F1}, {F2},
{F3}, {F4}, {F5}, {F6}, {F7}, {F8}, {F9}, {VK_ADD}, {VK_RCONTROL},
{VK_RETURN}, {BREAK}, {VK_NUMPAD9}, {VK_NUMPAD8}, {RWIN}, {VK_KANA},
{PGDN}, {VK_NUMPAD3}, {DEL}, {VK_NUMPAD1}, {VK_NUMPAD0}, {VK_NUMPAD7},
{VK_NUMPAD6}, {VK_NUMPAD5}, {DELETE}, {VK_PRIOR}, {VK_SUBTRACT}, {HELP},
{VK_PRINT}, {VK_BACK}, {CAP}, {VK_RBUTTON}, {VK_RSHIFT}, {VK_LWIN}, {DOWN},
{VK_HELP}, {VK_NONCONVERT}, {BACKSPACE}, {VK_SELECT}, {VK_TAB}, {VK_HANJA},
{VK_NUMPAD2}, {INSERT}, {VK_F9}, {VK_DECIMAL}, {VK_FINAL}, {VK_EXSEL},
{RMENU}, {VK_F3}, {VK_F2}, {VK_F1}, {VK_F7}, {VK_F6}, {VK_F5}, {VK_CRSEL},
{VK_SHIFT}, {VK_EREOF}, {VK_CANCEL}, {VK_DELETE}, {VK_HANGUL}, {VK_MBUTTON},
{VK_NUMLOCK}, {VK_CLEAR}, {END}, {VK_MENU}, {SPACE}, {BKSP}, {VK_INSERT},
{F18}, {F19}, {ESC}, {VK_MULTIPLY}, {F12}, {F13}, {F10}, {F11}, {F16},
{F17}, {F14}, {F15}, {F24}, {RIGHT}, {VK_F24}, {VK_CAPITAL}, {VK_LBUTTON},
{VK_OEM_CLEAR}, {VK_ESCAPE}, {UP}, {VK_DIVIDE}, {INS}, {VK_JUNJA},
{VK_F19}, {VK_EXECUTE}, {VK_PLAY}, {VK_RMENU}, {VK_F13}, {VK_F12}, {LWIN},
{VK_DOWN}, {VK_F17}, {VK_F16}, {VK_F15}, {VK_F14}
**Modifiers:**
- ``'+': {VK_SHIFT}``
- ``'^': {VK_CONTROL}``
- ``'%': {VK_MENU}`` a.k.a. Alt key
Example how to use modifiers: ::
SendKeys('^a^c') # select all (Ctrl+A) and copy to clipboard (Ctrl+C)
SendKeys('+{INS}') # insert from clipboard (Shift+Ins)
SendKeys('%{F4}') # close an active window with Alt+F4
Repetition count can be specified for special keys. ``{ENTER 2}`` says to
press Enter twice.
"""
from __future__ import unicode_literals
import sys
if sys.platform != 'win32':
from .linux.keyboard import KeySequenceError, KeyAction, PauseAction
from .linux.keyboard import handle_code, parse_keys, SendKeys
else:
import time
import ctypes
import win32api
import six
from . import win32structures
__all__ = ['KeySequenceError', 'SendKeys']
#pylint: disable-msg=R0903
DEBUG = 0
GetMessageExtraInfo = ctypes.windll.user32.GetMessageExtraInfo
MapVirtualKey = ctypes.windll.user32.MapVirtualKeyW
SendInput = ctypes.windll.user32.SendInput
UINT = ctypes.c_uint
SendInput.restype = UINT
SendInput.argtypes = [UINT, ctypes.c_void_p, ctypes.c_int]
VkKeyScan = ctypes.windll.user32.VkKeyScanW
VkKeyScan.restype = ctypes.c_short
VkKeyScan.argtypes = [ctypes.c_wchar]
INPUT_KEYBOARD = 1
KEYEVENTF_EXTENDEDKEY = 1
KEYEVENTF_KEYUP = 2
KEYEVENTF_UNICODE = 4
KEYEVENTF_SCANCODE = 8
VK_SHIFT = 16
VK_CONTROL = 17
VK_MENU = 18
# 'codes' recognized as {CODE( repeat)?}
CODES = {
'BACK': 8,
'BACKSPACE':8,
'BKSP': 8,
'BREAK': 3,
'BS': 8,
'CAP': 20,
'CAPSLOCK': 20,
'DEL': 46,
'DELETE': 46,
'DOWN': 40,
'END': 35,
'ENTER': 13,
'ESC': 27,
'F1': 112,
'F2': 113,
'F3': 114,
'F4': 115,
'F5': 116,
'F6': 117,
'F7': 118,
'F8': 119,
'F9': 120,
'F10': 121,
'F11': 122,
'F12': 123,
'F13': 124,
'F14': 125,
'F15': 126,
'F16': 127,
'F17': 128,
'F18': 129,
'F19': 130,
'F20': 131,
'F21': 132,
'F22': 133,
'F23': 134,
'F24': 135,
'HELP': 47,
'HOME': 36,
'INS': 45,
'INSERT': 45,
'LEFT': 37,
'LWIN': 91,
'NUMLOCK': 144,
'PGDN': 34,
'PGUP': 33,
'PRTSC': 44,
'RIGHT': 39,
'RMENU': 165,
'RWIN': 92,
'SCROLLLOCK':145,
'SPACE': 32,
'TAB': 9,
'UP': 38,
'VK_ACCEPT': 30,
'VK_ADD': 107,
'VK_APPS': 93,
'VK_ATTN': 246,
'VK_BACK': 8,
'VK_CANCEL': 3,
'VK_CAPITAL': 20,
'VK_CLEAR': 12,
'VK_CONTROL': 17,
'VK_CONVERT': 28,
'VK_CRSEL': 247,
'VK_DECIMAL': 110,
'VK_DELETE': 46,
'VK_DIVIDE': 111,
'VK_DOWN': 40,
'VK_END': 35,
'VK_EREOF': 249,
'VK_ESCAPE': 27,
'VK_EXECUTE': 43,
'VK_EXSEL': 248,
'VK_F1': 112,
'VK_F2': 113,
'VK_F3': 114,
'VK_F4': 115,
'VK_F5': 116,
'VK_F6': 117,
'VK_F7': 118,
'VK_F8': 119,
'VK_F9': 120,
'VK_F10': 121,
'VK_F11': 122,
'VK_F12': 123,
'VK_F13': 124,
'VK_F14': 125,
'VK_F15': 126,
'VK_F16': 127,
'VK_F17': 128,
'VK_F18': 129,
'VK_F19': 130,
'VK_F20': 131,
'VK_F21': 132,
'VK_F22': 133,
'VK_F23': 134,
'VK_F24': 135,
'VK_FINAL': 24,
'VK_HANGEUL': 21,
'VK_HANGUL': 21,
'VK_HANJA': 25,
'VK_HELP': 47,
'VK_HOME': 36,
'VK_INSERT': 45,
'VK_JUNJA': 23,
'VK_KANA': 21,
'VK_KANJI': 25,
'VK_LBUTTON': 1,
'VK_LCONTROL':162,
'VK_LEFT': 37,
'VK_LMENU': 164,
'VK_LSHIFT': 160,
'VK_LWIN': 91,
'VK_MBUTTON': 4,
'VK_MENU': 18,
'VK_MODECHANGE': 31,
'VK_MULTIPLY': 106,
'VK_NEXT': 34,
'VK_NONAME': 252,
'VK_NONCONVERT': 29,
'VK_NUMLOCK': 144,
'VK_NUMPAD0': 96,
'VK_NUMPAD1': 97,
'VK_NUMPAD2': 98,
'VK_NUMPAD3': 99,
'VK_NUMPAD4': 100,
'VK_NUMPAD5': 101,
'VK_NUMPAD6': 102,
'VK_NUMPAD7': 103,
'VK_NUMPAD8': 104,
'VK_NUMPAD9': 105,
'VK_OEM_CLEAR': 254,
'VK_PA1': 253,
'VK_PAUSE': 19,
'VK_PLAY': 250,
'VK_PRINT': 42,
'VK_PRIOR': 33,
'VK_PROCESSKEY': 229,
'VK_RBUTTON': 2,
'VK_RCONTROL': 163,
'VK_RETURN': 13,
'VK_RIGHT': 39,
'VK_RMENU': 165,
'VK_RSHIFT': 161,
'VK_RWIN': 92,
'VK_SCROLL': 145,
'VK_SELECT': 41,
'VK_SEPARATOR': 108,
'VK_SHIFT': 16,
'VK_SNAPSHOT': 44,
'VK_SPACE': 32,
'VK_SUBTRACT': 109,
'VK_TAB': 9,
'VK_UP': 38,
'ZOOM': 251,
}
# reverse the CODES dict to make it easy to look up a particular code name
CODE_NAMES = dict((entry[1], entry[0]) for entry in CODES.items())
# modifier keys
MODIFIERS = {
'+': VK_SHIFT,
'^': VK_CONTROL,
'%': VK_MENU,
}
class KeySequenceError(Exception):
"""Exception raised when a key sequence string has a syntax error"""
def __str__(self):
return ' '.join(self.args)
class KeyAction(object):
"""Class that represents a single keyboard action
It represents either a PAUSE action (not really keyboard) or a keyboard
action (press or release or both) of a particular key.
"""
def __init__(self, key, down = True, up = True):
self.key = key
if isinstance(self.key, six.string_types):
self.key = six.text_type(key)
self.down = down
self.up = up
def _get_key_info(self):
"""Return virtual_key, scan_code, and flags for the action
This is one of the methods that will be overridden by sub classes.
"""
return 0, ord(self.key), KEYEVENTF_UNICODE
def get_key_info(self):
"""Return virtual_key, scan_code, and flags for the action
This is one of the methods that will be overridden by sub classes.
"""
return self._get_key_info()
def GetInput(self):
"""Build the INPUT structure for the action"""
actions = 1
# if both up and down
if self.up and self.down:
actions = 2
inputs = (win32structures.INPUT * actions)()
vk, scan, flags = self._get_key_info()
for inp in inputs:
inp.type = INPUT_KEYBOARD
inp.ki.wVk = vk
inp.ki.wScan = scan
inp.ki.dwFlags |= flags
# it seems to return 0 every time but it's required by MSDN specification
# so call it just in case
inp.ki.dwExtraInfo = GetMessageExtraInfo()
# if we are releasing - then let it up
if self.up:
inputs[-1].ki.dwFlags |= KEYEVENTF_KEYUP
return inputs
def run(self):
"""Execute the action"""
inputs = self.GetInput()
# SendInput() supports all Unicode symbols
num_inserted_events = SendInput(len(inputs), ctypes.byref(inputs),
ctypes.sizeof(win32structures.INPUT))
if num_inserted_events != len(inputs):
raise RuntimeError('SendInput() inserted only ' + str(num_inserted_events) +
' out of ' + str(len(inputs)) + ' keyboard events')
def _get_down_up_string(self):
"""Return a string that will show whether the string is up or down
return 'down' if the key is a press only
return 'up' if the key is up only
return '' if the key is up & down (as default)
"""
down_up = ""
if not (self.down and self.up):
if self.down:
down_up = "down"
elif self.up:
down_up = "up"
return down_up
def key_description(self):
"""Return a description of the key"""
vk, scan, flags = self._get_key_info()
desc = ''
if vk:
if vk in CODE_NAMES:
desc = CODE_NAMES[vk]
else:
desc = "VK {}".format(vk)
else:
desc = "{}".format(self.key)
return desc
def __str__(self):
parts = []
parts.append(self.key_description())
up_down = self._get_down_up_string()
if up_down:
parts.append(up_down)
return "<{}>".format(" ".join(parts))
__repr__ = __str__
class VirtualKeyAction(KeyAction):
"""Represents a virtual key action e.g. F9 DOWN, etc
Overrides necessary methods of KeyAction
"""
def _get_key_info(self):
"""Virtual keys have extended flag set"""
# copied more or less verbatim from
# http://www.pinvoke.net/default.aspx/user32.sendinput
if 33 <= self.key <= 46 or 91 <= self.key <= 93:
flags = KEYEVENTF_EXTENDEDKEY
else:
flags = 0
# This works for %{F4} - ALT + F4
#return self.key, 0, 0
# this works for Tic Tac Toe i.e. +{RIGHT} SHIFT + RIGHT
return self.key, MapVirtualKey(self.key, 0), flags
def run(self):
"""Execute the action"""
# it works more stable for virtual keys than SendInput
for inp in self.GetInput():
win32api.keybd_event(inp.ki.wVk, inp.ki.wScan, inp.ki.dwFlags)
class EscapedKeyAction(KeyAction):
"""Represents an escaped key action e.g. F9 DOWN, etc
Overrides necessary methods of KeyAction
"""
def _get_key_info(self):
"""EscapedKeyAction doesn't send it as Unicode
The vk and scan code are generated differently.
"""
vkey_scan = LoByte(VkKeyScan(self.key))
return (vkey_scan, MapVirtualKey(vkey_scan, 0), 0)
def key_description(self):
"""Return a description of the key"""
return "KEsc {}".format(self.key)
def run(self):
"""Execute the action"""
# it works more stable for virtual keys than SendInput
for inp in self.GetInput():
win32api.keybd_event(inp.ki.wVk, inp.ki.wScan, inp.ki.dwFlags)
class PauseAction(KeyAction):
"""Represents a pause action"""
def __init__(self, how_long):
self.how_long = how_long
def run(self):
"""Pause for the lenght of time specified"""
time.sleep(self.how_long)
def __str__(self):
return "<PAUSE %1.2f>"% (self.how_long)
__repr__ = __str__
def handle_code(code):
"""Handle a key or sequence of keys in braces"""
code_keys = []
# it is a known code (e.g. {DOWN}, {ENTER}, etc)
if code in CODES:
code_keys.append(VirtualKeyAction(CODES[code]))
# it is an escaped modifier e.g. {%}, {^}, {+}
elif len(code) == 1:
code_keys.append(KeyAction(code))
# it is a repetition or a pause {DOWN 5}, {PAUSE 1.3}
elif ' ' in code:
to_repeat, count = code.rsplit(None, 1)
if to_repeat == "PAUSE":
try:
pause_time = float(count)
except ValueError:
raise KeySequenceError('invalid pause time %s'% count)
code_keys.append(PauseAction(pause_time))
else:
try:
count = int(count)
except ValueError:
raise KeySequenceError(
'invalid repetition count {}'.format(count))
# If the value in to_repeat is a VK e.g. DOWN
# we need to add the code repeated
if to_repeat in CODES:
code_keys.extend(
[VirtualKeyAction(CODES[to_repeat])] * count)
# otherwise parse the keys and we get back a KeyAction
else:
to_repeat = parse_keys(to_repeat)
if isinstance(to_repeat, list):
keys = to_repeat * count
else:
keys = [to_repeat] * count
code_keys.extend(keys)
else:
raise RuntimeError("Unknown code: {}".format(code))
return code_keys
def parse_keys(string,
with_spaces = False,
with_tabs = False,
with_newlines = False,
modifiers = None):
"""Return the parsed keys"""
keys = []
if not modifiers:
modifiers = []
index = 0
while index < len(string):
c = string[index]
index += 1
# check if one of CTRL, SHIFT, ALT has been pressed
if c in MODIFIERS.keys():
modifier = MODIFIERS[c]
# remember that we are currently modified
modifiers.append(modifier)
# hold down the modifier key
keys.append(VirtualKeyAction(modifier, up = False))
if DEBUG:
print("MODS+", modifiers)
continue
# Apply modifiers over a bunch of characters (not just one!)
elif c == "(":
# find the end of the bracketed text
end_pos = string.find(")", index)
if end_pos == -1:
raise KeySequenceError('`)` not found')
keys.extend(
parse_keys(string[index:end_pos], modifiers = modifiers))
index = end_pos + 1
# Escape or named key
elif c == "{":
# We start searching from index + 1 to account for the case {}}
end_pos = string.find("}", index+1)
if end_pos == -1:
raise KeySequenceError('`}` not found')
code = string[index:end_pos]
index = end_pos + 1
keys.extend(handle_code(code))
# unmatched ")"
elif c == ')':
raise KeySequenceError('`)` should be preceeded by `(`')
# unmatched "}"
elif c == '}':
raise KeySequenceError('`}` should be preceeded by `{`')
# so it is a normal character
else:
# don't output white space unless flags to output have been set
if (c == ' ' and not with_spaces or \
c == '\t' and not with_tabs or \
c == '\n' and not with_newlines):
continue
# output newline
if c in ('~', '\n'):
keys.append(VirtualKeyAction(CODES["ENTER"]))
# safest are the virtual keys - so if our key is a virtual key
# use a VirtualKeyAction
#if ord(c) in CODE_NAMES:
# keys.append(VirtualKeyAction(ord(c)))
elif modifiers:
keys.append(EscapedKeyAction(c))
else:
keys.append(KeyAction(c))
# as we have handled the text - release the modifiers
while modifiers:
if DEBUG:
print("MODS-", modifiers)
keys.append(VirtualKeyAction(modifiers.pop(), down = False))
# just in case there were any modifiers left pressed - release them
while modifiers:
keys.append(VirtualKeyAction(modifiers.pop(), down = False))
return keys
def LoByte(val):
"""Return the low byte of the value"""
return val & 0xff
def HiByte(val):
"""Return the high byte of the value"""
return (val & 0xff00) >> 8
def SendKeys(keys,
pause=0.05,
with_spaces=False,
with_tabs=False,
with_newlines=False,
turn_off_numlock=True):
"""Parse the keys and type them"""
keys = parse_keys(keys, with_spaces, with_tabs, with_newlines)
for k in keys:
k.run()
time.sleep(pause)