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.
5
6"""SiteCompare module for simulating mouse input.
7
8This module contains functions that can be used to simulate a user
9navigating using a pointing device. This includes mouse movement,
10clicking with any button, and dragging.
11"""
12
13import time                 # for sleep
14
15import win32api             # for mouse_event
16import win32con             # Windows constants
17import win32gui             # for window functions
18
19
20def ScreenToMouse(pt):
21  """Convert a value in screen coordinates to mouse coordinates.
22
23  Mouse coordinates are specified as a percentage of screen dimensions,
24  normalized to 16 bits. 0 represents the far left/top of the screen,
25  65535 represents the far right/bottom. This function assumes that
26  the size of the screen is fixed at module load time and does not change
27
28  Args:
29    pt: the point of the coords to convert
30
31  Returns:
32    the converted point
33  """
34
35  # Initialize the screen dimensions on first execution. Note that this
36  # function assumes that the screen dimensions do not change during run.
37  if not ScreenToMouse._SCREEN_DIMENSIONS:
38    desktop = win32gui.GetClientRect(win32gui.GetDesktopWindow())
39    ScreenToMouse._SCREEN_DIMENSIONS = (desktop[2], desktop[3])
40
41  return ((65535 * pt[0]) / ScreenToMouse._SCREEN_DIMENSIONS[0],
42          (65535 * pt[1]) / ScreenToMouse._SCREEN_DIMENSIONS[1])
43
44ScreenToMouse._SCREEN_DIMENSIONS = None
45
46
47def PressButton(down, button='left'):
48  """Simulate a mouse button press or release at the current mouse location.
49
50  Args:
51    down: whether the button is pressed or released
52    button: which button is pressed
53
54  Returns:
55    None
56  """
57
58  # Put the mouse_event flags in a convenient dictionary by button
59  flags = {
60    'left':   (win32con.MOUSEEVENTF_LEFTUP,   win32con.MOUSEEVENTF_LEFTDOWN),
61    'middle': (win32con.MOUSEEVENTF_MIDDLEUP, win32con.MOUSEEVENTF_MIDDLEDOWN),
62    'right':  (win32con.MOUSEEVENTF_RIGHTUP,  win32con.MOUSEEVENTF_RIGHTDOWN)
63    }
64
65  # hit the button
66  win32api.mouse_event(flags[button][down], 0, 0)
67
68
69def ClickButton(button='left', click_time=0):
70  """Press and release a mouse button at the current mouse location.
71
72  Args:
73    button: which button to click
74    click_time: duration between press and release
75
76  Returns:
77    None
78  """
79  PressButton(True, button)
80  time.sleep(click_time)
81  PressButton(False, button)
82
83
84def DoubleClickButton(button='left', click_time=0, time_between_clicks=0):
85  """Double-click a mouse button at the current mouse location.
86
87  Args:
88    button: which button to click
89    click_time: duration between press and release
90    time_between_clicks: time to pause between clicks
91
92  Returns:
93    None
94  """
95  ClickButton(button, click_time)
96  time.sleep(time_between_clicks)
97  ClickButton(button, click_time)
98
99
100def MoveToLocation(pos, duration=0, tick=0.01):
101  """Move the mouse cursor to a specified location, taking the specified time.
102
103  Args:
104    pos: position (in screen coordinates) to move to
105    duration: amount of time the move should take
106    tick: amount of time between successive moves of the mouse
107
108  Returns:
109    None
110  """
111  # calculate the number of moves to reach the destination
112  num_steps = (duration/tick)+1
113
114  # get the current and final mouse position in mouse coords
115  current_location = ScreenToMouse(win32gui.GetCursorPos())
116  end_location = ScreenToMouse(pos)
117
118  # Calculate the step size
119  step_size = ((end_location[0]-current_location[0])/num_steps,
120               (end_location[1]-current_location[1])/num_steps)
121  step = 0
122
123  while step < num_steps:
124    # Move the mouse one step
125    current_location = (current_location[0]+step_size[0],
126                        current_location[1]+step_size[1])
127
128    # Coerce the coords to int to avoid a warning from pywin32
129    win32api.mouse_event(
130      win32con.MOUSEEVENTF_MOVE|win32con.MOUSEEVENTF_ABSOLUTE,
131      int(current_location[0]), int(current_location[1]))
132
133    step += 1
134    time.sleep(tick)
135
136
137def ClickAtLocation(pos, button='left', click_time=0):
138  """Simulate a mouse click in a particular location, in screen coordinates.
139
140  Args:
141    pos: position in screen coordinates (x,y)
142    button: which button to click
143    click_time: duration of the click
144
145  Returns:
146    None
147  """
148  MoveToLocation(pos)
149  ClickButton(button, click_time)
150
151
152def ClickInWindow(hwnd, offset=None, button='left', click_time=0):
153  """Simulate a user mouse click in the center of a window.
154
155  Args:
156    hwnd: handle of the window to click in
157    offset: where to click, defaults to dead center
158    button: which button to click
159    click_time: duration of the click
160
161  Returns:
162    Nothing
163  """
164
165  rect = win32gui.GetClientRect(hwnd)
166  if offset is None: offset = (rect[2]/2, rect[3]/2)
167
168  # get the screen coordinates of the window's center
169  pos = win32gui.ClientToScreen(hwnd, offset)
170
171  ClickAtLocation(pos, button, click_time)
172
173
174def DoubleClickInWindow(
175  hwnd, offset=None, button='left', click_time=0, time_between_clicks=0.1):
176  """Simulate a user mouse double click in the center of a window.
177
178  Args:
179    hwnd: handle of the window to click in
180    offset: where to click, defaults to dead center
181    button: which button to click
182    click_time: duration of the clicks
183    time_between_clicks: length of time to pause between clicks
184
185  Returns:
186    Nothing
187  """
188  ClickInWindow(hwnd, offset, button, click_time)
189  time.sleep(time_between_clicks)
190  ClickInWindow(hwnd, offset, button, click_time)
191
192
193def main():
194  # We're being invoked rather than imported. Let's do some tests
195
196  screen_size = win32gui.GetClientRect(win32gui.GetDesktopWindow())
197  screen_size = (screen_size[2], screen_size[3])
198
199  # move the mouse (instantly) to the upper right corner
200  MoveToLocation((screen_size[0], 0))
201
202  # move the mouse (over five seconds) to the lower left corner
203  MoveToLocation((0, screen_size[1]), 5)
204
205  # click the left mouse button. This will open up the Start menu
206  # if the taskbar is at the bottom
207
208  ClickButton()
209
210  # wait a bit, then click the right button to open the context menu
211  time.sleep(3)
212  ClickButton('right')
213
214  # move the mouse away and then click the left button to dismiss the
215  # context menu
216  MoveToLocation((screen_size[0]/2, screen_size[1]/2), 3)
217  MoveToLocation((0, 0), 3)
218  ClickButton()
219
220
221if __name__ == "__main__":
222  sys.exit(main())
223