• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
8This module contains functions that can be used to simulate a user
9pressing keys on a keyboard. Support is provided for formatted strings
10including special characters to represent modifier keys like CTRL and ALT
11"""
12
13import time             # for sleep
14import win32api         # for keybd_event and VkKeyCode
15import 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
26def 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
43def 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
61def 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
170def 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
200if __name__ == "__main__":
201  sys.exit(main())
202