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