Commits

Ollie Rutherfurd  committed fb7a1c0

import of version 0.3 -- lost svn repo some time ago

  • Participants

Comments (0)

Files changed (7)

+===============
+SendKeys README
+===============
+
+:Version: 0.3
+:Date: 2003-06-14
+:Author: Ollie Rutherfurd <oliver@rutherfurd.net>
+:Homepage: http://www.rutherfurd.net/python/sendkeys/
+
+SendKeys is a Python extension for Windows (R) which can be used to send 
+one or more keystrokes or keystroke combinations to the active window.
+
+See `doc\SendKeys.txt <doc\SendKeys.txt>`_ or 
+`doc\SendKeys.html <doc\SendKeys.html>`_ for documentation.
+
+.. :lineSeparator=\r\n:maxLineLen=72:noTabs=true:tabSize=4:wrap=hard:
+"""
+SendKeys.py - Sends one or more keystroke or keystroke combinations 
+to the active window.
+
+Copyright (C) 2003 Ollie Rutherfurd <oliver@rutherfurd.net>
+
+Python License
+
+Version 0.3 (2003-06-14)
+
+$Id$
+"""
+
+import sys
+import time
+from _sendkeys import char2keycode, key_up, key_down, toggle_numlock
+
+__all__ = ['KeySequenceError', 'SendKeys']
+
+try:
+    True
+except NameError:
+    True,False = 1,0
+
+KEYEVENTF_KEYUP = 2
+VK_SHIFT        = 16
+VK_CONTROL      = 17
+VK_MENU         = 18
+
+PAUSE           = 50/1000.0 # 50 milliseconds
+
+# 'codes' recognized as {CODE( repeat)?}
+CODES = {
+    'BACK':         8,
+    'BACKSPACE':    8,
+    'BS':           8,
+    'BKSP':         8,
+    'BREAK':        3,
+    'CAP':          20,
+    'CAPSLOCK':     20,
+    'DEL':          46,
+    'DELETE':       46,
+    'DOWN':         40,
+    'END':          35,
+    'ENTER':        13,
+    'ESC':          27,
+    'HELP':         47,
+    'HOME':         36,
+    'INS':          45,
+    'INSERT':       45,
+    'LEFT':         37,
+    'LWIN':         91,
+    'NUMLOCK':      144,
+    'PGDN':         34,
+    'PGUP':         33,
+    'PRTSC':        44,
+    'RIGHT':        39,
+    'RMENU':        165,
+    'RWIN':         92,
+    'SCROLLLOCK':   145,
+    'SPACE':        32,
+    'TAB':          9,
+    'UP':           38,
+    'DOWN':         40,
+    'BACKSPACE':    8,
+    'F1':           112,
+    'F2':           113,
+    'F3':           114,
+    'F4':           115,
+    'F5':           116,
+    'F6':           117,
+    'F7':           118,
+    'F8':           119,
+    'F9':           120,
+    'F10':          121,
+    'F11':          122,
+    'F12':          123,
+    'F13':          124,
+    'F14':          125,
+    'F15':          126,
+    'F16':          127,
+    'F17':          128,
+    'F18':          129,
+    'F19':          130,
+    'F20':          131,
+    'F21':          132,
+    'F22':          133,
+    'F23':          134,
+    'F24':          135,
+}
+
+ESCAPE = '+^%~{}[]'
+NO_SHIFT = '[]'
+
+SHIFT = {
+    '!': '1',
+    '@': '2',
+    '#': '3',
+    '$': '4',
+    '&': '7',
+    '*': '8',
+    '_': '-',
+    '|': '\\',
+    ':': ';',
+    '"': '\'',
+    '<': ',',
+    '>': '.',
+    '?': '/',
+}
+
+# modifier keys
+MODIFIERS = {
+    '+': VK_SHIFT,
+    '^': VK_CONTROL,
+    '%': VK_MENU,
+}
+
+
+class KeySequenceError(Exception):
+    """Exception raised when a key sequence string has a syntax error"""
+    def __str__(self):
+        return ' '.join(self.args)
+
+
+def _append_code(keys,code):
+    keys.append((code, True))
+    keys.append((code, False))
+
+def _next_char(chars,error_msg=None):
+    if error_msg is None:
+        error_msg = 'expected another character'
+    try:
+        return chars.pop()
+    except IndexError:
+        raise KeySequenceError(error_msg)
+
+def _handle_char(c,keys,shift):
+    if shift:
+        keys.append((MODIFIERS['+'],True))
+    _append_code(keys, char2keycode(c))
+    if shift:
+        keys.append((MODIFIERS['+'],False))
+
+def _release_modifiers(keys,modifiers):
+    for c in modifiers.keys():
+        if modifiers[c]:
+            keys.append((MODIFIERS[c], False))
+            modifiers[c] = False
+
+
+def str2keys(key_string, 
+             with_spaces=False,
+             with_tabs=False, 
+             with_newlines=False):
+    """
+    Converts `key_string` string to a list of 2-tuples, 
+    ``(keycode,down)``, which  can be given to `playkeys`.
+
+    `key_string` : str
+        A string of keys.
+    `with_spaces` : bool
+        Whether to treat spaces as ``{SPACE}``. If `False`, spaces are ignored.
+    `with_tabs` : bool
+        Whether to treat tabs as ``{TAB}``. If `False`, tabs are ignored.
+    `with_newlines` : bool
+        Whether to treat newlines as ``{ENTER}``. If `False`, newlines are ignored.
+    """
+    # reading input as a stack
+    chars = list(key_string)
+    chars.reverse()
+    # results
+    keys = []
+    # for keeping track of whether shift, ctrl, & alt are pressed
+    modifiers = {}
+    for k in MODIFIERS.keys():
+        modifiers[k] = False
+
+    while chars:
+        c = chars.pop()
+
+        if c in MODIFIERS.keys():
+            keys.append((MODIFIERS[c],True))
+            modifiers[c] = True
+
+        # group of chars, for applying a modifier
+        elif c == '(':
+            while c != ')':
+                c = _next_char(chars,'`(` without `)`')
+                if c == ')':
+                    raise KeySequenceError('expected a character before `)`')
+
+                if c == ' ' and with_spaces:
+                    _handle_char(CODES['SPACE'], keys,  False)
+                elif c == '\n' and with_newlines:
+                    _handle_char(CODES['ENTER'], keys, False)
+                elif c == '\t' and with_tabs:
+                    _handle_char(CODES['TAB'], keys, False)
+                else:
+                    # if we need shift for this char and it's not already pressed
+                    shift = (c.isupper() or c in SHIFT.keys()) and not modifiers['+']
+                    if c in SHIFT.keys():
+                        _handle_char(SHIFT[c], keys, shift)
+                    else:
+                        _handle_char(c.lower(), keys, shift)
+                c = _next_char(chars,'`)` not found')
+            _release_modifiers(keys,modifiers)
+
+        # escaped code, modifier, or repeated char
+        elif c == '{':
+            saw_space = False
+            name = [_next_char(chars)]
+            arg = ['0']
+            c = _next_char(chars, '`{` without `}`')
+            while c != '}':
+                if c == ' ':
+                    saw_space = True
+                elif c in '.0123456789' and saw_space:
+                    arg.append(c)
+                else:
+                    name.append(c)
+                c = _next_char(chars, '`{` without `}`')
+            code = ''.join(name)
+            arg = float('0' + ''.join(arg))
+            if code == 'PAUSE':
+                if not arg:
+                    arg = PAUSE
+                keys.append((None,arg))
+            else:
+                # always having 1 here makes logic 
+                # easier -- we can always loop
+                if arg == 0: 
+                    arg = 1
+                for i in range(int(arg)):
+                    if code in CODES.keys():
+                        _append_code(keys, CODES[code])
+                    else:
+                        # must be an escaped modifier or a 
+                        # repeated char at this point
+                        if len(code) > 1:
+                            raise KeySequenceError('Unknown code: %s' % code)
+                        # handling both {e 3} and {+}, {%}, {^}
+                        shift = code in ESCAPE and not code in NO_SHIFT
+                        # do shift if we've got an upper case letter
+                        shift = shift or code[0].isupper()
+                        c = code
+                        if not shift:
+                            # handle keys in SHIFT (!, @, etc...)
+                            if c in SHIFT.keys():
+                                c = SHIFT[c]
+                                shift = True
+                        _handle_char(c.lower(), keys, shift)
+            _release_modifiers(keys,modifiers)
+
+        # unexpected ")"
+        elif c == ')':
+            raise KeySequenceError('`)` should be preceeded by `(`')
+
+        # unexpected "}"
+        elif c == '}':
+            raise KeySequenceError('`}` should be preceeded by `{`')
+
+        # handling a single character
+        else:
+            if c == ' ' and not with_spaces:
+                continue
+            elif c == '\t' and not with_tabs:
+                continue
+            elif c == '\n' and not with_newlines:
+                continue
+
+            if c in ('~','\n'):
+                _append_code(keys, CODES['ENTER'])
+            elif c == ' ':
+                _append_code(keys, CODES['SPACE'])
+            elif c == '\t':
+                _append_code(keys, CODES['TAB'])
+            else:
+                # if we need shift for this char and it's not already pressed
+                shift = (c.isupper() or c in SHIFT.keys()) and not modifiers['+']
+                if c in SHIFT.keys():
+                    _handle_char(SHIFT[c], keys, shift)
+                else:
+                    _handle_char(c.lower(), keys, shift)
+                _release_modifiers(keys,modifiers)
+
+    _release_modifiers(keys,modifiers)
+    return keys
+
+
+def playkeys(keys, pause=.05):
+    """
+    Simulates pressing and releasing one or more keys.
+
+    `keys` : str
+        A list of 2-tuples consisting of ``(keycode,down)``
+        where `down` is `True` when the key is being pressed
+        and `False` when it's being released.
+
+        `keys` is returned from `str2keys`.
+    `pause` : float
+        Number of seconds between releasing a key and pressing the 
+        next one.
+    """
+    for (vk, arg) in keys:
+        if vk:
+            if arg:
+                key_down(vk)
+            else:
+                key_up(vk)
+                if pause:   # pause after key up
+                    time.sleep(pause)
+        else:
+            time.sleep(arg)
+
+
+def SendKeys(keys, 
+             pause=0.05, 
+             with_spaces=False, 
+             with_tabs=False, 
+             with_newlines=False,
+             turn_off_numlock=True):
+    """
+    Sends keys to the current window.
+
+    `keys` : str
+        A string of keys.
+    `pause` : float
+        The number of seconds to wait between sending each key
+        or key combination.
+    `with_spaces` : bool
+        Whether to treat spaces as ``{SPACE}``. If `False`, spaces are ignored.
+    `with_tabs` : bool
+        Whether to treat tabs as ``{TAB}``. If `False`, tabs are ignored.
+    `with_newlines` : bool
+        Whether to treat newlines as ``{ENTER}``. If `False`, newlines are ignored.
+    `turn_off_numlock` : bool
+        Whether to turn off `NUMLOCK` before sending keys.
+
+    example::
+
+        SendKeys("+hello{SPACE}+world+1")
+
+    would result in ``"Hello World!"``
+    """
+
+    restore_numlock = False
+    try:
+        # read keystroke keys into a list of 2 tuples [(key,up),]
+        _keys = str2keys(keys, with_spaces, with_tabs, with_newlines)
+
+        # certain keystrokes don't seem to behave the same way if NUMLOCK
+        # is on (for example, ^+{LEFT}), so turn NUMLOCK off, if it's on
+        # and restore its original state when done.
+        if turn_off_numlock:
+            restore_numlock = toggle_numlock(False)
+
+        # "play" the keys to the active window
+        playkeys(_keys, pause)
+    finally:
+        if restore_numlock and turn_off_numlock:
+            key_down(CODES['NUMLOCK'])
+            key_up(CODES['NUMLOCK'])
+
+
+def usage():
+    """
+    Writes help message to `stderr` and exits.
+    """
+    print >> sys.stderr, """\
+%(name)s [-h] [-d seconds] [-p seconds] [-f filename] or [string of keys]
+
+    -dN    or --delay=N   : N is seconds before starting
+    -pN    or --pause=N   : N is seconds between each key
+    -fNAME or --file=NAME : NAME is filename containing keys to send
+    -h     or --help      : show help message
+""" % {'name': 'SendKeys.py'}
+    sys.exit(1)
+
+
+def error(msg):
+    """
+    Writes `msg` to `stderr`, displays usage
+    information, and exits.
+    """
+    print >> sys.stderr, '\nERROR: %s\n' % msg
+    usage()
+
+
+def main(args=None):
+    import getopt
+
+    if args is None:
+        args = sys.argv[1:]
+
+    try:
+        opts,args = getopt.getopt(args, 
+            "hp:d:f:", ["help","pause","delay","file"])
+    except getopt.GetoptError:
+        usage()
+
+    pause=0
+    delay=0
+    filename=None
+
+    for o, a in opts:
+        if o in ('-h','--help'):
+            usage()
+        elif o in ('-f','--file'):
+            filename = a
+        elif o in ('-p','--pause'):
+            try:
+                pause = float(a)
+                assert pause >= 0
+            except (ValueError,AssertionError),e:
+                error('`pause` must be >= 0.0')
+        elif o in ('-d','--delay'):
+            try:
+                delay = float(a)
+                assert delay >= 0
+            except (ValueError,AssertionError),e:
+                error('`delay` must be >= 0.0')
+
+    time.sleep(delay)
+
+    if not filename is None and args:
+        error("can't pass both filename and string of keys on command-line")
+    elif filename:
+        f = open(filename)
+        keys = f.read()
+        f.close()
+        SendKeys(keys, pause)
+    else:
+        for a in args:
+            SendKeys(a, pause)
+
+
+if __name__ == '__main__':
+    main(sys.argv[1:])
+
+# :indentSize=4:lineSeparator=\r\n:maxLineLen=80:noTabs=true:tabSize=4:
+
+TODO:
+remove numpad keys
+keymap support?
+ctypes?
+/*
+* _sendkeys module.
+*
+* This module is used by the SendKeys module.
+*
+* Copyright (C) 2003 Ollie Rutherfurd <oliver@rutherfurd.net>
+*
+* Version 0.3 (2003-06-14)
+*
+* $Id$
+*/
+
+#include "Python.h"
+#include "windows.h"
+
+/* sends a key pressed event */
+static void
+_key_down(char vk)
+{
+	char scan = 0;
+	scan = MapVirtualKeyA(vk,0);
+
+	keybd_event((char)vk,
+			scan,
+			0, /* down */
+			0);
+}
+
+/* sends a key released event */
+static void
+_key_up(char vk)
+{
+	char scan = 0;
+	scan = MapVirtualKeyA(vk,0);
+
+	keybd_event((char)vk,
+			scan,
+			KEYEVENTF_KEYUP,
+			0);
+}
+
+static char char2keycode_docs[] = "\
+char2keycode(char) -> int \n\
+\n\
+Converts character to virtual key code \n\
+";
+
+static PyObject*
+char2keycode(PyObject* self, PyObject* args)
+{
+	char c = 0;
+	int vk = 0;
+
+	if(!PyArg_ParseTuple(args, "c", &c))
+		return NULL;
+
+	vk = VkKeyScanA(c);
+
+	return Py_BuildValue("i", vk);
+}
+
+static char toggle_numlock_docs[] = "\
+toggle_numlock(int) ->  int \n\
+\n\
+Turns NUMLOCK on or off and returns whether \n\
+it was originally on or off. \n\
+";
+
+static PyObject*
+toggle_numlock(PyObject* self, PyObject* args)
+{
+	int is_on = 0;
+	int turn_on = 0;
+	BYTE keys[256] = {0};
+
+	if(!PyArg_ParseTuple(args, "i", &turn_on))
+		return NULL;
+
+	GetKeyboardState((LPBYTE)&keys);
+	is_on = keys[VK_NUMLOCK] & 0x1;
+	if(is_on != turn_on)
+	{
+		keybd_event(VK_NUMLOCK, 
+			    0x45, 
+			    KEYEVENTF_EXTENDEDKEY | 0, 
+			    0);
+		keybd_event(VK_NUMLOCK, 
+			    0x45, 
+			    KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 
+			    0);
+
+	}
+
+	return Py_BuildValue("i", is_on);
+}
+
+static char key_down_docs[] = "\
+key_down(int) -> None \n\
+\n\
+Generates a key pressed event.  Takes a \n\
+virtual key code. \n\
+";
+
+static PyObject*
+key_down(PyObject* self, PyObject* args)
+{
+	int vk = 0;
+
+	if(!PyArg_ParseTuple(args, "i", &vk))
+		return NULL;
+
+	// XXX exception if >= 256
+	_key_down((byte)vk);
+
+	return Py_BuildValue("");
+}
+
+static char key_up_docs[] = "\
+key_up(int) -> None \n\
+\n\
+Generates a key released event.  Takes a \n\
+virtual key code. \n\
+";
+
+static PyObject*
+key_up(PyObject* self, PyObject* args)
+{
+	int vk = 0;
+
+	if(!PyArg_ParseTuple(args, "i", &vk))
+		return NULL;
+
+	// XXX exception if >= 256
+	_key_up((byte)vk);
+
+	return Py_BuildValue("");
+}
+
+static PyMethodDef _sendkeys_methods[] = {
+	{"char2keycode", char2keycode, METH_VARARGS, char2keycode_docs},
+	{"key_down", key_down, METH_VARARGS, key_down_docs},
+	{"key_up",   key_up,   METH_VARARGS, key_up_docs},
+	{"toggle_numlock", toggle_numlock, METH_VARARGS, toggle_numlock_docs},
+	{NULL, NULL}
+};
+
+void 
+init_sendkeys(void)
+{
+	Py_InitModule("_sendkeys", _sendkeys_methods);
+}
+
+/* :indentSize=8:lineSeparator=\r\n:maxLineLen=76:mode=c:
+   :noTabs=false:tabSize=8:wrap=hard: */

