73
73
'}' : "braceright",
77
_keysym_translations = {
78
'Control' : 'Control_L',
77
87
def __init__(self):
78
self._display = Display()
80
def press(self, keys):
82
Send key press events for every key in the 'keys' string.
84
self.__perform_on_keys(keys, X.KeyPress)
87
def release(self, keys):
89
Send key release events for every key in the 'keys' string.
91
self.__perform_on_keys(keys, X.KeyRelease)
94
def press_and_release(self, keys):
96
Send key press events for every key in the 'keys' string, then send
97
key release events for every key in the 'keys' string.
99
This method is not appropriate for simulating a user typing a string
100
of text, since it presses all the keys, and then releases them all.
105
def type(self, keys):
107
Simulate a user typing the keys specified in 'keys'.
109
Each key will be pressed and released before the next key is processed. If
110
you need to simulate multiple keys being pressed at the same time, use the
111
'press_and_release' method above.
88
self.shifted_keys = [k[1] for k in _DISPLAY._keymap_codes if k]
90
def press(self, keys, delay=0.2):
91
"""Send key press events only.
93
The 'keys' argument must be a string of keys you want
98
presses the 'Alt' and 'F2' keys.
101
if not isinstance(keys, basestring):
102
raise TypeError("'keys' argument must be a string.")
103
logger.debug("Pressing keys %r with delay %f", keys, delay)
104
for key in self.__translate_keys(keys):
105
self.__perform_on_key(key, X.KeyPress)
108
def release(self, keys, delay=0.2):
109
"""Send key release events only.
111
The 'keys' argument must be a string of keys you want
112
released. For example:
116
releases the 'Alt' and 'F2' keys.
119
if not isinstance(keys, basestring):
120
raise TypeError("'keys' argument must be a string.")
121
logger.debug("Releasing keys %r with delay %f", keys, delay)
122
# release keys in the reverse order they were pressed in.
123
keys = self.__translate_keys(keys)
117
def __perform_on_keys(self, keys, event):
126
self.__perform_on_key(key, X.KeyRelease)
129
def press_and_release(self, keys, delay=0.2):
130
"""Press and release all items in 'keys'.
132
This is the same as calling 'press(keys);release(keys)'.
134
The 'keys' argument must be a string of keys you want
135
pressed and released.. For example:
137
press_and_release('Alt+F2'])
139
presses both the 'Alt' and 'F2' keys, and then releases both keys.
143
self.press(keys, delay)
144
self.release(keys, delay)
146
def type(self, string, delay=0.1):
147
"""Simulate a user typing a string of text.
149
Only 'normal' keys can be typed with this method. Control characters
150
(such as 'Alt' will be interpreted as an 'A', and 'l', and a 't').
153
if not isinstance(string, basestring):
154
raise TypeError("'keys' argument must be a string.")
155
logger.debug("Typing text %r", string)
157
self.press(key, delay)
158
self.release(key, delay)
162
"""Generate KeyRelease events for any un-released keys.
164
Make sure you call this at the end of any test to release
165
any keys that were pressed and not released.
169
for keycode in _PRESSED_KEYS:
170
logger.warning("Releasing key %r as part of cleanup call.", keycode)
171
fake_input(_DISPLAY, X.KeyRelease, keycode)
174
def __perform_on_key(self, key, event):
175
if not isinstance(key, basestring):
176
raise TypeError("Key parameter must be a string")
122
for index in range(len(keys)):
125
keycode = self._lame_hardcoded_keycodes[key]
128
elif index < len(keys) and key == '^' and keys[index+1] in self._lame_hardcoded_keycodes:
181
keycode, shift_mask = self.__char_to_keycode(key)
184
fake_input(_DISPLAY, event, 50)
186
if event == X.KeyPress:
187
logger.debug("Sending press event for key: %s", key)
188
_PRESSED_KEYS.append(keycode)
189
elif event == X.KeyRelease:
190
logger.debug("Sending release event for key: %s", key)
191
if keycode in _PRESSED_KEYS:
192
_PRESSED_KEYS.remove(keycode)
132
keycode, shift_mask = self.__char_to_keycode(key)
135
fake_input(self._display, event, 50)
137
fake_input(self._display, event, keycode)
194
logger.warning("Generating release event for keycode %d that was not pressed.", keycode)
196
fake_input(_DISPLAY, event, keycode)
140
199
def __get_keysym(self, key) :
141
200
keysym = XK.string_to_keysym(key)
145
204
# the subsequent display.keysym_to_keycode("numbersign") is 0.
146
205
keysym = XK.string_to_keysym(self._special_X_keysyms[key])
149
208
def __is_shifted(self, key) :
152
if "~!@#$%^&*()_+{}|:\"<>?".find(key) >= 0 :
209
return len(key) == 1 and ord(key) in self.shifted_keys
156
211
def __char_to_keycode(self, key) :
157
212
keysym = self.__get_keysym(key)
158
keycode = self._display.keysym_to_keycode(keysym)
213
keycode = _DISPLAY.keysym_to_keycode(keysym)
159
214
if keycode == 0 :
160
215
print "Sorry, can't map", key
162
217
if (self.__is_shifted(key)) :
163
218
shift_mask = X.ShiftMask
167
221
return keycode, shift_mask
223
def __translate_keys(self, key_string):
224
return [self._keysym_translations.get(k, k) for k in key_string.split('+')]
169
227
class Mouse(object):
170
'''Wrapper around xlib to make moving the mouse easier'''
173
self._display = Display()
228
"""Wrapper around xlib to make moving the mouse easier."""
232
"""Mouse position X coordinate."""
177
233
return self.position()[0]
237
"""Mouse position Y coordinate."""
181
238
return self.position()[1]
183
240
def press(self, button=1):
184
'''Press mouse button at current mouse location'''
185
fake_input(self._display, X.ButtonPress, button)
241
"""Press mouse button at current mouse location."""
242
logger.debug("Pressing mouse button %d", button)
243
_PRESSED_MOUSE_BUTTONS.append(button)
244
fake_input(_DISPLAY, X.ButtonPress, button)
188
247
def release(self, button=1):
189
'''Releases mouse button at current mouse location'''
190
fake_input(self._display, X.ButtonRelease, button)
248
"""Releases mouse button at current mouse location."""
249
logger.debug("Releasing mouse button %d", button)
250
if button in _PRESSED_MOUSE_BUTTONS:
251
_PRESSED_MOUSE_BUTTONS.remove(button)
253
logger.warning("Generating button release event or button %d that was not pressed.", button)
254
fake_input(_DISPLAY, X.ButtonRelease, button)
193
257
def click(self, button=1):
194
'''Click mouse at current location'''
258
"""Click mouse at current location."""
195
259
self.press(button)
197
261
self.release(button)
199
def move(self, x, y, animate=True):
200
'''Moves mouse to location (x, y)'''
201
def perform_move(x, y):
202
fake_input(self._display, X.MotionNotify, x=x, y=y)
263
def move(self, x, y, animate=True, rate=100, time_between_events=0.001):
264
'''Moves mouse to location (x, y, pixels_per_event, time_between_event)'''
265
logger.debug("Moving mouse to position %d,%d %s animation.", x, y,
266
"with" if animate else "without")
268
def perform_move(x, y, sync):
269
fake_input(_DISPLAY, X.MotionNotify, sync, X.CurrentTime, X.NONE, x=x, y=y)
271
sleep(time_between_events)
274
perform_move(x, y, False)
209
276
dest_x, dest_y = x, y
210
277
curr_x, curr_y = self.position()
212
279
# calculate a path from our current position to our destination
213
280
dy = float(curr_y - dest_y)
214
281
dx = float(curr_x - dest_x)
215
slope = dy/dx if dx > 0 else 0
282
slope = dy / dx if dx > 0 else 0
216
283
yint = curr_y - (slope * curr_x)
217
xscale = 1 if dest_x > curr_x else -1
284
xscale = rate if dest_x > curr_x else -rate
219
286
while (int(curr_x) != dest_x):
221
curr_y = int(slope * curr_x + yint) if curr_y > 0 else dest_y
223
perform_move(curr_x, curr_y)
287
target_x = min(curr_x + xscale, dest_x) if dest_x > curr_x else max(curr_x + xscale, dest_x)
288
perform_move(target_x - curr_x, 0, True)
225
291
if (curr_y != dest_y):
226
yscale = 1 if dest_y > curr_y else -1
292
yscale = rate if dest_y > curr_y else -rate
227
293
while (curr_y != dest_y):
229
perform_move(curr_x, curr_y)
294
target_y = min(curr_y + yscale, dest_y) if dest_y > curr_y else max(curr_y + yscale, dest_y)
295
perform_move(0, target_y - curr_y, True)
231
298
def position(self):
232
'''Returns the current position of the mouse pointer'''
233
coord = self._display.screen().root.query_pointer()._data
299
"""Returns the current position of the mouse pointer."""
300
coord = _DISPLAY.screen().root.query_pointer()._data
234
301
x, y = coord["root_x"], coord["root_y"]
238
self.move(16, 13, animate=False)
240
self.move(800, 500, animate=False)
306
"""Put mouse in a known safe state."""
307
global _PRESSED_MOUSE_BUTTONS
308
for btn in _PRESSED_MOUSE_BUTTONS:
309
logger.debug("Releasing mouse button %d as part of cleanup", btn)
310
fake_input(_DISPLAY, X.ButtonRelease, btn)
311
_PRESSED_MOUSE_BUTTONS = []
312
sg = ScreenGeometry()
313
sg.move_mouse_to_monitor(0)
316
class ScreenGeometry:
317
"""Get details about screen geometry."""
320
self._default_screen = gtk.gdk.screen_get_default()
322
def get_num_monitors(self):
323
"""Get the number of monitors attached to the PC."""
324
return self._default_screen.get_n_monitors()
326
def get_primary_monitor(self):
327
return self._default_screen.get_primary_monitor()
329
def get_screen_width(self):
330
return self._default_screen.get_width()
332
def get_screen_height(self):
333
return self._default_screen.get_height()
335
def get_monitor_geometry(self, monitor_number):
336
"""Get the geometry for a particular monitor.
338
Returns a tuple containing (x,y,width,height).
341
if monitor_number >= self.get_num_monitors():
342
raise ValueError('Specified monitor number is out of range.')
343
return tuple(self._default_screen.get_monitor_geometry(monitor_number))
345
def move_mouse_to_monitor(self, monitor_number):
346
"""Move the mouse to the center of the specified monitor."""
347
geo = self.get_monitor_geometry(monitor_number)
348
x = geo[0] + (geo[2] / 2)
349
y = geo[1] + (geo[3] / 2)
350
#dont animate this or it might not get there due to barriers
351
Mouse().move(x, y, False)