Add ICU message format support
[chromium-blink-merge.git] / tools / site_compare / drivers / win32 / windowing.py
blob47d63f02272ab208ef4ec705c791ec33e3bf6805
1 #!/usr/bin/env python
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
11 create them.
12 """
14 import os
15 import string
16 import time
18 import PIL.ImageGrab
19 import pywintypes
20 import win32event
21 import win32gui
22 import win32process
25 def FindChildWindows(hwnd, path):
26 """Find a set of windows through a path specification.
28 Args:
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
36 Returns:
37 A list of the windows that were found
38 """
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("/"):
46 windows_found = []
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:
59 window_found = None
60 while window_found != 0: # lint complains, but 0 != None
61 if window_found is None: window_found = 0
62 try:
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
69 if e[0] == 2:
70 window_found = 0
71 else:
72 raise e
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
80 return 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
89 Args:
90 hwnd: Handle of the parent window
91 path: Path to the window to find. See FindChildWindows()
93 Returns:
94 The window that was found
95 """
96 return FindChildWindows(hwnd, path)[0]
99 def ScrapeWindow(hwnd, rect=None):
100 """Scrape a visible window and return its contents as a bitmap.
102 Args:
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)
107 Returns:
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.
131 Args:
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
137 Returns:
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]
146 if pid == ret[0]:
147 ret[1] = hwnd
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
153 # same.
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
162 0, # creation flags
163 None, # environment variables
164 None, # directory
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
170 pid = proc[2]
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
176 ret = [pid, None]
178 tries_until_timeout = timeout/tick
179 num_tries = 0
181 # Enumerate top-level windows, look for one with our PID
182 while num_tries < tries_until_timeout and ret[1] is None:
183 try:
184 win32gui.EnumWindows(EnumWindowProc, ret)
185 except pywintypes.error, e:
186 # error 0 isn't an error, it just meant the enumeration was
187 # terminated early
188 if e[0]: raise e
190 time.sleep(tick)
191 num_tries += 1
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.
201 Args:
202 proc: handle to process
203 timeout: timeout (in seconds). None = wait indefinitely
205 Returns:
206 True if process ended, False if timed out
208 if timeout is None:
209 timeout = win32event.INFINITE
210 else:
211 # convert sec to msec
212 timeout *= 1000
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.
221 Args:
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
228 Returns:
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:
241 time.sleep(tick)
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()
247 else:
248 if time.clock() - last_changed_clock > done:
249 return last_changed_clock - start_clock
251 return -1
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
260 Args:
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
266 Returns:
267 None
269 rect = win32gui.GetWindowRect(wnd)
271 if position is None: position = (rect[0], rect[1])
272 if size is None:
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
283 size[0], # new width
284 size[1], # new height
285 True) # repaint?
288 def EndProcess(proc, code=0):
289 """Ends a process.
291 Wraps the OS TerminateProcess call for platform-independence
293 Args:
294 proc: process ID
295 code: process exit code
297 Returns:
298 None
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.
310 Args:
311 url: The URL to convert
312 path: path to the directory to store the file
313 extension: string to append to filename
315 Returns:
316 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.
330 Args:
331 path: fully-qualified path of directory to ensure exists
333 Returns:
334 None
336 try:
337 os.makedirs(path)
338 except OSError, e:
339 if e[0] != 17: raise e # error 17: path already exists
342 def main():
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)
357 # Take a screenshot
358 i = ScrapeWindow(browser)
360 i.show()
362 EndProcess(proc, 0)
365 if __name__ == "__main__":
366 sys.exit(main())