~3v1n0/unity/overlay-border-scale

« back to all changes in this revision

Viewing changes to tests/autopilot/autopilot/emulators/X11.py

  • Committer: Daniel van Vugt
  • Date: 2012-03-14 06:24:18 UTC
  • mfrom: (2108 unity)
  • mto: This revision was merged to the branch mainline in revision 2146.
  • Revision ID: daniel.van.vugt@canonical.com-20120314062418-nprucpbr0m7qky5e
MergedĀ latestĀ lp:unity

Show diffs side-by-side

added added

removed removed

Lines of Context:
2
2
# Copyright 2010 Canonical
3
3
# Author: Alex Launi
4
4
#
5
 
# This program is free software: you can redistribute it and/or modify it 
6
 
# under the terms of the GNU General Public License version 3, as published 
 
5
# This program is free software: you can redistribute it and/or modify it
 
6
# under the terms of the GNU General Public License version 3, as published
7
7
# by the Free Software Foundation.
8
8
#
9
 
# This script is designed to run unity in a test drive manner. It will drive 
 
9
# This script is designed to run unity in a test drive manner. It will drive
10
10
# X and test the GL calls that Unity makes, so that we can easily find out if
11
11
# we are triggering graphics driver/X bugs.
12
12
 
13
 
"""
14
 
A collection of emulators for X11 - namely keyboards and mice. In the future we may 
15
 
also need other devices.
16
 
"""
17
 
 
18
 
 
 
13
"""A collection of emulators for X11 - namely keyboards and mice.
 
14
 
 
15
In the future we may also need other devices.
 
16
 
 
17
"""
 
18
 
 
19
import logging
19
20
from time import sleep
20
21
 
21
22
from Xlib import X
22
23
from Xlib import XK
23
24
from Xlib.display import Display
24
25
from Xlib.ext.xtest import fake_input
 
26
import gtk.gdk
 
27
 
 
28
_PRESSED_KEYS = []
 
29
_PRESSED_MOUSE_BUTTONS = []
 
30
_DISPLAY = Display()
 
31
logger = logging.getLogger(__name__)
 
32
 
25
33
 
26
34
class Keyboard(object):
27
 
    '''Wrapper around xlib to make faking keyboard input possible'''
28
 
    _lame_hardcoded_keycodes = {
29
 
        'A' : 64, 
30
 
        'C' : 37,
31
 
        'S' : 50,
32
 
        'T' : 23,
33
 
        'W' : 133,
34
 
        'U' : 111
35
 
        }
 
35
    """Wrapper around xlib to make faking keyboard input possible."""
36
36
 
37
37
    _special_X_keysyms = {
38
38
        ' ' : "space",
73
73
        '}' : "braceright",
74
74
        '~' : "asciitilde"
75
75
        }
76
 
    
 
76
 
 
77
    _keysym_translations = {
 
78
        'Control' : 'Control_L',
 
79
        'Ctrl' : 'Control_L',
 
80
        'Alt' : 'Alt_L',
 
81
        'Super' : 'Super_L',
 
82
        'Shift' : 'Shift_L',
 
83
        'Enter' : 'Return',
 
84
        'Space' : ' ',
 
85
    }
 
86
 
77
87
    def __init__(self):
78
 
        self._display = Display()
79
 
 
80
 
    def press(self, keys):
81
 
        """
82
 
        Send key press events for every key in the 'keys' string.
83
 
        """
84
 
        self.__perform_on_keys(keys, X.KeyPress)            
85
 
        sleep(0.2)
86
 
 
87
 
    def release(self, keys):
88
 
        """
89
 
        Send key release events for every key in the 'keys' string.
90
 
        """
91
 
        self.__perform_on_keys(keys, X.KeyRelease)
92
 
        sleep(0.2)
93
 
        
94
 
    def press_and_release(self, keys):
95
 
        """
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. 
98
 
 
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. 
101
 
        """
102
 
        self.press(keys)
103
 
        self.release(keys)
104
 
 
105
 
    def type(self, keys):
106
 
        """
107
 
        Simulate a user typing the keys specified in 'keys'. 
108
 
 
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.
112
 
        """
 
88
        self.shifted_keys = [k[1] for k in _DISPLAY._keymap_codes if k]
 
89
 
 
90
    def press(self, keys, delay=0.2):
 
