Merge lp:~elopio/autopilot/no_uinput_side-effects2 into lp:~elopio/autopilot/no_uinput_side-effects
- no_uinput_side-effects2
- Merge into no_uinput_side-effects
Proposed by
Leo Arias
Status: | Superseded |
---|---|
Proposed branch: | lp:~elopio/autopilot/no_uinput_side-effects2 |
Merge into: | lp:~elopio/autopilot/no_uinput_side-effects |
Diff against target: |
680 lines (+434/-146) 2 files modified
autopilot/input/_uinput.py (+173/-146) autopilot/tests/unit/test_input.py (+261/-0) |
To merge this branch: | bzr merge lp:~elopio/autopilot/no_uinput_side-effects2 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Autopilot Hackers | Pending | ||
Review via email: mp+202414@code.launchpad.net |
This proposal supersedes a proposal from 2014-01-21.
This proposal has been superseded by a proposal from 2014-01-21.
Commit message
Not yet ready, just teasing jenkins.
Description of the change
To post a comment you must log in.
- 425. By Leo Arias
-
Added coverate for uinput.Touch.
- 426. By Leo Arias
-
Create the Touch device with a dummy resolution.
- 427. By Leo Arias
-
Fixed pep8.
- 428. By Leo Arias
-
Make sure that the keyboard mock will be used.
- 429. By Leo Arias
-
updated the pressed keys list.
- 430. By Leo Arias
-
Typo.
- 431. By Leo Arias
-
updated the pressed keys list.
- 432. By Leo Arias
-
Fixed the release all.
- 433. By Leo Arias
-
Added a unit test for the previous bug.
- 434. By Leo Arias
-
Added the failing test case.
- 435. By Leo Arias
-
Fixed the test with multiple fingers.
Unmerged revisions
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'autopilot/input/_uinput.py' | |||
2 | --- autopilot/input/_uinput.py 2014-01-21 04:44:16 +0000 | |||
3 | +++ autopilot/input/_uinput.py 2014-01-21 09:50:21 +0000 | |||
4 | @@ -20,10 +20,10 @@ | |||
5 | 20 | 20 | ||
6 | 21 | """UInput device drivers.""" | 21 | """UInput device drivers.""" |
7 | 22 | 22 | ||
8 | 23 | from autopilot import utilities | ||
9 | 23 | from autopilot.input import Keyboard as KeyboardBase | 24 | from autopilot.input import Keyboard as KeyboardBase |
10 | 24 | from autopilot.input import Touch as TouchBase | 25 | from autopilot.input import Touch as TouchBase |
11 | 25 | from autopilot.input._common import get_center_point | 26 | from autopilot.input._common import get_center_point |
12 | 26 | from autopilot.utilities import sleep | ||
13 | 27 | import autopilot.platform | 27 | import autopilot.platform |
14 | 28 | 28 | ||
15 | 29 | import logging | 29 | import logging |
16 | @@ -135,7 +135,7 @@ | |||
17 | 135 | for key in self._sanitise_keys(keys): | 135 | for key in self._sanitise_keys(keys): |
18 | 136 | for key_button in self._get_key_buttons(key): | 136 | for key_button in self._get_key_buttons(key): |
19 | 137 | self._device.press(key_button) | 137 | self._device.press(key_button) |
21 | 138 | sleep(delay) | 138 | utilities.sleep(delay) |
22 | 139 | 139 | ||
23 | 140 | def release(self, keys, delay=0.1): | 140 | def release(self, keys, delay=0.1): |
24 | 141 | """Send key release events only. | 141 | """Send key release events only. |
25 | @@ -156,7 +156,7 @@ | |||
26 | 156 | for key in reversed(self._sanitise_keys(keys)): | 156 | for key in reversed(self._sanitise_keys(keys)): |
27 | 157 | for key_button in reversed(self._get_key_buttons(key)): | 157 | for key_button in reversed(self._get_key_buttons(key)): |
28 | 158 | self._device.release(key_button) | 158 | self._device.release(key_button) |
30 | 159 | sleep(delay) | 159 | utilities.sleep(delay) |
31 | 160 | 160 | ||
32 | 161 | def press_and_release(self, keys, delay=0.1): | 161 | def press_and_release(self, keys, delay=0.1): |
33 | 162 | """Press and release all items in 'keys'. | 162 | """Press and release all items in 'keys'. |
34 | @@ -214,67 +214,16 @@ | |||
35 | 214 | return key_buttons | 214 | return key_buttons |
36 | 215 | 215 | ||
37 | 216 | 216 | ||
47 | 217 | last_tracking_id = 0 | 217 | @utilities.deprecated('Touch') |
39 | 218 | |||
40 | 219 | |||
41 | 220 | def get_next_tracking_id(): | ||
42 | 221 | global last_tracking_id | ||
43 | 222 | last_tracking_id += 1 | ||
44 | 223 | return last_tracking_id | ||
45 | 224 | |||
46 | 225 | |||
48 | 226 | def create_touch_device(res_x=None, res_y=None): | 218 | def create_touch_device(res_x=None, res_y=None): |
49 | 227 | """Create and return a UInput touch device. | 219 | """Create and return a UInput touch device. |
50 | 228 | 220 | ||
51 | 229 | If res_x and res_y are not specified, they will be queried from the system. | 221 | If res_x and res_y are not specified, they will be queried from the system. |
52 | 230 | 222 | ||
53 | 231 | """ | 223 | """ |
100 | 232 | 224 | return UInput(events=_get_touch_events(), name='autopilot-finger', | |
101 | 233 | if res_x is None or res_y is None: | 225 | version=0x2, devnode=_get_devnode_path()) |
102 | 234 | from autopilot.display import Display | 226 | |
57 | 235 | display = Display.create() | ||
58 | 236 | # TODO: This calculation needs to become part of the display module: | ||
59 | 237 | l = r = t = b = 0 | ||
60 | 238 | for screen in range(display.get_num_screens()): | ||
61 | 239 | geometry = display.get_screen_geometry(screen) | ||
62 | 240 | if geometry[0] < l: | ||
63 | 241 | l = geometry[0] | ||
64 | 242 | if geometry[1] < t: | ||
65 | 243 | t = geometry[1] | ||
66 | 244 | if geometry[0] + geometry[2] > r: | ||
67 | 245 | r = geometry[0] + geometry[2] | ||
68 | 246 | if geometry[1] + geometry[3] > b: | ||
69 | 247 | b = geometry[1] + geometry[3] | ||
70 | 248 | res_x = r - l | ||
71 | 249 | res_y = b - t | ||
72 | 250 | |||
73 | 251 | # android uses BTN_TOOL_FINGER, whereas desktop uses BTN_TOUCH. I have no | ||
74 | 252 | # idea why... | ||
75 | 253 | touch_tool = e.BTN_TOOL_FINGER | ||
76 | 254 | if autopilot.platform.model() == 'Desktop': | ||
77 | 255 | touch_tool = e.BTN_TOUCH | ||
78 | 256 | |||
79 | 257 | cap_mt = { | ||
80 | 258 | e.EV_ABS: [ | ||
81 | 259 | (e.ABS_X, (0, res_x, 0, 0)), | ||
82 | 260 | (e.ABS_Y, (0, res_y, 0, 0)), | ||
83 | 261 | (e.ABS_PRESSURE, (0, 65535, 0, 0)), | ||
84 | 262 | (e.ABS_MT_POSITION_X, (0, res_x, 0, 0)), | ||
85 | 263 | (e.ABS_MT_POSITION_Y, (0, res_y, 0, 0)), | ||
86 | 264 | (e.ABS_MT_TOUCH_MAJOR, (0, 30, 0, 0)), | ||
87 | 265 | (e.ABS_MT_TRACKING_ID, (0, 65535, 0, 0)), | ||
88 | 266 | (e.ABS_MT_PRESSURE, (0, 255, 0, 0)), | ||
89 | 267 | (e.ABS_MT_SLOT, (0, 9, 0, 0)), | ||
90 | 268 | ], | ||
91 | 269 | e.EV_KEY: [ | ||
92 | 270 | touch_tool, | ||
93 | 271 | ] | ||
94 | 272 | } | ||
95 | 273 | |||
96 | 274 | return UInput(cap_mt, name='autopilot-finger', version=0x2, | ||
97 | 275 | devnode=_get_devnode_path()) | ||
98 | 276 | |||
99 | 277 | _touch_device = create_touch_device() | ||
103 | 278 | 227 | ||
104 | 279 | # Multiouch notes: | 228 | # Multiouch notes: |
105 | 280 | # ---------------- | 229 | # ---------------- |
106 | @@ -319,58 +268,177 @@ | |||
107 | 319 | # about this is that the SLOT refers to a finger number, and the TRACKING_ID | 268 | # about this is that the SLOT refers to a finger number, and the TRACKING_ID |
108 | 320 | # identifies a unique touch for the duration of it's existance. | 269 | # identifies a unique touch for the duration of it's existance. |
109 | 321 | 270 | ||
143 | 322 | _touch_fingers_in_use = [] | 271 | |
144 | 323 | 272 | def _get_touch_events(res_x, res_y): | |
145 | 324 | 273 | if res_x is None or res_y is None: | |
146 | 325 | def _get_touch_finger(): | 274 | res_x, res_y = _get_system_resolution() |
147 | 326 | """Claim a touch finger id for use. | 275 | |
148 | 327 | 276 | touch_tool = _get_touch_tool() | |
149 | 328 | :raises: RuntimeError if no more fingers are available. | 277 | |
150 | 329 | 278 | events = { | |
151 | 330 | """ | 279 | e.EV_ABS: [ |
152 | 331 | global _touch_fingers_in_use | 280 | (e.ABS_X, (0, res_x, 0, 0)), |
153 | 332 | 281 | (e.ABS_Y, (0, res_y, 0, 0)), | |
154 | 333 | for i in range(9): | 282 | (e.ABS_PRESSURE, (0, 65535, 0, 0)), |
155 | 334 | if i not in _touch_fingers_in_use: | 283 | (e.ABS_MT_POSITION_X, (0, res_x, 0, 0)), |
156 | 335 | _touch_fingers_in_use.append(i) | 284 | (e.ABS_MT_POSITION_Y, (0, res_y, 0, 0)), |
157 | 336 | return i | 285 | (e.ABS_MT_TOUCH_MAJOR, (0, 30, 0, 0)), |
158 | 337 | raise RuntimeError("All available fingers have been used already.") | 286 | (e.ABS_MT_TRACKING_ID, (0, 65535, 0, 0)), |
159 | 338 | 287 | (e.ABS_MT_PRESSURE, (0, 255, 0, 0)), | |
160 | 339 | 288 | (e.ABS_MT_SLOT, (0, 9, 0, 0)), | |
161 | 340 | def _release_touch_finger(finger_num): | 289 | ], |
162 | 341 | """Relase a previously-claimed finger id. | 290 | e.EV_KEY: [ |
163 | 342 | 291 | touch_tool, | |
164 | 343 | :raises: RuntimeError if the finger given was never claimed, or was already | 292 | ] |
165 | 344 | released. | 293 | } |
166 | 345 | 294 | return events | |
167 | 346 | """ | 295 | |
168 | 347 | global _touch_fingers_in_use | 296 | |
169 | 348 | 297 | def _get_system_resolution(): | |
170 | 349 | if finger_num not in _touch_fingers_in_use: | 298 | from autopilot.display import Display |
171 | 350 | raise RuntimeError( | 299 | display = Display.create() |
172 | 351 | "Finger %d was never claimed, or has already been released." % | 300 | # TODO: This calculation needs to become part of the display module: |
173 | 352 | (finger_num)) | 301 | l = r = t = b = 0 |
174 | 353 | _touch_fingers_in_use.remove(finger_num) | 302 | for screen in range(display.get_num_screens()): |
175 | 354 | assert(finger_num not in _touch_fingers_in_use) | 303 | geometry = display.get_screen_geometry(screen) |
176 | 304 | if geometry[0] < l: | ||
177 | 305 | l = geometry[0] | ||
178 | 306 | if geometry[1] < t: | ||
179 | 307 | t = geometry[1] | ||
180 | 308 | if geometry[0] + geometry[2] > r: | ||
181 | 309 | r = geometry[0] + geometry[2] | ||
182 | 310 | if geometry[1] + geometry[3] > b: | ||
183 | 311 | b = geometry[1] + geometry[3] | ||
184 | 312 | res_x = r - l | ||
185 | 313 | res_y = b - t | ||
186 | 314 | return res_x, res_y | ||
187 | 315 | |||
188 | 316 | |||
189 | 317 | def _get_touch_tool(): | ||
190 | 318 | # android uses BTN_TOOL_FINGER, whereas desktop uses BTN_TOUCH. I have | ||
191 | 319 | # no idea why... | ||
192 | 320 | if autopilot.platform.model() == 'Desktop': | ||
193 | 321 | touch_tool = e.BTN_TOUCH | ||
194 | 322 | else: | ||
195 | 323 | touch_tool = e.BTN_TOOL_FINGER | ||
196 | 324 | return touch_tool | ||
197 | 325 | |||
198 | 326 | |||
199 | 327 | class _UInputTouchDevice(object): | ||
200 | 328 | """Wrapper for the UInput Touch to execute its primitives. | ||
201 | 329 | |||
202 | 330 | If res_x and res_y are not specified, they will be queried from the system. | ||
203 | 331 | |||
204 | 332 | """ | ||
205 | 333 | |||
206 | 334 | _touch_fingers_in_use = [] | ||
207 | 335 | _max_number_of_fingers = 9 | ||
208 | 336 | _last_tracking_id = 0 | ||
209 | 337 | |||
210 | 338 | def __init__(self, res_x=None, res_y=None, device_class=UInput): | ||
211 | 339 | super(_UInputTouchDevice, self).__init__() | ||
212 | 340 | self._device = device_class( | ||
213 | 341 | events=_get_touch_events(res_x, res_y), name='autopilot-finger', | ||
214 | 342 | version=0x2, devnode=_get_devnode_path()) | ||
215 | 343 | self._touch_finger_slot = None | ||
216 | 344 | |||
217 | 345 | @property | ||
218 | 346 | def pressed(self): | ||
219 | 347 | return self._touch_finger_slot is not None | ||
220 | 348 | |||
221 | 349 | def finger_down(self, x, y): | ||
222 | 350 | """Internal: moves finger "finger" down on the touchscreen. | ||
223 | 351 | |||
224 | 352 | :param x: The finger will be moved to this x coordinate. | ||
225 | 353 | :param y: The finger will be moved to this y coordinate. | ||
226 | 354 | |||
227 | 355 | """ | ||
228 | 356 | if self.pressed: | ||
229 | 357 | raise RuntimeError("Cannot press finger: it's already pressed.") | ||
230 | 358 | self._touch_finger_slot = self._get_free_touch_finger_slot() | ||
231 | 359 | |||
232 | 360 | self._device.write(e.EV_ABS, e.ABS_MT_SLOT, self._touch_finger_slot) | ||
233 | 361 | self._device.write( | ||
234 | 362 | e.EV_ABS, e.ABS_MT_TRACKING_ID, self._get_next_tracking_id()) | ||
235 | 363 | press_value = 1 | ||
236 | 364 | self._device.write(e.EV_KEY, e.BTN_TOOL_FINGER, press_value) | ||
237 | 365 | self._device.write(e.EV_ABS, e.ABS_MT_POSITION_X, int(x)) | ||
238 | 366 | self._device.write(e.EV_ABS, e.ABS_MT_POSITION_Y, int(y)) | ||
239 | 367 | self._device.write(e.EV_ABS, e.ABS_MT_PRESSURE, 400) | ||
240 | 368 | self._device.syn() | ||
241 | 369 | |||
242 | 370 | def _get_free_touch_finger_slot(self): | ||
243 | 371 | """Return the id of a free touch finger. | ||
244 | 372 | |||
245 | 373 | :raises: RuntimeError if no more fingers are available. | ||
246 | 374 | |||
247 | 375 | """ | ||
248 | 376 | for i in range(_UInputTouchDevice._max_number_of_fingers): | ||
249 | 377 | if i not in _UInputTouchDevice._touch_fingers_in_use: | ||
250 | 378 | _UInputTouchDevice._touch_fingers_in_use.append(i) | ||
251 | 379 | return i | ||
252 | 380 | raise RuntimeError('All available fingers have been used already.') | ||
253 | 381 | |||
254 | 382 | def _get_next_tracking_id(self): | ||
255 | 383 | _UInputTouchDevice._last_tracking_id += 1 | ||
256 | 384 | return _UInputTouchDevice._last_tracking_id | ||
257 | 385 | |||
258 | 386 | def finger_move(self, x, y): | ||
259 | 387 | """Internal: moves finger "finger" on the touchscreen to pos (x,y) | ||
260 | 388 | |||
261 | 389 | NOTE: The finger has to be down for this to have any effect. | ||
262 | 390 | |||
263 | 391 | """ | ||
264 | 392 | if not self.pressed: | ||
265 | 393 | raise RuntimeError('Attempting to move without finger being down.') | ||
266 | 394 | else: | ||
267 | 395 | self._device.write( | ||
268 | 396 | e.EV_ABS, e.ABS_MT_SLOT, self._touch_finger_slot) | ||
269 | 397 | self._device.write(e.EV_ABS, e.ABS_MT_POSITION_X, int(x)) | ||
270 | 398 | self._device.write(e.EV_ABS, e.ABS_MT_POSITION_Y, int(y)) | ||
271 | 399 | self._device.syn() | ||
272 | 400 | |||
273 | 401 | def finger_up(self): | ||
274 | 402 | """Internal: moves finger "finger" up from the touchscreen""" | ||
275 | 403 | if not self.pressed: | ||
276 | 404 | raise RuntimeError("Cannot release finger: it's not pressed.") | ||
277 | 405 | self._device.write(e.EV_ABS, e.ABS_MT_SLOT, self._touch_finger_slot) | ||
278 | 406 | lift_tracking_id = -1 | ||
279 | 407 | self._device.write(e.EV_ABS, e.ABS_MT_TRACKING_ID, lift_tracking_id) | ||
280 | 408 | release_value = 0 | ||
281 | 409 | self._device.write(e.EV_KEY, e.BTN_TOOL_FINGER, release_value) | ||
282 | 410 | self._device.syn() | ||
283 | 411 | self._release_touch_finger() | ||
284 | 412 | |||
285 | 413 | def _release_touch_finger(self): | ||
286 | 414 | """Release the touch finger.""" | ||
287 | 415 | if (self._touch_finger_slot not in | ||
288 | 416 | _UInputTouchDevice._touch_fingers_in_use): | ||
289 | 417 | raise RuntimeError( | ||
290 | 418 | "Finger %d was never claimed, or has already been released." % | ||
291 | 419 | self._touch_finger_slot) | ||
292 | 420 | _UInputTouchDevice._touch_fingers_in_use.remove( | ||
293 | 421 | self._touch_finger_slot) | ||
294 | 422 | self._touch_finger_slot = None | ||
295 | 355 | 423 | ||
296 | 356 | 424 | ||
297 | 357 | class Touch(TouchBase): | 425 | class Touch(TouchBase): |
298 | 358 | """Low level interface to generate single finger touch events.""" | 426 | """Low level interface to generate single finger touch events.""" |
299 | 359 | 427 | ||
301 | 360 | def __init__(self): | 428 | def __init__(self, device_class=_UInputTouchDevice): |
302 | 361 | super(Touch, self).__init__() | 429 | super(Touch, self).__init__() |
304 | 362 | self._touch_finger = None | 430 | Touch._device = device_class() |
305 | 363 | 431 | ||
306 | 364 | @property | 432 | @property |
307 | 365 | def pressed(self): | 433 | def pressed(self): |
309 | 366 | return self._touch_finger is not None | 434 | return self._device.pressed |
310 | 367 | 435 | ||
311 | 368 | def tap(self, x, y): | 436 | def tap(self, x, y): |
312 | 369 | """Click (or 'tap') at given x and y coordinates.""" | 437 | """Click (or 'tap') at given x and y coordinates.""" |
313 | 370 | logger.debug("Tapping at: %d,%d", x, y) | 438 | logger.debug("Tapping at: %d,%d", x, y) |
317 | 371 | self._finger_down(x, y) | 439 | self._device.finger_down(x, y) |
318 | 372 | sleep(0.1) | 440 | utilities.sleep(0.1) |
319 | 373 | self._finger_up() | 441 | self._device.finger_up() |
320 | 374 | 442 | ||
321 | 375 | def tap_object(self, object): | 443 | def tap_object(self, object): |
322 | 376 | """Click (or 'tap') a given object""" | 444 | """Click (or 'tap') a given object""" |
323 | @@ -382,12 +450,12 @@ | |||
324 | 382 | """Press and hold a given object or at the given coordinates | 450 | """Press and hold a given object or at the given coordinates |
325 | 383 | Call release() when the object has been pressed long enough""" | 451 | Call release() when the object has been pressed long enough""" |
326 | 384 | logger.debug("Pressing at: %d,%d", x, y) | 452 | logger.debug("Pressing at: %d,%d", x, y) |
328 | 385 | self._finger_down(x, y) | 453 | self._device.finger_down(x, y) |
329 | 386 | 454 | ||
330 | 387 | def release(self): | 455 | def release(self): |
331 | 388 | """Release a previously pressed finger""" | 456 | """Release a previously pressed finger""" |
332 | 389 | logger.debug("Releasing") | 457 | logger.debug("Releasing") |
334 | 390 | self._finger_up() | 458 | self._device.finger_up() |
335 | 391 | 459 | ||
336 | 392 | def move(self, x, y): | 460 | def move(self, x, y): |
337 | 393 | """Moves the pointing "finger" to pos(x,y). | 461 | """Moves the pointing "finger" to pos(x,y). |
338 | @@ -395,14 +463,12 @@ | |||
339 | 395 | NOTE: The finger has to be down for this to have any effect. | 463 | NOTE: The finger has to be down for this to have any effect. |
340 | 396 | 464 | ||
341 | 397 | """ | 465 | """ |
345 | 398 | if self._touch_finger is None: | 466 | self._device.finger_move(x, y) |
343 | 399 | raise RuntimeError("Attempting to move without finger being down.") | ||
344 | 400 | self._finger_move(x, y) | ||
346 | 401 | 467 | ||
347 | 402 | def drag(self, x1, y1, x2, y2): | 468 | def drag(self, x1, y1, x2, y2): |
348 | 403 | """Perform a drag gesture from (x1,y1) to (x2,y2)""" | 469 | """Perform a drag gesture from (x1,y1) to (x2,y2)""" |
349 | 404 | logger.debug("Dragging from %d,%d to %d,%d", x1, y1, x2, y2) | 470 | logger.debug("Dragging from %d,%d to %d,%d", x1, y1, x2, y2) |
351 | 405 | self._finger_down(x1, y1) | 471 | self._device.finger_down(x1, y1) |
352 | 406 | 472 | ||
353 | 407 | # Let's drag in 100 steps for now... | 473 | # Let's drag in 100 steps for now... |
354 | 408 | dx = 1.0 * (x2 - x1) / 100 | 474 | dx = 1.0 * (x2 - x1) / 100 |
355 | @@ -410,52 +476,13 @@ | |||
356 | 410 | cur_x = x1 + dx | 476 | cur_x = x1 + dx |
357 | 411 | cur_y = y1 + dy | 477 | cur_y = y1 + dy |
358 | 412 | for i in range(0, 100): | 478 | for i in range(0, 100): |
361 | 413 | self._finger_move(int(cur_x), int(cur_y)) | 479 | self._device.finger_move(int(cur_x), int(cur_y)) |
362 | 414 | sleep(0.002) | 480 | utilities.sleep(0.002) |
363 | 415 | cur_x += dx | 481 | cur_x += dx |
364 | 416 | cur_y += dy | 482 | cur_y += dy |
365 | 417 | # Make sure we actually end up at target | 483 | # Make sure we actually end up at target |
407 | 418 | self._finger_move(x2, y2) | 484 | self._device.finger_move(x2, y2) |
408 | 419 | self._finger_up() | 485 | self._device.finger_up() |
368 | 420 | |||
369 | 421 | def _finger_down(self, x, y): | ||
370 | 422 | """Internal: moves finger "finger" down on the touchscreen. | ||
371 | 423 | |||
372 | 424 | :param x: The finger will be moved to this x coordinate. | ||
373 | 425 | :param y: The finger will be moved to this y coordinate. | ||
374 | 426 | |||
375 | 427 | """ | ||
376 | 428 | if self._touch_finger is not None: | ||
377 | 429 | raise RuntimeError("Cannot press finger: it's already pressed.") | ||
378 | 430 | self._touch_finger = _get_touch_finger() | ||
379 | 431 | |||
380 | 432 | _touch_device.write(e.EV_ABS, e.ABS_MT_SLOT, self._touch_finger) | ||
381 | 433 | _touch_device.write( | ||
382 | 434 | e.EV_ABS, e.ABS_MT_TRACKING_ID, get_next_tracking_id()) | ||
383 | 435 | _touch_device.write(e.EV_KEY, e.BTN_TOOL_FINGER, 1) | ||
384 | 436 | _touch_device.write(e.EV_ABS, e.ABS_MT_POSITION_X, int(x)) | ||
385 | 437 | _touch_device.write(e.EV_ABS, e.ABS_MT_POSITION_Y, int(y)) | ||
386 | 438 | _touch_device.write(e.EV_ABS, e.ABS_MT_PRESSURE, 400) | ||
387 | 439 | _touch_device.syn() | ||
388 | 440 | |||
389 | 441 | def _finger_move(self, x, y): | ||
390 | 442 | """Internal: moves finger "finger" on the touchscreen to pos (x,y) | ||
391 | 443 | NOTE: The finger has to be down for this to have any effect.""" | ||
392 | 444 | if self._touch_finger is not None: | ||
393 | 445 | _touch_device.write(e.EV_ABS, e.ABS_MT_SLOT, self._touch_finger) | ||
394 | 446 | _touch_device.write(e.EV_ABS, e.ABS_MT_POSITION_X, int(x)) | ||
395 | 447 | _touch_device.write(e.EV_ABS, e.ABS_MT_POSITION_Y, int(y)) | ||
396 | 448 | _touch_device.syn() | ||
397 | 449 | |||
398 | 450 | def _finger_up(self): | ||
399 | 451 | """Internal: moves finger "finger" up from the touchscreen""" | ||
400 | 452 | if self._touch_finger is None: | ||
401 | 453 | raise RuntimeError("Cannot release finger: it's not pressed.") | ||
402 | 454 | _touch_device.write(e.EV_ABS, e.ABS_MT_SLOT, self._touch_finger) | ||
403 | 455 | _touch_device.write(e.EV_ABS, e.ABS_MT_TRACKING_ID, -1) | ||
404 | 456 | _touch_device.write(e.EV_KEY, e.BTN_TOOL_FINGER, 0) | ||
405 | 457 | _touch_device.syn() | ||
406 | 458 | self._touch_finger = _release_touch_finger(self._touch_finger) | ||
409 | 459 | 486 | ||
410 | 460 | 487 | ||
411 | 461 | # veebers: there should be a better way to handle this. | 488 | # veebers: there should be a better way to handle this. |
412 | 462 | 489 | ||
413 | === modified file 'autopilot/tests/unit/test_input.py' | |||
414 | --- autopilot/tests/unit/test_input.py 2014-01-21 04:52:16 +0000 | |||
415 | +++ autopilot/tests/unit/test_input.py 2014-01-21 09:50:21 +0000 | |||
416 | @@ -317,3 +317,264 @@ | |||
417 | 317 | self.assertEqual( | 317 | self.assertEqual( |
418 | 318 | expected_press_calls + expected_release_calls, | 318 | expected_press_calls + expected_release_calls, |
419 | 319 | self.keyboard._device.mock_calls) | 319 | self.keyboard._device.mock_calls) |
420 | 320 | |||
421 | 321 | |||
422 | 322 | class UInputTouchDeviceTestCase(TestCase): | ||
423 | 323 | """Test the integration with evdev.UInput for the touch device.""" | ||
424 | 324 | |||
425 | 325 | def setUp(self): | ||
426 | 326 | super(UInputTouchDeviceTestCase, self).setUp() | ||
427 | 327 | self._number_of_slots = 9 | ||
428 | 328 | |||
429 | 329 | # Return to the original fingers after the test. | ||
430 | 330 | self.addCleanup( | ||
431 | 331 | self._set_fingers_in_use, | ||
432 | 332 | _uinput._UInputTouchDevice._touch_fingers_in_use, | ||
433 | 333 | _uinput._UInputTouchDevice._last_tracking_id) | ||
434 | 334 | |||
435 | 335 | # Always start the tests without fingers in use. | ||
436 | 336 | _uinput._UInputTouchDevice._touch_fingers_in_use = [] | ||
437 | 337 | _uinput._UInputTouchDevice._last_tracking_id = 0 | ||
438 | 338 | |||
439 | 339 | def _set_fingers_in_use(self, touch_fingers_in_use, last_tracking_id): | ||
440 | 340 | _uinput._UInputTouchDevice._touch_fingers_in_use = touch_fingers_in_use | ||
441 | 341 | _uinput._UInputTouchDevice._last_tracking_id = last_tracking_id | ||
442 | 342 | |||
443 | 343 | def test_finger_down_should_use_free_slot(self): | ||
444 | 344 | for slot in range(self._number_of_slots): | ||
445 | 345 | touch = self._get_touch_device() | ||
446 | 346 | |||
447 | 347 | touch.finger_down(0, 0) | ||
448 | 348 | |||
449 | 349 | self._assert_finger_down_emitted_write_and_syn( | ||
450 | 350 | touch, slot=slot, tracking_id=mock.ANY, x=0, y=0) | ||
451 | 351 | |||
452 | 352 | def _get_touch_device(self): | ||
453 | 353 | touch = _uinput._UInputTouchDevice(device_class=mock.Mock) | ||
454 | 354 | touch._device.mock_add_spec(uinput.UInput, spec_set=True) | ||
455 | 355 | return touch | ||
456 | 356 | |||
457 | 357 | def _assert_finger_down_emitted_write_and_syn( | ||
458 | 358 | self, touch, slot, tracking_id, x, y): | ||
459 | 359 | press_value = 1 | ||
460 | 360 | expected_calls = [ | ||
461 | 361 | mock.call.write(ecodes.EV_ABS, ecodes.ABS_MT_SLOT, slot), | ||
462 | 362 | mock.call.write( | ||
463 | 363 | ecodes.EV_ABS, ecodes.ABS_MT_TRACKING_ID, tracking_id), | ||
464 | 364 | mock.call.write( | ||
465 | 365 | ecodes.EV_KEY, ecodes.BTN_TOOL_FINGER, press_value), | ||
466 | 366 | mock.call.write(ecodes.EV_ABS, ecodes.ABS_MT_POSITION_X, x), | ||
467 | 367 | mock.call.write(ecodes.EV_ABS, ecodes.ABS_MT_POSITION_Y, y), | ||
468 | 368 | mock.call.write(ecodes.EV_ABS, ecodes.ABS_MT_PRESSURE, 400), | ||
469 | 369 | mock.call.syn() | ||
470 | 370 | ] | ||
471 | 371 | self.assertEqual(expected_calls, touch._device.mock_calls) | ||
472 | 372 | |||
473 | 373 | def test_finger_down_without_free_slots_should_raise_error(self): | ||
474 | 374 | # Claim all the available slots. | ||
475 | 375 | for slot in range(self._number_of_slots): | ||
476 | 376 | touch = self._get_touch_device() | ||
477 | 377 | touch.finger_down(0, 0) | ||
478 | 378 | |||
479 | 379 | touch = self._get_touch_device() | ||
480 | 380 | |||
481 | 381 | # Try to use one more. | ||
482 | 382 | error = self.assertRaises(RuntimeError, touch.finger_down, 11, 11) | ||
483 | 383 | self.assertEqual( | ||
484 | 384 | 'All available fingers have been used already.', str(error)) | ||
485 | 385 | |||
486 | 386 | def test_finger_down_should_use_unique_tracking_id(self): | ||
487 | 387 | for number in range(self._number_of_slots): | ||
488 | 388 | touch = self._get_touch_device() | ||
489 | 389 | touch.finger_down(0, 0) | ||
490 | 390 | |||
491 | 391 | self._assert_finger_down_emitted_write_and_syn( | ||
492 | 392 | touch, slot=mock.ANY, tracking_id=number + 1, x=0, y=0) | ||
493 | 393 | |||
494 | 394 | def test_finger_down_should_not_reuse_tracking_ids(self): | ||
495 | 395 | # Claim and release all the available slots once. | ||
496 | 396 | for number in range(self._number_of_slots): | ||
497 | 397 | touch = self._get_touch_device() | ||
498 | 398 | touch.finger_down(0, 0) | ||
499 | 399 | touch.finger_up() | ||
500 | 400 | |||
501 | 401 | touch = self._get_touch_device() | ||
502 | 402 | |||
503 | 403 | touch.finger_down(12, 12) | ||
504 | 404 | self._assert_finger_down_emitted_write_and_syn( | ||
505 | 405 | touch, slot=mock.ANY, tracking_id=number + 2, x=12, y=12) | ||
506 | 406 | |||
507 | 407 | def test_finger_down_with_finger_pressed_should_raise_error(self): | ||
508 | 408 | touch = self._get_touch_device() | ||
509 | 409 | touch.finger_down(0, 0) | ||
510 | 410 | |||
511 | 411 | error = self.assertRaises(RuntimeError, touch.finger_down, 0, 0) | ||
512 | 412 | self.assertEqual( | ||
513 | 413 | "Cannot press finger: it's already pressed.", str(error)) | ||
514 | 414 | |||
515 | 415 | def test_finger_move_without_finger_pressed_should_raise_error(self): | ||
516 | 416 | touch = self._get_touch_device() | ||
517 | 417 | |||
518 | 418 | error = self.assertRaises(RuntimeError, touch.finger_move, 10, 10) | ||
519 | 419 | self.assertEqual( | ||
520 | 420 | 'Attempting to move without finger being down.', str(error)) | ||
521 | 421 | |||
522 | 422 | def test_finger_move_should_use_assigned_slot(self): | ||
523 | 423 | for slot in range(self._number_of_slots): | ||
524 | 424 | touch = self._get_touch_device() | ||
525 | 425 | touch.finger_down(0, 0) | ||
526 | 426 | touch._device.reset_mock() | ||
527 | 427 | |||
528 | 428 | touch.finger_move(10, 10) | ||
529 | 429 | |||
530 | 430 | self._assert_finger_move_emitted_write_and_syn( | ||
531 | 431 | touch, slot=slot, x=10, y=10) | ||
532 | 432 | |||
533 | 433 | def _assert_finger_move_emitted_write_and_syn(self, touch, slot, x, y): | ||
534 | 434 | expected_calls = [ | ||
535 | 435 | mock.call.write(ecodes.EV_ABS, ecodes.ABS_MT_SLOT, slot), | ||
536 | 436 | mock.call.write(ecodes.EV_ABS, ecodes.ABS_MT_POSITION_X, x), | ||
537 | 437 | mock.call.write(ecodes.EV_ABS, ecodes.ABS_MT_POSITION_Y, y), | ||
538 | 438 | mock.call.syn() | ||
539 | 439 | ] | ||
540 | 440 | self.assertEqual(expected_calls, touch._device.mock_calls) | ||
541 | 441 | |||
542 | 442 | def test_finger_move_should_reuse_assigned_slot(self): | ||
543 | 443 | first_slot = 0 | ||
544 | 444 | touch = self._get_touch_device() | ||
545 | 445 | touch.finger_down(1, 1) | ||
546 | 446 | touch._device.reset_mock() | ||
547 | 447 | |||
548 | 448 | touch.finger_move(13, 13) | ||
549 | 449 | self._assert_finger_move_emitted_write_and_syn( | ||
550 | 450 | touch, slot=first_slot, x=13, y=13) | ||
551 | 451 | touch._device.reset_mock() | ||
552 | 452 | |||
553 | 453 | touch.finger_move(14, 14) | ||
554 | 454 | self._assert_finger_move_emitted_write_and_syn( | ||
555 | 455 | touch, slot=first_slot, x=14, y=14) | ||
556 | 456 | |||
557 | 457 | def test_finger_up_without_finger_pressed_should_raise_error(self): | ||
558 | 458 | touch = self._get_touch_device() | ||
559 | 459 | |||
560 | 460 | error = self.assertRaises(RuntimeError, touch.finger_up) | ||
561 | 461 | self.assertEqual( | ||
562 | 462 | "Cannot release finger: it's not pressed.", str(error)) | ||
563 | 463 | |||
564 | 464 | def test_finger_up_should_use_assigned_slot(self): | ||
565 | 465 | fingers = [] | ||
566 | 466 | for slot in range(self._number_of_slots): | ||
567 | 467 | touch = self._get_touch_device() | ||
568 | 468 | touch.finger_down(0, 0) | ||
569 | 469 | touch._device.reset_mock() | ||
570 | 470 | fingers.append(touch) | ||
571 | 471 | |||
572 | 472 | for slot, touch in enumerate(fingers): | ||
573 | 473 | touch.finger_up() | ||
574 | 474 | |||
575 | 475 | self._assert_finger_up_emitted_write_and_syn(touch, slot=slot) | ||
576 | 476 | |||
577 | 477 | def _assert_finger_up_emitted_write_and_syn(self, touch, slot): | ||
578 | 478 | lift_tracking_id = -1 | ||
579 | 479 | release_value = 0 | ||
580 | 480 | expected_calls = [ | ||
581 | 481 | mock.call.write(ecodes.EV_ABS, ecodes.ABS_MT_SLOT, slot), | ||
582 | 482 | mock.call.write( | ||
583 | 483 | ecodes.EV_ABS, ecodes.ABS_MT_TRACKING_ID, lift_tracking_id), | ||
584 | 484 | mock.call.write( | ||
585 | 485 | ecodes.EV_KEY, ecodes.BTN_TOOL_FINGER, release_value), | ||
586 | 486 | mock.call.syn() | ||
587 | 487 | ] | ||
588 | 488 | self.assertEqual(expected_calls, touch._device.mock_calls) | ||
589 | 489 | |||
590 | 490 | def test_finger_up_should_release_slot(self): | ||
591 | 491 | fingers = [] | ||
592 | 492 | # Claim all the available slots. | ||
593 | 493 | for slot in range(self._number_of_slots): | ||
594 | 494 | touch = self._get_touch_device() | ||
595 | 495 | touch.finger_down(0, 0) | ||
596 | 496 | fingers.append(touch) | ||
597 | 497 | |||
598 | 498 | slot_to_reuse = 3 | ||
599 | 499 | fingers[slot_to_reuse].finger_up() | ||
600 | 500 | |||
601 | 501 | touch = self._get_touch_device() | ||
602 | 502 | |||
603 | 503 | # Try to use one more. | ||
604 | 504 | touch.finger_down(15, 15) | ||
605 | 505 | self._assert_finger_down_emitted_write_and_syn( | ||
606 | 506 | touch, slot=slot_to_reuse, tracking_id=mock.ANY, x=15, y=15) | ||
607 | 507 | |||
608 | 508 | def test_pressed_with_finger_down(self): | ||
609 | 509 | touch = self._get_touch_device() | ||
610 | 510 | touch.finger_down(0, 0) | ||
611 | 511 | |||
612 | 512 | self.assertTrue(touch.pressed) | ||
613 | 513 | |||
614 | 514 | def test_pressed_without_finger_down(self): | ||
615 | 515 | touch = self._get_touch_device() | ||
616 | 516 | self.assertFalse(touch.pressed) | ||
617 | 517 | |||
618 | 518 | def test_pressed_after_finger_up(self): | ||
619 | 519 | touch = self._get_touch_device() | ||
620 | 520 | touch.finger_down(0, 0) | ||
621 | 521 | touch.finger_up() | ||
622 | 522 | |||
623 | 523 | self.assertFalse(touch.pressed) | ||
624 | 524 | |||
625 | 525 | def test_pressed_with_other_finger_down(self): | ||
626 | 526 | other_touch = self._get_touch_device() | ||
627 | 527 | other_touch.finger_down(0, 0) | ||
628 | 528 | |||
629 | 529 | touch = self._get_touch_device() | ||
630 | 530 | self.assertFalse(touch.pressed) | ||
631 | 531 | |||
632 | 532 | |||
633 | 533 | class UInputTouchTestCase(TestCase): | ||
634 | 534 | """Test UInput Touch helper for autopilot tests.""" | ||
635 | 535 | |||
636 | 536 | def setUp(self): | ||
637 | 537 | super(UInputTouchTestCase, self).setUp() | ||
638 | 538 | self.touch = _uinput.Touch(device_class=mock.Mock) | ||
639 | 539 | self.touch._device.mock_add_spec( | ||
640 | 540 | _uinput._UInputTouchDevice, spec_set=True) | ||
641 | 541 | # Mock the sleeps so we don't have to spend time actually sleeping. | ||
642 | 542 | self.addCleanup(utilities.sleep.disable_mock) | ||
643 | 543 | utilities.sleep.enable_mock() | ||
644 | 544 | |||
645 | 545 | def test_tap(self): | ||
646 | 546 | expected_calls = [ | ||
647 | 547 | mock.call.finger_down(0, 0), | ||
648 | 548 | mock.call.finger_up() | ||
649 | 549 | ] | ||
650 | 550 | |||
651 | 551 | self.touch.tap(0, 0) | ||
652 | 552 | self.assertEqual(expected_calls, self.touch._device.mock_calls) | ||
653 | 553 | |||
654 | 554 | def test_tap_object(self): | ||
655 | 555 | object_ = type('Dummy', (object,), {'globalRect': (0, 0, 10, 10)}) | ||
656 | 556 | expected_calls = [ | ||
657 | 557 | mock.call.finger_down(5, 5), | ||
658 | 558 | mock.call.finger_up() | ||
659 | 559 | ] | ||
660 | 560 | |||
661 | 561 | self.touch.tap_object(object_) | ||
662 | 562 | self.assertEqual(expected_calls, self.touch._device.mock_calls) | ||
663 | 563 | |||
664 | 564 | def test_press(self): | ||
665 | 565 | expected_calls = [mock.call.finger_down(0, 0)] | ||
666 | 566 | |||
667 | 567 | self.touch.press(0, 0) | ||
668 | 568 | self.assertEqual(expected_calls, self.touch._device.mock_calls) | ||
669 | 569 | |||
670 | 570 | def test_release(self): | ||
671 | 571 | expected_calls = [mock.call.finger_up()] | ||
672 | 572 | |||
673 | 573 | self.touch.release() | ||
674 | 574 | self.assertEqual(expected_calls, self.touch._device.mock_calls) | ||
675 | 575 | |||
676 | 576 | def test_move(self): | ||
677 | 577 | expected_calls = [mock.call.finger_move(10, 10)] | ||
678 | 578 | |||
679 | 579 | self.touch.move(10, 10) | ||
680 | 580 | self.assertEqual(expected_calls, self.touch._device.mock_calls) |