views:

937

answers:

4

The latest Python Sendkeys module is for Python 2.6. I can't upgrade it myself as it needs the C module to be recompiled.

Does anyone know of a fairly easy alternative way to send keys to a window?

Using win32ui.FindWindow() I can find the right window, then make it active with PyCWnd.SetActiveWindow(), so all that's needed is a simple way to send keystrokes to the active window.

The purpose is to execute a menu item.

The app was written in Delphi, and doesn't have any inter-process interface that I know about.

A: 

You want the keybd_event API. My PushKeys program is syntax compatible with SendKeys and has a sleep function built in. Although it isn't in Python it should be translatable readily enough (its been translated into a lot of languages - at least it should show you the API to use). It is available here in a variety of languages.

Chris Latta
Thanks Chris, but i'm not smart enough to figure out how to use it in Python ;) Wish i knew how to tell pywin32 to send keys. pywin32's win32ui mentions WM_CHAR in HookKeyStroke(), but hook seems to be the reverse of send. (Hope I can get this working by Thursday night :)
jh45dev
Did the obvious and loooked in pywin32 help ;) There is a win32api.keybd_event there, so maybe i can make that work for now. just have to go and look up the virtual-key codes.
jh45dev
You can get the virtual key codes from the VB source in the link. If I knew Python I'd translate it for you - the code is pretty simple.
Chris Latta
This is my first post at Stack Overflow. Not sure what to do about giving points in this situation... please advise ;)
jh45dev
You've done the right thing and answered your own question with a solution. You should mark that as being the accepted answer so people know the question has been answered. As for points, if an answer is helpful or informative click the up arrow, otherwise there's no reason to
Chris Latta
Don't have enough reputation to vote up your answer yet. My own answer isn't final; if someone comes in with details of how to use ctypes/SendInput (or something better) before i get around to it, that will be the accepted answer.
jh45dev
+1  A: 
jh45dev
When I have time, I'll try calling SendInput via ctypes. Btw, I'll use this in a tk app which modifies a file used by the other app. It needs to tell the other app to save before and reload after. Nice of that app not to lock the file :)
jh45dev
A: 

This looks very interesting. Documentation is abit lacking in the code, but its rather straight forward. What are your plans for this code? Are you planning on releasing it as a module ?

bluegeek9
Don't know. Had a go at making a decnt function of it, but making Input_Array from a loop stumped me (was too tired to think straight at the time ;). i think i'll have to build the "Input_Array = " command as a string during the loop, then exec() it after. If i have time.
jh45dev
Module provided as new answer.
jh45dev
A: 

Here is a working module that calls user32.SendInput().

Not perfect, but usable.

Edit:

Yesterday I did a version with a class, and am using it in a working tkinter app. Will put it here when i get time to clean it up.

Have added this in the doc string below:

[ It is OK if I work from a folder within my profile.
These problems happened when working on another partition.
File permissions were OK, so do not know what blocked SendInput. ]

SciTE still needs the full exact window title.

#!/usr/bin/python
# -*- coding: utf-8 -*-

''' send_input for python 3, from [email protected]

code from Daniel F is adapted here. The original is at:
http://mail.python.org/pipermail/python-win32/2005-April/003131.html


SendInput sends to the window that has the keyboard focus.
That window must not be minimized.


There seem to be some strange limitations with user32.SendInput()
Here is what happened in my testing (on Vista sp2).

 [edit: It is OK if I work from a folder within my profile.    
 These problems happened when working on another partition.    
 File permissions were OK, so do not know what blocked SendInput.]

1
I opened Notepad from the Start menu,
then in Notepad opened test.txt,
and all worked fine.

2
I opened Notepad by opening test.txt in Explorer.
find_window() found Notepad, but user32.SendInput() had no effect.
If Notepad was minimized, it did not get restored or focussed.

The same happened with SciTE and Notepad2.


Another strangeness:
For SciTE I had to put in the whole window title, eg "test.txt - SciTE",
but for Notepad and Notepad2, only the app name, eg "Notepad".


'''

import ctypes as ct
from win32con import SW_MINIMIZE, SW_RESTORE
from win32ui import FindWindow, error as ui_err
from time import sleep


class cls_KeyBdInput(ct.Structure):
    _fields_ = [
        ("wVk", ct.c_ushort),
        ("wScan", ct.c_ushort),
        ("dwFlags", ct.c_ulong),
        ("time", ct.c_ulong),
        ("dwExtraInfo", ct.POINTER(ct.c_ulong) )
    ]

class cls_HardwareInput(ct.Structure):
    _fields_ = [
        ("uMsg", ct.c_ulong),
        ("wParamL", ct.c_short),
        ("wParamH", ct.c_ushort)
    ]

