15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#!/usr/bin/env python
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Copyright (c) 2011 The Chromium Authors. All rights reserved.
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# found in the LICENSE file.
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)"""SiteCompare module for simulating keyboard input.
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)This module contains functions that can be used to simulate a user
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)pressing keys on a keyboard. Support is provided for formatted strings
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)including special characters to represent modifier keys like CTRL and ALT
115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)"""
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import time             # for sleep
145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import win32api         # for keybd_event and VkKeyCode
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import win32con         # Windows constants
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# TODO(jhaas): Ask the readability guys if this would be acceptable:
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#  from win32con import VK_SHIFT, VK_CONTROL, VK_MENU, VK_LWIN, KEYEVENTF_KEYUP
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# This is a violation of the style guide but having win32con. everywhere
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# is just plain ugly, and win32con is a huge import for just a handful of
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# constants
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def PressKey(down, key):
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Presses or unpresses a key.
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Uses keybd_event to simulate either depressing or releasing
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  a key
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Args:
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    down: Whether the key is to be pressed or released
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    key:  Virtual key code of key to press or release
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # keybd_event injects key events at a very low level (it's the
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Windows API keyboard device drivers call) so this is a very
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # reliable way of simulating user input
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  win32api.keybd_event(key, 0, (not down) * win32con.KEYEVENTF_KEYUP)
415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def TypeKey(key, keystroke_time=0):
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Simulate a keypress of a virtual key.
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Args:
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    key: which key to press
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    keystroke_time: length of time (in seconds) to "hold down" the key
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    Note that zero works just fine
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Returns:
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    None
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """
545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # This just wraps a pair of PressKey calls with an intervening delay
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  PressKey(True, key)
575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  time.sleep(keystroke_time)
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  PressKey(False, key)
595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def TypeString(string_to_type,
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)               use_modifiers=False,
635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)               keystroke_time=0,
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)               time_between_keystrokes=0):
655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Simulate typing a string on the keyboard.
665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Args:
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    string_to_type: the string to print
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    use_modifiers: specifies whether the following modifier characters
705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      should be active:
715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      {abc}: type characters with ALT held down
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      [abc]: type characters with CTRL held down
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      \ escapes {}[] and treats these values as literal
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      standard escape sequences are valid even if use_modifiers is false
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      \p is "pause" for one second, useful when driving menus
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      \1-\9 is F-key, \0 is F10
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      TODO(jhaas): support for explicit control of SHIFT, support for
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                   nonprintable keys (F-keys, ESC, arrow keys, etc),
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                   support for explicit control of left vs. right ALT or SHIFT,
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                   support for Windows key
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    keystroke_time: length of time (in secondes) to "hold down" the key
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    time_between_keystrokes: length of time (seconds) to pause between keys
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Returns:
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    None
885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  shift_held = win32api.GetAsyncKeyState(win32con.VK_SHIFT  ) < 0
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ctrl_held  = win32api.GetAsyncKeyState(win32con.VK_CONTROL) < 0
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  alt_held   = win32api.GetAsyncKeyState(win32con.VK_MENU   ) < 0
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  next_escaped = False
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  escape_chars = {
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    'a': '\a', 'b': '\b', 'f': '\f', 'n': '\n', 'r': '\r', 't': '\t', 'v': '\v'}
975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  for char in string_to_type:
995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    vk = None
1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    handled = False
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Check to see if this is the start or end of a modified block (that is,
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # {abc} for ALT-modified keys or [abc] for CTRL-modified keys
1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if use_modifiers and not next_escaped:
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      handled = True
1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if char == "{" and not alt_held:
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        alt_held = True
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        PressKey(True, win32con.VK_MENU)
1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      elif char == "}" and alt_held:
1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        alt_held = False
1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        PressKey(False, win32con.VK_MENU)
1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      elif char == "[" and not ctrl_held:
1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        ctrl_held = True
1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        PressKey(True, win32con.VK_CONTROL)
1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      elif char == "]" and ctrl_held:
1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        ctrl_held = False
1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        PressKey(False, win32con.VK_CONTROL)
1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      else:
1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        handled = False
1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # If this is an explicitly-escaped character, replace it with the
1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # appropriate code
1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if next_escaped and char in escape_chars: char = escape_chars[char]
1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # If this is \p, pause for one second.
1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if next_escaped and char == 'p':
1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      time.sleep(1)
1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      next_escaped = False
1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      handled = True
1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # If this is \(d), press F key
1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if next_escaped and char.isdigit():
1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      fkey = int(char)
1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if not fkey: fkey = 10
1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      next_escaped = False
1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      vk = win32con.VK_F1 + fkey - 1
1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # If this is the backslash, the next character is escaped
1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if not next_escaped and char == "\\":
1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      next_escaped = True
1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      handled = True
1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # If we make it here, it's not a special character, or it's an
1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # escaped special character which should be treated as a literal
1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if not handled:
1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      next_escaped = False
1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if not vk: vk = win32api.VkKeyScan(char)
1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # VkKeyScan() returns the scan code in the low byte. The upper
1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # byte specifies modifiers necessary to produce the given character
1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # from the given scan code. The only one we're concerned with at the
1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # moment is Shift. Determine the shift state and compare it to the
1535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # current state... if it differs, press or release the shift key.
1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      new_shift_held = bool(vk & (1<<8))
1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if new_shift_held != shift_held:
1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        PressKey(new_shift_held, win32con.VK_SHIFT)
1585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        shift_held = new_shift_held
1595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # Type the key with the specified length, then wait the specified delay
1615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      TypeKey(vk & 0xFF, keystroke_time)
1625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      time.sleep(time_between_keystrokes)
1635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Release the modifier keys, if held
1655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if shift_held: PressKey(False, win32con.VK_SHIFT)
1665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if ctrl_held:  PressKey(False, win32con.VK_CONTROL)
1675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if alt_held:   PressKey(False, win32con.VK_MENU)
1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def main():
1715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # We're being invoked rather than imported. Let's do some tests
1725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Press command-R to bring up the Run dialog
1745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  PressKey(True, win32con.VK_LWIN)
1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  TypeKey(ord('R'))
1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  PressKey(False, win32con.VK_LWIN)
1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Wait a sec to make sure it comes up
1795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  time.sleep(1)
1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Invoke Notepad through the Run dialog
1825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  TypeString("wordpad\n")
1835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Wait another sec, then start typing
1855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  time.sleep(1)
1865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  TypeString("This is a test of SiteCompare's Keyboard.py module.\n\n")
1875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  TypeString("There should be a blank line above and below this one.\n\n")
1885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  TypeString("This line has control characters to make "
1895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)             "[b]boldface text[b] and [i]italic text[i] and normal text.\n\n",
1905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)             use_modifiers=True)
1915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  TypeString(r"This line should be typed with a visible delay between "
1925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)             "characters. When it ends, there should be a 3-second pause, "
1935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)             "then the menu will select File/Exit, then another 3-second "
1945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)             "pause, then No to exit without saving. Ready?\p\p\p{f}x\p\p\pn",
1955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)             use_modifiers=True,
1965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)             keystroke_time=0.05,
1975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)             time_between_keystrokes=0.05)
1985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)if __name__ == "__main__":
2015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  sys.exit(main())
202