summaryrefslogtreecommitdiffstats
path: root/tools/site_compare/drivers/win32/keyboard.py
blob: a25df5ebb405a9ba6aad32e08374b59ddd076788 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
#!/usr/bin/python2.4
# Copyright (c) 2006-2008 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)

if __name__ == "__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)