Search code examples
pythonwindowspywin32pywinauto

Waiting for user input when controlling 3rd party application via python script


I am writing a script intended to be used by members of a project team. As part of the script, I am launching a 3rd party proprietary application run through Citrix. I am going to use the script mostly to send keys to this application, but the first step once it launches is for the user to log in.

Because I would like the user to log in while the script is running, rather than asking for user/pass from some kind of GUI input earlier, and because the time it takes Citrix to launch varies, I would like to include some kind of logic that detects when the user has logged in and then resume the script from there, rather than including an obnoxiously long implicit wait or risking the script timing out.

Is there a way to detect user keystrokes using win32com.client (or to detect a change in state of the application itself)? See below for the relevant code to launch the app:

import win32com.client
shell = win32com.client.Dispatch("WScript.Shell")
shell.Run('C:\Citrix\[rest of path])

EDIT: Per Vasily's suggestion in the comments below, I attempted to adapt the "hook and listen" code to my scenario, but was unsuccessful. When I launch my file, I don't even get an exception message in my terminal, I get a Windows pop-up that says Python encountered a problem and needs to quit.

This is how I adapted it:

#[omitting import lines for brevity]
def on_timer():
    """Callback by timer out"""
    win32api.PostThreadMessage(main_thread_id, win32con.WM_QUIT, 0, 0);


def on_event(args):
    """Callback for keyboard and mouse events"""
    if isinstance(args, KeyboardEvent):
        for i in range(1,100):
            time.sleep(1)
            if args.pressed_key == 'Lcontrol':
                break

def init():
    hk = Hook()
    hk.handler = on_event
    main_thread_id = win32api.GetCurrentThreadId()
    t = Timer(55.0, on_timer)  # Quit after 55 seconds
    t.start()
    hk.hook(keyboard=True, mouse=True)

At the point when the 3rd party Citrix app begins to launch in my main script, I call hookandlisten.init().

As a reminder, my goal is to wait until the user sends a certain keystroke (here I chose Control) before proceeding with the rest of the main script.


Solution

  • Solved this by eliminating the timer and unhooking the keyboard upon the correct keystroke:

    import win32api
    import win32con
    from pywinauto.win32_hooks import Hook
    from pywinauto.win32_hooks import KeyboardEvent
    from pywinauto.win32_hooks import MouseEvent
    
    
    def on_event(args):
    
        """Callback for keyboard and mouse events"""
        if isinstance(args, KeyboardEvent):
            if args.current_key == 'Lcontrol' and args.event_type == 'key down':
                print("Success")
                hk.unhook_keyboard()
                return
    
    def init():
        hk.handler = on_event
        hk.hook(keyboard=True, mouse=False)
    
    hk = Hook()