Pythonic control of VMware -- ctypes does not provide access to 'menu' gui-control.

Michael Geary Mike at DeleteThis.Geary.com
Mon Mar 1 19:40:10 CET 2004


zapazap wrote:
> My Test:
>   I have a unittest that successfully opens and closes both
>   _Notepad_ and _Freecell_, but fails to close _VMware_.
>
> My Suspicion:
>   I suspect that there is a different "path" (so to speak) to
>   the menu control in the VMWare application than exists in
>   the other two applications.

If you are trying to close an application, I wouldn't access its menu at
all. The normal way to close a Windows app is to send or post a WM_CLOSE
message to its main window.

> Question #2:
>   Is my suspicion correct?  Might I have to go through several
>   nested controls before I get to the menu control of some apps?
>   If so, I think may need to search for some interactive browser
>   and/or debugger for windows app to reverse engineer the app's
>   structure.  But I fear how much windows arcana I will need to
>   learn if I am to do this :-(  -- advice of any sort would sure
>   be encouraging.

If you need to find out more about how an application is structured,
Winspector Spy may be useful:

http://www.windows-spy.com/

> Let me say that my goal is for a user to be able to see ONLY the
> virtual machine, but be able to have snapshots taken and restored,
> devices reassigned, etc, from within the VM without seeing the
> host.  I will just have the VM talk to the Host via sockets, that
> is no problem to hack on.  My only difficulty is having a process
> on the host actually drive the VMWare application in a way that
> does not require the VM to leave full-screen mode.  Perhaps this
> description might help someone in their helping me?

Won't the user still be able to press Ctrl+Alt (or whatever you set the
hotkey to) to break out of full screen mode and get back to the host system?

> # path to vmware folder
> VMWARE_HOME = 'D:\\Progra~1\\VMware\\VMware~1'

You shouldn't need to hard code this path. Windows has an AppPath to
VMware.exe, so it should be able to run it with just the filename.

> # number of seconds to wait for the OS to respond to a change request.
> DELAY = 2.0
> # command prefix
> CMD = 'start cmd /c '
> ...
>         # text to be found in the title of a freecell application
>         appname = 'FreeCell'
>         command = CMD + appname
>         # ensure no currently running instance of the application
>         assert not get_hwnds(appname)
>         # launch the application
>         os.system(command)

Don't use os.system to run a program in Windows. It launches a new instance
of CMD.EXE. You won't notice this if you're running in a console window
already, but if you're not in a console window, os.system will open one
needlessly.

You can use os.startfile to run a program using the ShellExecute function in
Windows, which is much better than using os.system.

Or use win32api.ShellExecute, which gives you more control because it
supports all of the arguments to the ShellExecute function.

>         # give it time
>         time.sleep(DELAY)

If you were coding in C, I would recommend using ShellExecuteEx instead of
ShellExecute. ShellExecuteEx gives you the process and thread handles to the
application, so you can use the WaitForInputIdle() function instead of an
unreliable time delay.

I don't see a wrapper for ShellExecuteEx in win32api, but you could probably
do it with ctypes.

>         # get the (unique) hwnd for the application's main window
>         [hwnd] = get_hwnds(appname)
>
>         # find the id for the Game|Exit menu entry
>         hmenu = ctypes.windll.user32.GetMenu(hwnd)
>         assert hmenu, 'Application %s has no menu!' % appname
>         assert menu_name(hmenu,0)=='&Game'
>         hmenu = ctypes.windll.user32.GetSubMenu(hmenu, 0)
>         assert menu_name(hmenu,9)=='E&xit'
>         ExitID = ctypes.windll.user32.GetMenuItemID(hmenu, 9)
>
>         # try to close the application
>         win32gui.PostMessage(hwnd, win32con.WM_COMMAND, ExitID, 0)

Yeah, definitely skip all the menu stuff and use WM_CLOSE. Untested:

win32gui.PostMessage( hwnd, win32con.WM_CLOSE, 0, 0 )

>         # give it time
>         time.sleep(DELAY)
>         # it should be gone now
>         assert not get_hwnds(appname)

Better yet, use SendMessage instead of PostMessage:

win32gui.SendMessage( hwnd, win32con.WM_CLOSE, 0, 0 )

The difference between PostMessage and SendMessage is that SendMessage waits
until the target window function returns, so you wouldn't need the delay.

> def get_hwnds(text):
>     """
>     Return list of all top level hwnds with specified
>     text in the title.
>     """
>     fn = win32gui.GetWindowText
>     list = []
>     win32gui.EnumWindows( (lambda hwnd,acc: acc.append(hwnd)), list)
>     return [ hwnd for hwnd in list if text in fn(hwnd) ]

This doesn't seem like a good idea. Some unrelated window could have the
same text in its title.

Instead of GetWindowText, I'd use win32gui.GetClassName and look for the
window class name you want. You can use Winspector Spy to find the class
name for any window.

There's no guarantee that class names will be unique either, but it's a lot
better than using window titles. You could check both.

A general comment about closing applications: As you know, many apps will
put up a confirmation dialog box when you close them. In your Notepad test,
what if the user had edited the text? Notepad would put up a "do you want to
save" dialog and your code would take the assert. VMware always puts up a
confirmation message box if the VM is running. SendMessage(WM_CLOSE) would
avoid this problem by waiting for the WM_CLOSE message to return.

-Mike





More information about the Python-list mailing list