91
        """Send key press events only.
 
92
 
 
93
        The 'keys' argument must be a string of keys you want
 
94
        pressed. For example:
 
95
 
 
96
        press('Alt+F2')
 
97
 
 
98
        presses the 'Alt' and 'F2' keys.
 
99
 
 
100
        """
 
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)
 
106
            sleep(delay)
 
107
 
 
108
    def release(self, keys, delay=0.2):
 
109
        """Send key release events only.
 
110
 
 
111
        The 'keys' argument must be a string of keys you want
 
112
        released. For example:
 
113
 
 
114
        release('Alt+F2')
 
115
 
 
116
        releases the 'Alt' and 'F2' keys.
 
117
 
 
118
        """
 
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)
 
124
        keys.reverse()
113
125
        for key in keys:
114
 
            self.press(key)
115
 
            self.release(key)
116
 
 
117
 
    def __perform_on_keys(self, keys, event):
118
 
        control_key = False
 
126
            self.__perform_on_key(key, X.KeyRelease)
 
127
            sleep(delay)
 
128
 
 
129
    def press_and_release(self, keys, delay=0.2):
 
130
        """Press and release all items in 'keys'.
 
131
 
 
132
        This is the same as calling 'press(keys);release(keys)'.
 
133
 
 
134
        The 'keys' argument must be a string of keys you want
 
135
        pressed and released.. For example:
 
136
 
 
137
        press_and_release('Alt+F2'])
 
138
 
 
139
        presses both the 'Alt' and 'F2' keys, and then releases both keys.
 
140
 
 
141
        """
 
142
 
 
143
        self.press(keys, delay)
 
144
        self.release(keys, delay)
 
145
 
 
146
    def type(self, string, delay=0.1):
 
147
        """Simulate a user typing a string of text.
 
148
 
 
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').
 
151
 
 
152
        """
 
153
        if not isinstance(string, basestring):
 
154
            raise TypeError("'keys' argument must be a string.")
 
155
        logger.debug("Typing text %r", string)
 
156
        for key in string:
 
157
            self.press(key, delay)
 
158
            self.release(key, delay)
 
159
 
 
160
    @staticmethod
 
161
    def cleanup():
 
162
        """Generate KeyRelease events for any un-released keys.
 
163
 
 
164
        Make sure you call this at the end of any test to release
 
165
        any keys that were pressed and not released.
 
166
 
 
167
        """
 
168
        global _PRESSED_KEYS
 
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)
 
172
        _PRESSED_KEYS = []
 
173
 
 
174
    def __perform_on_key(self, key, event):
 
175
        if not isinstance(key, basestring):
 
176
            raise TypeError("Key parameter must be a string")
 
177
 
119
178
        keycode = 0
120
179
        shift_mask = 0
121
180
 
122
 
        for index in range(len(keys)):
123
 
            key = keys[index]
124
 
            if control_key:
125
 
                keycode = self._lame_hardcoded_keycodes[key]
126
 
                shift_mask = 0
127
 
                control_key = False
128
 
            elif index < len(keys) and key == '^' and keys[index+1] in self._lame_hardcoded_keycodes:
129
 
                control_key = True
130
 
                continue
 
181
        keycode, shift_mask = self.__char_to_keycode(key)
 
182
 
 
183
        if shift_mask != 0:
 
184
            fake_input(_DISPLAY, event, 50)
 
185
 
 
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)
131
193
            else:
132
 
                keycode, shift_mask = self.__char_to_keycode(key)
133
 
 
134
 
            if shift_mask != 0:
135
 
                fake_input(self._display, event, 50)
136
 
 
137
 
            fake_input(self._display, event, keycode)
138
 
        self._display.sync()
 
194
                logger.warning("Generating release event for keycode %d that was not pressed.", keycode)
 
195
 
 
196
        fake_input(_DISPLAY, event, keycode)
 
197
        _DISPLAY.sync()
139
198
 
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])
147
206
        return keysym
148
 
        
 
207
 
149
208
    def __is_shifted(self, key) :
150
 
        if key.isupper() :
151
 
            return True
152
 
        if "~!@#$%^&*()_+{}|:\"<>?".find(key) >= 0 :
153
 
            return True
154
 
        return False
 
209
        return len(key) == 1 and ord(key) in self.shifted_keys
155
210
 
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
161
 
            
 
216
 
162
217
        if (self.__is_shifted(key)) :
163
218
            shift_mask = X.ShiftMask
164
219
        else :
