202 lines
6.8 KiB
Python
202 lines
6.8 KiB
Python
|
#!/usr/bin/env python
|
||
|
# Copyright (c) 2011 The Chromium Authors. All rights reserved.
|
||
|
# Use of this source code is governed by a BSD-style license that can be
|
||
|
# found in the LICENSE file.
|
||
|
|
||
|
"""SiteCompare module for simulating keyboard input.
|
||
|
|
||
|
This module contains functions that can be used to simulate a user
|
||
|
pressing keys on a keyboard. Support is provided for formatted strings
|
||
|
including special characters to represent modifier keys like CTRL and ALT
|
||
|
"""
|
||
|
|
||
|
import time # for sleep
|
||
|
import win32api # for keybd_event and VkKeyCode
|
||
|
import win32con # Windows constants
|
||
|
|
||
|
# TODO(jhaas): Ask the readability guys if this would be acceptable:
|
||
|
#
|
||
|
# from win32con import VK_SHIFT, VK_CONTROL, VK_MENU, VK_LWIN, KEYEVENTF_KEYUP
|
||
|
#
|
||
|
# This is a violation of the style guide but having win32con. everywhere
|
||
|
# is just plain ugly, and win32con is a huge import for just a handful of
|
||
|
# constants
|
||
|
|
||
|
|
||
|
def PressKey(down, key):
|
||
|
"""Presses or unpresses a key.
|
||
|
|
||
|
Uses keybd_event to simulate either depressing or releasing
|
||
|
a key
|
||
|
|
||
|
Args:
|
||
|
down: Whether the key is to be pressed or released
|
||
|
key: Virtual key code of key to press or release
|
||
|
"""
|
||
|
|
||
|
# keybd_event injects key events at a very low level (it's the
|
||
|
# Windows API keyboard device drivers call) so this is a very
|
||
|
# reliable way of simulating user input
|
||
|
win32api.keybd_event(key, 0, (not down) * win32con.KEYEVENTF_KEYUP)
|
||
|
|
||
|
|
||
|
def TypeKey(key, keystroke_time=0):
|
||
|
"""Simulate a keypress of a virtual key.
|
||
|
|
||
|
Args:
|
||
|
key: which key to press
|
||
|
keystroke_time: length of time (in seconds) to "hold down" the key
|
||
|
Note that zero works just fine
|
||
|
|
||
|
Returns:
|
||
|
None
|
||
|
"""
|
||
|
|
||
|
# This just wraps a pair of PressKey calls with an intervening delay
|
||
|
PressKey(True, key)
|
||
|
time.sleep(keystroke_time)
|
||
|
PressKey(False, key)
|
||
|
|
||
|
|
||
|
def TypeString(string_to_type,
|
||
|
use_modifiers=False,
|
||
|
keystroke_time=0,
|
||
|
time_between_keystrokes=0):
|
||
|
"""Simulate typing a string on the keyboard.
|
||
|
|
||
|
Args:
|
||
|
string_to_type: the string to print
|
||
|
use_modifiers: specifies whether the following modifier characters
|
||
|
should be active:
|
||
|
{abc}: type characters with ALT held down
|
||
|
[abc]: type characters with CTRL held down
|
||
|
\ escapes {}[] and treats these values as literal
|
||
|
standard escape sequences are valid even if use_modifiers is false
|
||
|
\p is "pause" for one second, useful when driving menus
|
||
|
\1-\9 is F-key, \0 is F10
|
||
|
|
||
|
TODO(jhaas): support for explicit control of SHIFT, support for
|
||
|
nonprintable keys (F-keys, ESC, arrow keys, etc),
|
||
|
support for explicit control of left vs. right ALT or SHIFT,
|
||
|
support for Windows key
|
||
|
|
||
|
keystroke_time: length of time (in secondes) to "hold down" the key
|
||
|
time_between_keystrokes: length of time (seconds) to pause between keys
|
||
|
|
||
|
Returns:
|
||
|
None
|
||
|
"""
|
||
|
|
||
|
shift_held = win32api.GetAsyncKeyState(win32con.VK_SHIFT ) < 0
|
||
|
ctrl_held = win32api.GetAsyncKeyState(win32con.VK_CONTROL) < 0
|
||
|
alt_held = win32api.GetAsyncKeyState(win32con.VK_MENU ) < 0
|
||
|
|
||
|
next_escaped = False
|
||
|
escape_chars = {
|
||
|
'a': '\a', 'b': '\b', 'f': '\f', 'n': '\n', 'r': '\r', 't': '\t', 'v': '\v'}
|
||
|
|
||
|
for char in string_to_type:
|
||
|
vk = None
|
||
|
handled = False
|
||
|
|
||
|
# Check to see if this is the start or end of a modified block (that is,
|
||
|
# {abc} for ALT-modified keys or [abc] for CTRL-modified keys
|
||
|
if use_modifiers and not next_escaped:
|
||
|
handled = True
|
||
|
if char == "{" and not alt_held:
|
||
|
alt_held = True
|
||
|
PressKey(True, win32con.VK_MENU)
|
||
|
elif char == "}" and alt_held:
|
||
|
alt_held = False
|
||
|
PressKey(False, win32con.VK_MENU)
|
||
|
elif char == "[" and not ctrl_held:
|
||
|
ctrl_held = True
|
||
|
PressKey(True, win32con.VK_CONTROL)
|
||
|
elif char == "]" and ctrl_held:
|
||
|
ctrl_held = False
|
||
|
PressKey(False, win32con.VK_CONTROL)
|
||
|
else:
|
||
|
handled = False
|
||
|
|
||
|
# If this is an explicitly-escaped character, replace it with the
|
||
|
# appropriate code
|
||
|
if next_escaped and char in escape_chars: char = escape_chars[char]
|
||
|
|
||
|
# If this is \p, pause for one second.
|
||
|
if next_escaped and char == 'p':
|
||
|
time.sleep(1)
|
||
|
next_escaped = False
|
||
|
handled = True
|
||
|
|
||
|
# If this is \(d), press F key
|
||
|
if next_escaped and char.isdigit():
|
||
|
fkey = int(char)
|
||
|
if not fkey: fkey = 10
|
||
|
next_escaped = False
|
||
|
vk = win32con.VK_F1 + fkey - 1
|
||
|
|
||
|
# If this is the backslash, the next character is escaped
|
||
|
if not next_escaped and char == "\\":
|
||
|
next_escaped = True
|
||
|
handled = True
|
||
|
|
||
|
# If we make it here, it's not a special character, or it's an
|
||
|
# escaped special character which should be treated as a literal
|
||
|
if not handled:
|
||
|
next_escaped = False
|
||
|
if not vk: vk = win32api.VkKeyScan(char)
|
||
|
|
||
|
# VkKeyScan() returns the scan code in the low byte. The upper
|
||
|
# byte specifies modifiers necessary to produce the given character
|
||
|
# from the given scan code. The only one we're concerned with at the
|
||
|
# moment is Shift. Determine the shift state and compare it to the
|
||
|
# current state... if it differs, press or release the shift key.
|
||
|
new_shift_held = bool(vk & (1<<8))
|
||
|
|
||
|
if new_shift_held != shift_held:
|
||
|
PressKey(new_shift_held, win32con.VK_SHIFT)
|
||
|
shift_held = new_shift_held
|
||
|
|
||
|
# Type the key with the specified length, then wait the specified delay
|
||
|
TypeKey(vk & 0xFF, keystroke_time)
|
||
|
time.sleep(time_between_keystrokes)
|
||
|
|
||
|
# Release the modifier keys, if held
|
||
|
if shift_held: PressKey(False, win32con.VK_SHIFT)
|
||
|
if ctrl_held: PressKey(False, win32con.VK_CONTROL)
|
||
|
if alt_held: PressKey(False, win32con.VK_MENU)
|
||
|
|
||
|
|
||
|
def main():
|
||
|
# We're being invoked rather than imported. Let's do some tests
|
||
|
|
||
|
# Press command-R to bring up the Run dialog
|
||
|
PressKey(True, win32con.VK_LWIN)
|
||
|
TypeKey(ord('R'))
|
||
|
PressKey(False, win32con.VK_LWIN)
|
||
|
|
||
|
# Wait a sec to make sure it comes up
|
||
|
time.sleep(1)
|
||
|
|
||
|
# Invoke Notepad through the Run dialog
|
||
|
TypeString("wordpad\n")
|
||
|
|
||
|
# Wait another sec, then start typing
|
||
|
time.sleep(1)
|
||
|
TypeString("This is a test of SiteCompare's Keyboard.py module.\n\n")
|
||
|
TypeString("There should be a blank line above and below this one.\n\n")
|
||
|
TypeString("This line has control characters to make "
|
||
|
"[b]boldface text[b] and [i]italic text[i] and normal text.\n\n",
|
||
|
use_modifiers=True)
|
||
|
TypeString(r"This line should be typed with a visible delay between "
|
||
|
"characters. When it ends, there should be a 3-second pause, "
|
||
|
"then the menu will select File/Exit, then another 3-second "
|
||
|
"pause, then No to exit without saving. Ready?\p\p\p{f}x\p\p\pn",
|
||
|
use_modifiers=True,
|
||
|
keystroke_time=0.05,
|
||
|
time_between_keystrokes=0.05)
|
||
|
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
sys.exit(main())
|