File doc/SendKeys.txt

+========
+SendKeys
+========
+
+:Version: 0.3
+:Date: 2003-06-14
+:Author: Ollie Rutherfurd <oliver@rutherfurd.net>
+:Homepage: http://www.rutherfurd.net/python/sendkeys/
+
+.. contents:: 
+
+Overview
+========
+
+SendKeys is a Python module for Windows (R) which can be used to send 
+one or more keystrokes or keystroke combinations to the active window.
+
+SendKeys exports 1 function, `SendKeys`, and 1 exception, `KeySequenceError`.
+
+SendKeys
+--------
+
+SendKeys(`keys`, `pause` = ``0.05``,  `with_spaces` = ``False``, 
+        `with_tabs` = ``False``, `with_newlines` = ``False``, 
+        `turn_off_numlock` = ``True``)
+
+Parameters
+~~~~~~~~~~
+
+`keys` : str
+    A string of keys.
+`pause` : float
+    The number of seconds to wait between sending each key
+    or key combination.
+`with_spaces` : bool
+    Whether to treat spaces as ``{SPACE}``. If ``False``, spaces are ignored.
+`with_tabs` : bool
+    Whether to treat tabs as ``{TAB}``. If ``False``, tabs are ignored.
+`with_newlines` : bool
+    Whether to treat newlines as ``{ENTER}``. If ``False``, newlines are ignored.
+`turn_off_numlock` : bool
+    Whether to turn off `NUMLOCK` before sending keys.
+
+KeySequenceError
+----------------
+
+`SendKeys` may throw `KeySequenceError` if an error is found when reading `keys`.
+**SendKeys reads all keys before pressing any, so if an error is found, no keys 
+will be pressed.**
+
+
+Key Syntax
+==========
+
+Modifiers
+---------
+
+`SendKeys` takes a string specifying one or more keys to press.  Most letter,
+number, and  punctuation keys are represented by the corresponding characters, 
+but the following keys and characters have special meaning:
+
+======= =======
+Key     Meaning
+======= =======
+``+``   `SHIFT`
+``^``   `CTRL`
+``%``   `ALT`
+======= =======
+
+To apply one or more modifiers to a key, place it in front of that key.  
+For example, here's how to get `CONTROL` + `a`::
+
+    ^a
+
+You may apply one or more modifiers to a group of keys, by putting the keys
+in parentheses.  For example, the following holds `SHIFT` down while all 3
+keys are pressed::
+
+    +(abc)
+
+To use one of the modifier keys, escape it by surrounding it with curly braces
+``{`` and ``}``.  For example, for a literal `%` instead of `ALT`::
+
+    {%}
+
+All of the following must be escaped within curly braces::
+
+    + ^ % ~ { } [ ] ( )
+
+**Each character must be escaped individually.** You may **not** do the 
+following:: 
+
+    {%%}
+
+.. Note:: When possible, `SendKeys` will automatically press `SHIFT` for you.
+          This easier for the programmer to read and write.  For example, if 
+          you wish `SendKeys` to type ``ABC!!``, rather than using one of the 
+          following::
+
+            +a+b+c+1+1
+            +(abc11)
+
+          you may use::
+
+            ABC!!
+
+
+Key Codes
+---------
+
+For keys which don't have a character representation, you must use a code.
+Here are the codes `SendKeys` recognizes:
+
+=================== =======================================
+Key                 Code
+=================== =======================================
+BACKSPACE           {BACKSPACE}, {BS}, or {BKSP}
+BREAK               {BREAK}
+CAPS LOCK           {CAPSLOCK} or {CAP}
+DEL or DELETE       {DELETE} or {DEL}
+DOWN ARROW          {DOWN}
+END                 {END}
+ENTER               {ENTER} or ``~``
+ESC                 {ESC}
+HELP                {HELP}
+HOME                {HOME}
+INS or INSERT       {INSERT} or {INS}
+LEFT ARROW          {LEFT}
+NUM LOCK            {NUMLOCK}
+PAGE DOWN           {PGDN}
+PAGE UP             {PGUP}
+PRINT SCREEN        {PRTSC}
+RIGHT ARROW         {RIGHT}
+SCROLL LOCK         {SCROLLLOCK}
+SPACE BAR           {PACE}
+TAB                 {TAB}
+UP ARROW            {UP}
+F1                  {F1}
+F2                  {F2}
+F3                  {F3}
+F4                  {F4}
+F5                  {F5}
+F6                  {F6}
+F7                  {F7}
+F8                  {F8}
+F9                  {F9}
+F10                 {F10}
+F11                 {F11}
+F12                 {F12}
+F13                 {F13}
+F14                 {F14}
+F15                 {F15}
+F16                 {F16}
+F17                 {F17}
+F18                 {F18}
+F19                 {F19}
+F20                 {F20}
+F21                 {F21}
+F22                 {F22}
+F23                 {F23}
+F24                 {F24}
+Keypad add          {ADD}
+Keypad subtract     {SUBTRACT}
+Keypad multiply     {MULTIPLY}
+Keypad divide       {DIVIDE}
+Left Windows(R)     {LWIN}
+Right Windows(R)    {RWIN}
+=================== =======================================
+
+Modifers maybe used with codes as well.  For example, for 
+`CONTROL` + `SHIFT` + `HOME`::
+
+    ^+{HOME}
+
+
+Repeating Keys
+--------------
+
+Here's the syntax for a repeating key::
+
+    {key count}
+
+Where ``key`` may be one of the `Key Codes`_ or a character and ``count`` 
+is the number of times to press the key.
+
+Examples:
+
+=============== ========================================
+Sequence        Equivlent
+=============== ========================================
+``{ENTER 2}``   ``{ENTER}{ENTER}``
+``{o 3}``       ``ooo``
+``^{A 2}``      ``^(AA)``
+=============== ========================================
+
+
+Examples
+========
+
+Hello World
+-----------
+
+In this example, `SendKeys` is used to type "Hello World!" in notepad.
+
+::
+
+    import SendKeys
+    SendKeys.SendKeys("""
+        {LWIN}
+        {PAUSE .25}
+        r
+        Notepad.exe{ENTER}
+        {PAUSE 1}
+        Hello{SPACE}World!
+        {PAUSE 1}
+        %{F4}
+        n
+    """)
+
+Tic-Tac-Toe
+-----------
+
+In this example, `SendKeys` is used to play a game of Tic-Tac-Toe in notepad.
+
+::
+
+    import os, sys, tempfile
+    from SendKeys import SendKeys
+    
+    try:
+        True
+    except NameError:
+        True,False = 1,0
+    
+    if __name__ == '__main__':
+    
+        # create file game will be saved to
+        filename = tempfile.mktemp('.txt')
+        print >> sys.stdout, "saving tic-tac-toe game to `%s`" % filename
+        f = open(filename,'w')
+        f.write('')
+        f.close()
+    
+        # open notepad
+        SendKeys("""{LWIN}rNotepad.exe{SPACE}"%(filename)s"{ENTER}{PAUSE 1}""" 
+            % {'filename': filename.replace('~','{~}')}, with_spaces=True)
+    
+        # draw board
+        SendKeys("""\
+       |   |   
+    ---+---+---
+       |   |   
+    ---+---+---
+       |   |  """.replace('+','{+}'),0.1, with_spaces=True, with_newlines=True)
+    
+       # play the game
+        SendKeys("""\
+        ^{HOME}
+        {DOWN 2}{RIGHT 5}+{RIGHT}{PAUSE 1}x
+        {LEFT 4}+{LEFT}+o
+        {UP 2}{RIGHT 3}+{RIGHT}x
+        {DOWN 4}+{LEFT}+(o)
+        {LEFT 4}+{LEFT}x
+        {RIGHT 7}{UP 4}+{RIGHT}O
+        {DOWN 4}+{LEFT}x
+        {UP 4}{LEFT 8}+{LEFT}+O
+        {RIGHT 7}{DOWN 2}+{RIGHT 1}x
+        ^s
+        """, 0.1)
+    
+        # read game saved from notepad
+        f = open(filename)
+        output = f.read()
+        f.close()
+    
+        assert output == """\
+     O | x | O 
+    ---+---+---
+     O | x | x 
+    ---+---+---
+     x | O | x"""
+        print 'Bad news: cat got the game'
+        print "Good news: that's what we expected, so the test passed"
+
+
+Command Line Usage
+==================
+
+SendKeys may also be used as a script::
+
+    C:\>python SendKeys.py --help
+    SendKeys.py [-h] [-d seconds] [-p seconds] [-f filename] or [string of keys]
+    
+        -dN    or --delay=N   : N is seconds before starting
+        -pN    or --pause=N   : N is seconds between each key
+        -fNAME or --file=NAME : NAME is filename containing keys to send
+        -h     or --help      : show help message
+
+
+Here's a silly example::
+
+    C:\>python SendKeys.py "echo{SPACE}'Hello{SPACE}World!'{ENTER}"
+    
+    C:\>echo 'Hello World!'
+    'Hello World!'
+    
+    C:\>
+
+
+Downloads
+=========
+
+The latest version is available at http://www.rutherfurd.net/python/sendkeys/.
+
+
+References
+==========
+
+This implementation of `SendKeys` is based on the 
+following sources:
+
+1. SendKeys, implemented in Pascal: 
+
+   http://www.ddj.com/documents/s=928/ddj9718j/9718j.htm
+
+2. SendKeys, implemented in Perl:
+
+   http://triumvir.org/prog/perl/guitest/
+
+3. SendKeys, Windows Scripting Host docs:
+
+   http://msdn.microsoft.com/library/default.asp?url=/library/en-us/script56/html/wsmthsendkeys.asp
+
+
+Known Issues
+============
+
+If `NUMLOCK` is on when `SendKeys` is called, then certain
+keystroke combinations don't seem to work.  For example,
+`C` + `SHIFT` + `LEFT` (or ``^+{LEFT}``) doesn't select text
+in notepad.  Instead, the cursor is just moved one to the 
+left as if `SHIFT` hadn't been pressed.
+
+As a work-around, when you call `SendKeys()`, by default it
+turns off `NUMLOCK`, if it's on.  You may disable this behavior
+by passing ``turn_off_numlock=False`` to `SendKeys()`.  If 
+`NUMLOCK` was turned off, it is turned back on before `SendKeys()`
+returns.
+
+
+License
+=======
+
+`Python License`__
+
+__ http://www.python.org/doc/Copyright.html
+
+
+Version History
+===============
+
+0.3 (2003-06-14)
+----------------
+
+* Compatible w/Python 2.1.
+
+0.2 (2003-06-06)
+----------------
+
+* Uses an extension, written in c, so `ctypes`_ (cool as it is)
+  is not required.
+
+0.1 (never released)
+--------------------
+
+* Used `ctypes`_ to make Windows API calls.
+
+.. _ctypes: http://starship.python.net/crew/theller/ctypes.html
+
+
+Feedback
+========
+
+Please send questions, comments, bug reports, etc... to Ollie Rutherfurd
+at oliver@rutherfurd.net.
+
+.. :lineSeparator=\r\n:maxLineLen=80:noTabs=true:tabSize=4:wrap=none:

