Search code examples
pythonpywin32pywinauto

How do I send keystrokes to a background window using Python & pywinauto without bringing that window into the foreground?


Tried this code:

import win32gui
import win32con

windowID = win32gui.FindWindow(None, "testing.pdf - Adobe Acrobat Pro DC")
#win32gui.SetForegroundWindow(windowID)
win32gui.SendMessage(windowID, win32con.WM_KEYDOWN, win32con.VK_NEXT, 0) #VK_NEXT is Page Down button

Above code works only if i de-comment the SetForegroundWindow line. Is there any way to send keystrokes without bringing the window into focus?

I wish to keep the terminal in focus while my script does tasks on a window in the background. (mainly sending different keystrokes like ctrl+Tab, pgDn/pgUp, etc)

Edit - the suggested question does not answer my question because the solution over there brings the target window into focus. I want to keep the target window in the background itself.


Thank you to @Vasily Ryabov, the code now even works with PyWinAuto. :) This is the final code that works:

import time
import pywinauto

app = pywinauto.Application(backend="win32").start(r"C:\Program Files (x86)\Adobe\Acrobat DC\Acrobat\Acrobat.exe")

time.sleep(10)
print('sleeping over')
Wizard = app['testing.pdf - Adobe Acrobat Pro DC']

while True:
    Wizard.send_keystrokes("{VK_NEXT}")
    time.sleep(1)

I think the problem was that i was using type_keys method instead of send_keystrokes. Also, going back & forth between win32 & uia helped me as well to find the one that works correctly. For Acrobat DC Pro, win32 works.

Thank you @Vasily Ryabov for your excellent help & your wonderful Python library PyWinAuto! :)


Solution

  • If you bring the window to the foreground, it handles all keyboard input and it will direct keyboard input to the control that has focus. When the window is not in the foreground, none of its control have active focus and keys sent to the window won't automatically get sent to the control you want to receive the input.

    I use Foxit reader, but the same would apply for Acrobat or other applications. I tried this:

    from time import sleep
    import win32gui
    import win32con
    
    
    def callback(handle, param):
        s = win32gui.GetClassName(handle)
        try:
            print(f'Sending key to {handle}, {s}')
            win32gui.SendMessage(handle, win32con.WM_KEYDOWN, win32con.VK_NEXT, 0)
            win32gui.SendMessage(handle, win32con.WM_KEYUP, win32con.VK_NEXT, 0)
            sleep(2)
        except Exception:
            print('Exception sending to {handle}, {s}')
    
    
    window_id = win32gui.FindWindow(None, "my_multipage_doc.pdf - Foxit Reader")
    win32gui.EnumChildWindows(window_id, callback, 0)
    

    That produced a lot of output, one line for each object in the window, but this is a relevant bit:

    ...
    Sending key to 594376, ScrollBar
    Sending key to 1904808, ScrollBar
    Sending key to 397704, ScrollBar
    Sending key to 397598, AfxFrameOrView140su
    Sending key to 397580, AfxWnd140su
    Sending key to 397734, AfxWnd140su
    Sending key to 1971214, AfxWnd140su
    Sending key to 856494, FoxitDocWnd
    Sending key to 986558, Static
    ...
    

    Both when a VK_NEXT was sent to the first Scrollbar as when it was sent to the FoxitDocWnd, the viewer scrolled down by one page.

    So, I rewrote as this:

    import win32gui
    import win32con
    
    
    def send_page_down(handle, param):
        if win32gui.GetClassName(handle) == param:
            win32gui.SendMessage(handle, win32con.WM_KEYDOWN, win32con.VK_NEXT, 0)
            win32gui.SendMessage(handle, win32con.WM_KEYUP, win32con.VK_NEXT, 0)
    
    
    window_id = win32gui.FindWindow(None, "my_multipage_doc.pdf - Foxit Reader")
    win32gui.EnumChildWindows(window_id, send_page_down, 'FoxitDocWnd')
    

    That does what you need.

    Oddly, something else I tried, didn't work:

    import win32gui
    import win32con
    
    window_id = win32gui.FindWindow(None, "my_multipage_doc.pdf - Foxit Reader")
    viewer_id = win32gui.FindWindowEx(window_id, 0, 'FoxitDocWnd', None)
    win32gui.SendMessage(viewer_id , win32con.WM_KEYDOWN, win32con.VK_NEXT, 0)
    win32gui.SendMessage(viewer_id , win32con.WM_KEYUP, win32con.VK_NEXT, 0)
    

    But that fails when trying to get viewer_id. So, even though the FoxitDocWnd shows up when enumerating all child windows, it cannot find it explicitly. If you can find what's wrong with that, that would be a nicer solution.