Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Keyboard event not sent to window with pywin32

I've wrote A code that gets the HWND from any program I want. So thats how I got the hwnd if your asking.

The following code should bring up device manger and send the down arrow to the program.

But it doenst. It does bring up the device manager but it doesnt send the arrow down key to the program, at least nothing happens.

If I change the hwndMain number with the hwnd code of a notepad window, the code does work and sends the arrow down key

import win32api
import win32con
import win32gui
import time

hwndMain = 133082
hwndChild = win32gui.GetWindow(hwndMain, win32con.GW_CHILD)
win32gui.SetForegroundWindow(hwndMain)
time.sleep(1)

win32api.SendMessage(hwndChild, win32con.WM_CHAR, 0x28, 0)

EDIT

I've tried

win32api.SendMessage(hwndChild, win32con.WM_CHAR, win32con.WM_KEYDOWN, 0)

Instead of

win32api.SendMessage(hwndChild, win32con.WM_CHAR, 0x28, 0)

But that doesnt work either.

I'm on python 2.7

like image 334
hetijav Avatar asked Dec 14 '18 10:12

hetijav


1 Answers

Every Win window can have 0 or more child windows, and each of those child windows can also have 0 or more children of their own, and so on... So each window may have a whole tree of children.

There is more about windows, than meets the eye. The user might look at one (top) window and imagine that its tree looks in a certain way, when in fact that tree could look totally different (more complex), as there might be some windows that are not visible.

When sending the message to a window and expecting a certain behavior to occur, the message must be sent to the exact window (or to one of its ancestors which are designed in such a way to forward it), otherwise the message will simply be ignored (as the wrong window doesn't handle that kind of message).
In our case, it means that the WM_KEYDOWN (or WM_CHAR) message should be sent to:

  • The (Edit) window that holds the text for Notepad
  • The (TreeView) window that holds the device list for Device Manager

You are using [ActiveState.Docs]: win32gui.GetWindow, which wraps [MS.Docs]: GetWindow function which states (for GW_CHILD):

The retrieved handle identifies the child window at the top of the Z order, if the specified window is a parent window; otherwise, the retrieved handle is NULL. The function examines only child windows of the specified window. It does not examine descendant windows.

Coincidentally, for Notepad sending the message to its 1st child works, because that child turned to be the very Edit window that I mentioned above (besides that child, Notepad only has another one which is the StatusBar, and that's it, none of these windows has any children of their own).

For Device Manager on the other hand things are not so simple. As you can see, its structure is more complex (e.g. ToolBar window is visible). As recommended, In order to work with windows, I'm using [MS.Docs]: EnumChildWindows function.

code.py:

#!/usr/bin/env python3

import sys
import pywintypes
import win32gui
import win32con


def enum_child_proc(wnd, param):
    print("    Handling child 0x{:08X} - [{:}] - 0x{:08X}".format(wnd, win32gui.GetWindowText(wnd), win32gui.GetParent(wnd)))
    if param[0] >= 0:
        if param[1] == param[0]:
            win32gui.SendMessage(wnd, win32con.WM_KEYDOWN, win32con.VK_DOWN, 0)
            return 0
        param[1] += 1


def handle_window(wnd, child_index=-1):
    print("Handling 0x{:08X} - [{:}]".format(wnd, win32gui.GetWindowText(wnd)))
    cur_child = 0
    param = [child_index, cur_child]
    try:
        win32gui.EnumChildWindows(wnd, enum_child_proc, param)
    except pywintypes.error as e:
        if child_index < 0 or e.args[0]:
            raise e


def main():
    np_wnd = 0x01DB1EE2  # Notepad handle
    dm_wnd = 0x000E2042  # Device Manager handle

    handle_window(np_wnd, child_index=0)
    handle_window(dm_wnd, child_index=6)


if __name__ == "__main__":
    print("Python {:s} on {:s}\n".format(sys.version, sys.platform))
    main()

Notes:

  • I hardcoded the 2 window handles (np_wnd, dm_wnd). Obviously, they won't be valid (they are no longer valid on my machine either since I closed the windows), and their values need to be changed
  • In order to find a window's handle (and some of its children) I'm using Spy++ ([MS.Docs]: How to: Start Spy++), which is part of VStudio, but I'm sure there are tons of other similar applications

Output:

e:\Work\Dev\StackOverflow\q053778227>"e:\Work\Dev\VEnvs\py_064_03.06.08_test0\Scripts\python.exe" code.py
Python 3.6.8 (tags/v3.6.8:3c6b436a57, Dec 24 2018, 00:16:47) [MSC v.1916 64 bit (AMD64)] on win32

Handling 0x01DB1EE2 - [Untitled - Notepad]
    Handling child 0x01811FA4 - [] - 0x01DB1EE2
Handling 0x000E2042 - [Device Manager]
    Handling child 0x00621A5A - [] - 0x000E2042
    Handling child 0x01991F44 - [Device Manager] - 0x00621A5A
    Handling child 0x01691F3E - [] - 0x01991F44
    Handling child 0x000C20B0 - [] - 0x01691F3E
    Handling child 0x004D2000 - [] - 0x000C20B0
    Handling child 0x004420CA - [] - 0x004D2000
    Handling child 0x01191F20 - [] - 0x004420CA

As seen from the output, the TreeView window is the 7th child (of the 7th child :) ) of the Device Manager window meaning that there are 6 intermediary (and invisible) windows between them (which ignore that message).

Although the code did the trick for the windows in question, there's no current recipe that works for any window (or if there is, I am not aware of it). I must mention that I've tried to determine the child window of interest in the tree by looking at its:

  • Name
  • Class
  • Style (MS doc is quite poor in this area)
    • Extended style
  • Position (in relation to its parent)
  • SendMessage's return code

but I couldn't find anything that would differentiate it from other windows. The only thing that I noticed is that for Notepad, the desired window is the 1st child enumerated, while for Device Manager it's the 7st one, so I did the filtering based on this fact (child_index), but I consider it totally unreliable.

As an alternative, there could be no filtering at all, and the message sent to all the child windows in the tree, but this might have unwanted effects, as there could be other windows that respond to that message. For example Device Manager tree consists of ~30 child windows.

At the end, I would also like to mention that some windows (web browsers like Chrome), have their own windows systems, so none of this will work.

like image 197
CristiFati Avatar answered Oct 04 '22 07:10

CristiFati