class cls_MouseInput(ct.Structure):
    _fields_ = [
        ("dx", ct.c_long),
        ("dy", ct.c_long),
        ("mouseData", ct.c_ulong),
        ("dwFlags", ct.c_ulong),
        ("time", ct.c_ulong),
        ("dwExtraInfo", ct.POINTER(ct.c_ulong) )
    ]

class cls_Input_I(ct.Union):
    _fields_ = [
        ("ki", cls_KeyBdInput),
        ("mi", cls_MouseInput),
        ("hi", cls_HardwareInput)
    ]

class cls_Input(ct.Structure):
    _fields_ = [
        ("type", ct.c_ulong),
        ("ii", cls_Input_I)
    ]


def find_window( s_app_name ):

    try:
        window1 = FindWindow(  None, s_app_name,)
        return window1
    except ui_err:
        pass
    except:
        raise

    try:
        window1 = FindWindow( s_app_name, None, )
        return window1
    except ui_err:
        return None
    except:
        raise


def make_input_objects( l_keys ):

    p_ExtraInfo_0 = ct.pointer(ct.c_ulong(0))

    l_inputs = [ ]
    for n_key, n_updown in l_keys:
        ki = cls_KeyBdInput( n_key, 0, n_updown, 0, p_ExtraInfo_0 )
        ii = cls_Input_I()
        ii.ki = ki
        l_inputs.append( ii )

    n_inputs = len(l_inputs)

    l_inputs_2=[]
    for ndx in range( 0, n_inputs ):
        s2 = "(1, l_inputs[%s])" % ndx
        l_inputs_2.append(s2)
    s_inputs = ', '.join(l_inputs_2)


    cls_input_array = cls_Input * n_inputs
    o_input_array = eval( "cls_input_array( %s )" % s_inputs )

    p_input_array = ct.pointer( o_input_array )
    n_size_0 = ct.sizeof( o_input_array[0] )

    # these are the args for user32.SendInput()
    return ( n_inputs, p_input_array, n_size_0 )

    '''It is interesting that o_input_array has gone out of scope
    by the time p_input_array is used, but it works.'''


def send_input( window1, t_inputs, b_minimize=True ):

    tpl1 = window1.GetWindowPlacement()
    was_min = False
    if tpl1[1] == 2:
        was_min = True
        window1.ShowWindow(SW_RESTORE)
        sleep(0.2)

    window1.SetForegroundWindow()
    sleep(0.2)
    window1.SetFocus()
    sleep(0.2)
    rv = ct.windll.user32.SendInput( *t_inputs )

    if was_min and b_minimize:
        sleep(0.3) # if the last input was Save, it may need time to take effect
        window1.ShowWindow(SW_MINIMIZE)

    return rv



# define some commonly-used key sequences
t_ctrl_s = (  # save in many apps
    ( 0x11, 0 ),
    ( 0x53, 0 ),
    ( 0x11, 2 ),
)
t_ctrl_r = (  # reload in some apps
    ( 0x11, 0 ),
    ( 0x52, 0 ),
    ( 0x11, 2 ),
)


def test():

    # file > open; a non-invasive way to test
    t_ctrl_o = ( ( 0x11, 0 ), ( 0x4F, 0 ), ( 0x11, 2 ), )

    # writes "Hello\n"
    # 0x10 is shift.  note that to repeat a key, as with 4C here, you have to release it after the first press
    t_hello = ( ( 0x10, 0 ), ( 0x48, 0 ), ( 0x10, 2 ), ( 0x45, 0 ), ( 0x4C, 0 ), ( 0x4C, 2 ), ( 0x4C, 0 ), ( 0x4F, 0 ), ( 0x0D, 0 ), )


    l_keys = [ ]
    ## l_keys.extend( t_ctrl_o )
    l_keys.extend( t_hello )
    l_keys.extend( t_ctrl_s )

    ## s_app_name = "SciTE"
    ## s_app_name = "(Untitled) - SciTE"
    s_app_name = "test.txt - SciTE"
    ## s_app_name = "Notepad2"
    ## s_app_name = "Notepad"

    window1 = find_window( s_app_name )
    if window1 == None:
        print( "%r has no window." % s_app_name )
        input( 'press enter to close' )
        exit()

    t_inputs = make_input_objects( l_keys )

    n = send_input( window1, t_inputs )

    ## print( "SendInput returned: %r" % n )
    ## print( "GetLastError: %r" % ct.windll.kernel32.GetLastError() )
    ## input( 'press enter to close' )



if __name__ == '__main__':
    test()
jh45dev