1 #!/usr/bin/env python 2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. 3 # Use of this source code is governed by a BSD-style license that can be 4 # found in the LICENSE file. 5 6 """SiteCompare module for simulating keyboard input. 7 8 This module contains functions that can be used to simulate a user 9 pressing keys on a keyboard. Support is provided for formatted strings 10 including special characters to represent modifier keys like CTRL and ALT 11 """ 12 13 import time # for sleep 14 import win32api # for keybd_event and VkKeyCode 15 import win32con # Windows constants 16 17 # TODO(jhaas): Ask the readability guys if this would be acceptable: 18 # 19 # from win32con import VK_SHIFT, VK_CONTROL, VK_MENU, VK_LWIN, KEYEVENTF_KEYUP 20 # 21 # This is a violation of the style guide but having win32con. everywhere 22 # is just plain ugly, and win32con is a huge import for just a handful of 23 # constants 24 25 26 def PressKey(down, key): 27 """Presses or unpresses a key. 28 29 Uses keybd_event to simulate either depressing or releasing 30 a key 31 32 Args: 33 down: Whether the key is to be pressed or released 34 key: Virtual key code of key to press or release 35 """ 36 37 # keybd_event injects key events at a very low level (it's the 38 # Windows API keyboard device drivers call) so this is a very 39 # reliable way of simulating user input 40 win32api.keybd_event(key, 0, (not down) * win32con.KEYEVENTF_KEYUP) 41 42 43 def TypeKey(key, keystroke_time=0): 44 """Simulate a keypress of a virtual key. 45 46 Args: 47 key: which key to press 48 keystroke_time: length of time (in seconds) to "hold down" the key 49 Note that zero works just fine 50 51 Returns: 52 None 53 """ 54 55 # This just wraps a pair of PressKey calls with an intervening delay 56 PressKey(True, key) 57 time.sleep(keystroke_time) 58 PressKey(False, key) 59 60 61 def TypeString(string_to_type, 62 use_modifiers=False, 63 keystroke_time=0, 64 time_between_keystrokes=0): 65 """Simulate typing a string on the keyboard. 66 67 Args: 68 string_to_type: the string to print 69 use_modifiers: specifies whether the following modifier characters 70 should be active: 71 {abc}: type characters with ALT held down 72 [abc]: type characters with CTRL held down 73 \ escapes {}[] and treats these values as literal 74 standard escape sequences are valid even if use_modifiers is false 75 \p is "pause" for one second, useful when driving menus 76 \1-\9 is F-key, \0 is F10 77 78 TODO(jhaas): support for explicit control of SHIFT, support for 79 nonprintable keys (F-keys, ESC, arrow keys, etc), 80 support for explicit control of left vs. right ALT or SHIFT, 81 support for Windows key 82 83 keystroke_time: length of time (in secondes) to "hold down" the key 84 time_between_keystrokes: length of time (seconds) to pause between keys 85 86 Returns: 87 None 88 """ 89 90 shift_held = win32api.GetAsyncKeyState(win32con.VK_SHIFT ) < 0 91 ctrl_held = win32api.GetAsyncKeyState(win32con.VK_CONTROL) < 0 92 alt_held = win32api.GetAsyncKeyState(win32con.VK_MENU ) < 0 93 94 next_escaped = False 95 escape_chars = { 96 'a': '\a', 'b': '\b', 'f': '\f', 'n': '\n', 'r': '\r', 't': '\t', 'v': '\v'} 97 98 for char in string_to_type: 99 vk = None 100 handled = False 101 102 # Check to see if this is the start or end of a modified block (that is, 103 # {abc} for ALT-modified keys or [abc] for CTRL-modified keys 104 if use_modifiers and not next_escaped: 105 handled = True 106 if char == "{" and not alt_held: 107 alt_held = True 108 PressKey(True, win32con.VK_MENU) 109 elif char == "}" and alt_held: 110 alt_held = False 111 PressKey(False, win32con.VK_MENU) 112 elif char == "[" and not ctrl_held: 113 ctrl_held = True 114 PressKey(True, win32con.VK_CONTROL) 115 elif char == "]" and ctrl_held: 116 ctrl_held = False 117 PressKey(False, win32con.VK_CONTROL) 118 else: 119 handled = False 120 121 # If this is an explicitly-escaped character, replace it with the 122 # appropriate code 123 if next_escaped and char in escape_chars: char = escape_chars[char] 124 125 # If this is \p, pause for one second. 126 if next_escaped and char == 'p': 127 time.sleep(1) 128 next_escaped = False 129 handled = True 130 131 # If this is \(d), press F key 132 if next_escaped and char.isdigit(): 133 fkey = int(char) 134 if not fkey: fkey = 10 135 next_escaped = False 136 vk = win32con.VK_F1 + fkey - 1 137 138 # If this is the backslash, the next character is escaped 139 if not next_escaped and char == "\\": 140 next_escaped = True 141 handled = True 142 143 # If we make it here, it's not a special character, or it's an 144 # escaped special character which should be treated as a literal 145 if not handled: 146 next_escaped = False 147 if not vk: vk = win32api.VkKeyScan(char) 148 149 # VkKeyScan() returns the scan code in the low byte. The upper 150 # byte specifies modifiers necessary to produce the given character 151 # from the given scan code. The only one we're concerned with at the 152 # moment is Shift. Determine the shift state and compare it to the 153 # current state... if it differs, press or release the shift key. 154 new_shift_held = bool(vk & (1<<8)) 155 156 if new_shift_held != shift_held: 157 PressKey(new_shift_held, win32con.VK_SHIFT) 158 shift_held = new_shift_held 159 160 # Type the key with the specified length, then wait the specified delay 161 TypeKey(vk & 0xFF, keystroke_time) 162 time.sleep(time_between_keystrokes) 163 164 # Release the modifier keys, if held 165 if shift_held: PressKey(False, win32con.VK_SHIFT) 166 if ctrl_held: PressKey(False, win32con.VK_CONTROL) 167 if alt_held: PressKey(False, win32con.VK_MENU) 168 169 170 def main(): 171 # We're being invoked rather than imported. Let's do some tests 172 173 # Press command-R to bring up the Run dialog 174 PressKey(True, win32con.VK_LWIN) 175 TypeKey(ord('R')) 176 PressKey(False, win32con.VK_LWIN) 177 178 # Wait a sec to make sure it comes up 179 time.sleep(1) 180 181 # Invoke Notepad through the Run dialog 182 TypeString("wordpad\n") 183 184 # Wait another sec, then start typing 185 time.sleep(1) 186 TypeString("This is a test of SiteCompare's Keyboard.py module.\n\n") 187 TypeString("There should be a blank line above and below this one.\n\n") 188 TypeString("This line has control characters to make " 189 "[b]boldface text[b] and [i]italic text[i] and normal text.\n\n", 190 use_modifiers=True) 191 TypeString(r"This line should be typed with a visible delay between " 192 "characters. When it ends, there should be a 3-second pause, " 193 "then the menu will select File/Exit, then another 3-second " 194 "pause, then No to exit without saving. Ready?\p\p\p{f}x\p\p\pn", 195 use_modifiers=True, 196 keystroke_time=0.05, 197 time_between_keystrokes=0.05) 198 199 200 if __name__ == "__main__": 201 sys.exit(main()) 202