Home | History | Annotate | Download | only in win32
      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