indowMover is a little project originally created for a study that involved tracking the gaze of subjects when presented with various images. The study used a special eye-tracker device that comes with control software. The device itself is excellent, but the control software leaves a lot to be desired. In addition to serious memory management issues, it requires images to be presented on the primary display, while the control software runs on a secondary display. This is a problem, because test subjects should not be distracted by other windows popping up during an experiment, or even have to see the taskbar in the background. WindowMover solves the problem by ensuring that only the image presentation runs on the primary display; it moves all other windows to the secondary display automatically. WindowMover works on Windows only, but you could apply the concept and the techniques to other graphical operating systems.
How WindowMover Works
WindowMover works by constantly monitoring all the visible top-level windows. It keeps a list of target windows that should remain on the primary display. When a window shows up on the primary display WindowMover checks if it's on the target list; if not, WindowMover moves the window to the secondary display. That sounds simple enough. I originally estimated it would take me a few hours. It turned out to be more complicated than that. Maximized windows require special treatment, and different screen dimensions for the primary and secondary displays require special adjustment. WindowMover handles such problems automatically.
shows two desktops. The right (primary) desktop is initially empty, but WindowMover has been configured to move FireFox from the secondary to the primary display, as shown in Figure 2
|Figure 1. Before Move: In this screenshot, the primary display on the right is empty, but WindowMover has been configured to move FireFox windows to the primary display.||
|Figure 2. After Move: Here, WindowMover has detected the FireFox window and moved it to the primary desktop. Typically, this essentially happens instantaneously.||
WindowMover is a multi-threaded Python program that uses the win32extensions
package for Python to access the Win32 APIs. It can run for a certain number of seconds or indefinitely. For development and testing purposes it is convenient to run it for a limited number of seconds. When you are sure it is working you may run it with no time limit. A single file, WindowMover.py
, contains the entire program. The program exposes one class called MonitorThread, which runs in its own thread and does the heavy lifting of checking all the top-level windows and moving them as necessary. MonitorThread uses two utility functions, called _getTopLevelWindows()
, which respectively retrieve the list of top-level windows and call a function for each retrieved window in the list.
Enumerating Top-Level Windows
The Windows API allows enumerating top-level windows via a callback API. You call the EnumWindows()
function and provide a callback function, which Windows will call for each window, passing the window handle as a parameter. In general, callback interfaces are efficient but not terribly convenient to work with. It is usually more convenient to work with a list of all the objects that you want to process. In this case, the list is not too big to fit into memory, and you don't need to process the Windows as soon as possible, so the list approach works fine. The _getTopLevelWindows
function retrieves the list:
"""Get all top-level Windows (visible and invisible)"""
windows = 
The name of the module from the win32extensions
package that deals with Windows and other user interface objects is win32gui
. The EnumWindows()
function takes the callback function (_enumWindowsCallback
in this case) and a context object as parameters. Here, the context object is an (initially) empty list that the callback will populate. In the scope of one call to _getTopLevelWindows()
function will be invoked multiple times (once for each window in the list):
def _enumWindowsCallback(hwnd, windows):
className = win32gui.GetClassName(hwnd)
text = win32gui.GetWindowText(hwnd)
windows.append((hwnd, className, text))
The callback function is invoked with the hwnd
(window handle) that uniquely identifies every top-level window. The hwnd
provides the function with additional information about each window (its class name and title), which it stores in the list passed in as the context object (the second parameter in the preceding code).