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__":