165
220
            shift_mask = 0
166
 
 
167
221
        return keycode, shift_mask
168
222
 
 
223
    def __translate_keys(self, key_string):
 
224
        return [self._keysym_translations.get(k, k) for k in key_string.split('+')]
 
225
 
 
226
 
169
227
class Mouse(object):
170
 
    '''Wrapper around xlib to make moving the mouse easier'''
171
 
        
172
 
    def __init__(self):
173
 
        self._display = Display()
 
228
    """Wrapper around xlib to make moving the mouse easier."""
174
229
 
175
230
    @property
176
231
    def x(self):
 
232
        """Mouse position X coordinate."""
177
233
        return self.position()[0]
178
234
 
179
235
    @property
180
236
    def y(self):
 
237
        """Mouse position Y coordinate."""
181
238
        return self.position()[1]
182
 
                
 
239
 
183
240
    def press(self, button=1):
184
 
        '''Press mouse button at current mouse location'''
185
 
        fake_input(self._display, X.ButtonPress, button)
186
 
        self._display.sync()
187
 
                
 
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)
 
245
        _DISPLAY.sync()
 
246
 
188
247
    def release(self, button=1):
189
 
        '''Releases mouse button at current mouse location'''
190
 
        fake_input(self._display, X.ButtonRelease, button)
191
 
        self._display.sync()
192
 
                
 
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)
 
252
        else:
 
253
            logger.warning("Generating button release event or button %d that was not pressed.", button)
 
254
        fake_input(_DISPLAY, X.ButtonRelease, button)
 
255
        _DISPLAY.sync()
 
256
 
193
257
    def click(self, button=1):
194
 
        '''Click mouse at current location'''
 
258
        """Click mouse at current location."""
195
259
        self.press(button)
196
260
        sleep(0.25)
197
261
        self.release(button)
198
 
                
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)
203
 
            self._display.sync()
204
 
            sleep(0.001)
 
262
 
 
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")
 
267
 
 
268
        def perform_move(x, y, sync):
 
269
            fake_input(_DISPLAY, X.MotionNotify, sync, X.CurrentTime, X.NONE, x=x, y=y)
 
270
            _DISPLAY.sync()
 
271
            sleep(time_between_events)
205
272
 
206
273
        if not animate:
207
 
            perform_move(x, y)
208
 
                        
 
274
            perform_move(x, y, False)
 
275
 
209
276
        dest_x, dest_y = x, y
210
277
        curr_x, curr_y = self.position()
211
 
                
 
278
 
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
218
 
        
 
284
        xscale = rate if dest_x > curr_x else -rate
 
285
 
219
286
        while (int(curr_x) != dest_x):
220
 
            curr_x += xscale;
221
 
            curr_y = int(slope * curr_x + yint) if curr_y > 0 else dest_y
222
 
                        
223
 
            perform_move(curr_x, curr_y)
224
 
                        
 
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)
 
289
            curr_x = target_x
 
290
 
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):
228
 
                curr_y += yscale
229
 
                perform_move(curr_x, curr_y)
230
 
                                
 
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)
 
296
                curr_y = target_y
 
297
 
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"]
235
302
        return x, y
236
 
        
237
 
    def reset(self):
238
 
        self.move(16, 13, animate=False)
239
 
        self.click()
240
 
        self.move(800, 500, animate=False)
 
303
 
 
304
    @staticmethod
 
305
    def cleanup():
 
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)
 
314
 
 
315
 
 
316
class ScreenGeometry:
 
317
    """Get details about screen geometry."""
 
318
 
 
319
    def __init__(self):
 
320
        self._default_screen = gtk.gdk.screen_get_default()
 
321
 
 
322
    def get_num_monitors(self):
 
323
        """Get the number of monitors attached to the PC."""
 
324
        return self._default_screen.get_n_monitors()
 
325
 
 
326
    def get_primary_monitor(self):
 
327
        return self._default_screen.get_primary_monitor()
 
328
 
 
329
    def get_screen_width(self):
 
330
        return self._default_screen.get_width()
 
331
 
 
332
    def get_screen_height(self):
 
333
        return self._default_screen.get_height()
 
334
 
 
335
    def get_monitor_geometry(self, monitor_number):
 
336
        """Get the geometry for a particular monitor.
 
337
 
 
338
        Returns a tuple containing (x,y,width,height).
 
339
 
 
340
        """
 
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))
 
344
 
 
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)