File examples/SendKeys_TicTacToe.py

+"""
+Using SendKeys to play a game of Tic-Tac-Toe.
+
+Ollie Rutherfurd <oliver@rutherfurd.net>
+
+$Id$
+"""
+
+import os, sys, tempfile
+from SendKeys import SendKeys
+
+try:
+    True
+except NameError:
+    True,False = 1,0
+
+if __name__ == '__main__':
+
+    # create file game will be saved to
+    filename = tempfile.mktemp('.txt')
+    print >> sys.stdout, "saving tic-tac-toe game to `%s`" % filename
+    f = open(filename,'w')
+    f.write('')
+    f.close()
+
+    # open notepad
+    SendKeys("""{LWIN}rNotepad.exe{SPACE}"%(filename)s"{ENTER}{PAUSE 1}""" 
+        % {'filename': filename.replace('~','{~}')}, with_spaces=True)
+
+    # draw board
+    SendKeys("""\
+   |   |   
+---+---+---
+   |   |   
+---+---+---
+   |   |  """.replace('+','{+}'),0.1, with_spaces=True, with_newlines=True)
+
+   # play the game
+    SendKeys("""\
+    ^{HOME}
+    {DOWN 2}{RIGHT 5}+{RIGHT}{PAUSE 1}x
+    {LEFT 4}+{LEFT}+o
+    {UP 2}{RIGHT 3}+{RIGHT}x
+    {DOWN 4}+{LEFT}+(o)
+    {LEFT 4}+{LEFT}x
+    {RIGHT 7}{UP 4}+{RIGHT}O
+    {DOWN 4}+{LEFT}x
+    {UP 4}{LEFT 8}+{LEFT}+O
+    {RIGHT 7}{DOWN 2}+{RIGHT 1}x
+    ^s
+    """, 0.1)
+
+    # read game saved from notepad
+    f = open(filename)
+    output = f.read()
+    f.close()
+
+    assert output == """\
+ O | x | O 
+---+---+---
+ O | x | x 
+---+---+---
+ x | O | x"""
+    print 'Bad news: cat got the game'
+    print "Good news: that's what we expected, so the test passed"
+
+# :indentSize=4:lineSeparator=\r\n:maxLineLen=80:noTabs=true:tabSize=4:wrap=none:
+# distutils script for SendKeys module
+from distutils.core import setup
+from distutils.extension import Extension
+from glob import glob
+import sys
+
+# patch distutils if it can't cope with the "classifiers" or
+# "download_url" keywords
+if sys.version < '2.3':
+    from distutils.dist import DistributionMetadata
+    DistributionMetadata.classifiers = None
+    DistributionMetadata.download_url = None
+
+setup(
+    name='SendKeys',
+    description="An implementation of Visual Basic's SendKeys function",
+    long_description="""SendKeys is a Python module for Windows (R) 
+        which can be used to send one or more keystrokes or keystroke 
+        combinations to the active window.""",
+    url='http://www.rutherfurd.net/python/sendkeys/',
+    download_url='http://www.rutherfurd.net/python/sendkeys/',
+    version='0.3',
+    author='Ollie Rutherfurd',
+    author_email='oliver@rutherfurd.net',
+    license='Python License',
+    py_modules=['SendKeys'],
+    ext_modules=[
+        Extension("_sendkeys", ["_sendkeys.c"],
+            libraries=['user32','kernel32']),
+    ],
+    data_files=[
+        ('./doc', glob('doc\\*.*')),
+        ('.', ['README.txt']),
+    ],
+    classifiers=[
+        'Development Status :: 4 - Beta',
+        'Environment :: Console',
+        'Environment :: Win32 (MS Windows)',
+        'License :: OSI Approved :: Python Software Foundation License',
+        'Operating System :: Microsoft :: Windows',
+        'Programming Language :: Python',
+        'Topic :: Software Development :: Libraries :: Python Modules',
+        'Topic :: Software Development :: Testing',
+    ]
+)
+
+#:indentSize=4:lineSeparator=\r\n:maxLineLen=76:noTabs=true:tabSize=4:wrap=hard: