2 # Copyright (c) 2011 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
6 """SiteCompare module for invoking, locating, and manipulating windows.
8 This module is a catch-all wrapper for operating system UI functionality
9 that doesn't belong in other modules. It contains functions for finding
10 particular windows, scraping their contents, and invoking processes to
25 def FindChildWindows(hwnd
, path
):
26 """Find a set of windows through a path specification.
29 hwnd: Handle of the parent window
30 path: Path to the window to find. Has the following form:
31 "foo/bar/baz|foobar/|foobarbaz"
32 The slashes specify the "path" to the child window.
33 The text is the window class, a pipe (if present) is a title.
34 * is a wildcard and will find all child windows at that level
37 A list of the windows that were found
39 windows_to_check
= [hwnd
]
41 # The strategy will be to take windows_to_check and use it
42 # to find a list of windows that match the next specification
43 # in the path, then repeat with the list of found windows as the
44 # new list of windows to check
45 for segment
in path
.split("/"):
47 check_values
= segment
.split("|")
49 # check_values is now a list with the first element being
50 # the window class, the second being the window caption.
51 # If the class is absent (or wildcarded) set it to None
52 if check_values
[0] == "*" or not check_values
[0]: check_values
[0] = None
54 # If the window caption is also absent, force it to None as well
55 if len(check_values
) == 1: check_values
.append(None)
57 # Loop through the list of windows to check
58 for window_check
in windows_to_check
:
60 while window_found
!= 0: # lint complains, but 0 != None
61 if window_found
is None: window_found
= 0
63 # Look for the next sibling (or first sibling if window_found is 0)
64 # of window_check with the specified caption and/or class
65 window_found
= win32gui
.FindWindowEx(
66 window_check
, window_found
, check_values
[0], check_values
[1])
67 except pywintypes
.error
, e
:
68 # FindWindowEx() raises error 2 if not found
74 # If FindWindowEx struck gold, add to our list of windows found
75 if window_found
: windows_found
.append(window_found
)
77 # The windows we found become the windows to check for the next segment
78 windows_to_check
= windows_found
83 def FindChildWindow(hwnd
, path
):
84 """Find a window through a path specification.
86 This method is a simple wrapper for FindChildWindows() for the
87 case (the majority case) where you expect to find a single window
90 hwnd: Handle of the parent window
91 path: Path to the window to find. See FindChildWindows()
94 The window that was found
96 return FindChildWindows(hwnd
, path
)[0]
99 def ScrapeWindow(hwnd
, rect
=None):
100 """Scrape a visible window and return its contents as a bitmap.
103 hwnd: handle of the window to scrape
104 rect: rectangle to scrape in client coords, defaults to the whole thing
105 If specified, it's a 4-tuple of (left, top, right, bottom)
108 An Image containing the scraped data
110 # Activate the window
111 SetForegroundWindow(hwnd
)
113 # If no rectangle was specified, use the fill client rectangle
114 if not rect
: rect
= win32gui
.GetClientRect(hwnd
)
116 upper_left
= win32gui
.ClientToScreen(hwnd
, (rect
[0], rect
[1]))
117 lower_right
= win32gui
.ClientToScreen(hwnd
, (rect
[2], rect
[3]))
118 rect
= upper_left
+lower_right
120 return PIL
.ImageGrab
.grab(rect
)
123 def SetForegroundWindow(hwnd
):
124 """Bring a window to the foreground."""
125 win32gui
.SetForegroundWindow(hwnd
)
128 def InvokeAndWait(path
, cmdline
="", timeout
=10, tick
=1.):
129 """Invoke an application and wait for it to bring up a window.
132 path: full path to the executable to invoke
133 cmdline: command line to pass to executable
134 timeout: how long (in seconds) to wait before giving up
135 tick: length of time to wait between checks
138 A tuple of handles to the process and the application's window,
139 or (None, None) if it timed out waiting for the process
142 def EnumWindowProc(hwnd
, ret
):
143 """Internal enumeration func, checks for visibility and proper PID."""
144 if win32gui
.IsWindowVisible(hwnd
): # don't bother even checking hidden wnds
145 pid
= win32process
.GetWindowThreadProcessId(hwnd
)[1]
148 return 0 # 0 means stop enumeration
149 return 1 # 1 means continue enumeration
151 # We don't need to change anything about the startupinfo structure
152 # (the default is quite sufficient) but we need to create it just the
154 sinfo
= win32process
.STARTUPINFO()
156 proc
= win32process
.CreateProcess(
157 path
, # path to new process's executable
158 cmdline
, # application's command line
159 None, # process security attributes (default)
160 None, # thread security attributes (default)
161 False, # inherit parent's handles
163 None, # environment variables
165 sinfo
) # default startup info
167 # Create process returns (prochandle, pid, threadhandle, tid). At
168 # some point we may care about the other members, but for now, all
169 # we're after is the pid
172 # Enumeration APIs can take an arbitrary integer, usually a pointer,
173 # to be passed to the enumeration function. We'll pass a pointer to
174 # a structure containing the PID we're looking for, and an empty out
175 # parameter to hold the found window ID
178 tries_until_timeout
= timeout
/tick
181 # Enumerate top-level windows, look for one with our PID
182 while num_tries
< tries_until_timeout
and ret
[1] is None:
184 win32gui
.EnumWindows(EnumWindowProc
, ret
)
185 except pywintypes
.error
, e
:
186 # error 0 isn't an error, it just meant the enumeration was
193 # TODO(jhaas): Should we throw an exception if we timeout? Or is returning
194 # a window ID of None sufficient?
195 return (proc
[0], ret
[1])
198 def WaitForProcessExit(proc
, timeout
=None):
199 """Waits for a given process to terminate.
202 proc: handle to process
203 timeout: timeout (in seconds). None = wait indefinitely
206 True if process ended, False if timed out
209 timeout
= win32event
.INFINITE
211 # convert sec to msec
214 return (win32event
.WaitForSingleObject(proc
, timeout
) ==
215 win32event
.WAIT_OBJECT_0
)
218 def WaitForThrobber(hwnd
, rect
=None, timeout
=20, tick
=0.1, done
=10):
219 """Wait for a browser's "throbber" (loading animation) to complete.
222 hwnd: window containing the throbber
223 rect: rectangle of the throbber, in client coords. If None, whole window
224 timeout: if the throbber is still throbbing after this long, give up
225 tick: how often to check the throbber
226 done: how long the throbber must be unmoving to be considered done
229 Number of seconds waited, -1 if timed out
231 if not rect
: rect
= win32gui
.GetClientRect(hwnd
)
233 # last_throbber will hold the results of the preceding scrape;
234 # we'll compare it against the current scrape to see if we're throbbing
235 last_throbber
= ScrapeWindow(hwnd
, rect
)
236 start_clock
= time
.clock()
237 timeout_clock
= start_clock
+ timeout
238 last_changed_clock
= start_clock
;
240 while time
.clock() < timeout_clock
:
243 current_throbber
= ScrapeWindow(hwnd
, rect
)
244 if current_throbber
.tostring() != last_throbber
.tostring():
245 last_throbber
= current_throbber
246 last_changed_clock
= time
.clock()
248 if time
.clock() - last_changed_clock
> done
:
249 return last_changed_clock
- start_clock
254 def MoveAndSizeWindow(wnd
, position
=None, size
=None, child
=None):
255 """Moves and/or resizes a window.
257 Repositions and resizes a window. If a child window is provided,
258 the parent window is resized so the child window has the given size
261 wnd: handle of the frame window
262 position: new location for the frame window
263 size: new size for the frame window (or the child window)
264 child: handle of the child window
269 rect
= win32gui
.GetWindowRect(wnd
)
271 if position
is None: position
= (rect
[0], rect
[1])
273 size
= (rect
[2]-rect
[0], rect
[3]-rect
[1])
274 elif child
is not None:
275 child_rect
= win32gui
.GetWindowRect(child
)
276 slop
= (rect
[2]-rect
[0]-child_rect
[2]+child_rect
[0],
277 rect
[3]-rect
[1]-child_rect
[3]+child_rect
[1])
278 size
= (size
[0]+slop
[0], size
[1]+slop
[1])
280 win32gui
.MoveWindow(wnd
, # window to move
281 position
[0], # new x coord
282 position
[1], # new y coord
284 size
[1], # new height
288 def EndProcess(proc
, code
=0):
291 Wraps the OS TerminateProcess call for platform-independence
295 code: process exit code
300 win32process
.TerminateProcess(proc
, code
)
303 def URLtoFilename(url
, path
=None, extension
=None):
304 """Converts a URL to a filename, given a path.
306 This in theory could cause collisions if two URLs differ only
307 in unprintable characters (eg. http://www.foo.com/?bar and
308 http://www.foo.com/:bar. In practice this shouldn't be a problem.
311 url: The URL to convert
312 path: path to the directory to store the file
313 extension: string to append to filename
318 trans
= string
.maketrans(r
'\/:*?"<>|', '_________')
320 if path
is None: path
= ""
321 if extension
is None: extension
= ""
322 if len(path
) > 0 and path
[-1] != '\\': path
+= '\\'
323 url
= url
.translate(trans
)
324 return "%s%s%s" % (path
, url
, extension
)
327 def PreparePath(path
):
328 """Ensures that a given path exists, making subdirectories if necessary.
331 path: fully-qualified path of directory to ensure exists
339 if e
[0] != 17: raise e
# error 17: path already exists
343 PreparePath(r
"c:\sitecompare\scrapes\ie7")
344 # We're being invoked rather than imported. Let's do some tests
346 # Hardcode IE's location for the purpose of this test
347 (proc
, wnd
) = InvokeAndWait(
348 r
"c:\program files\internet explorer\iexplore.exe")
350 # Find the browser pane in the IE window
351 browser
= FindChildWindow(
352 wnd
, "TabWindowClass/Shell DocObject View/Internet Explorer_Server")
354 # Move and size the window
355 MoveAndSizeWindow(wnd
, (0, 0), (1024, 768), browser
)
358 i
= ScrapeWindow(browser
)
365 if __name__
== "__main__":