• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright © 2014-2015 Red Hat, Inc.
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a
5  * copy of this software and associated documentation files (the "Software"),
6  * to deal in the Software without restriction, including without limitation
7  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8  * and/or sell copies of the Software, and to permit persons to whom the
9  * Software is furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice (including the next
12  * paragraph) shall be included in all copies or substantial portions of the
13  * Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21  * DEALINGS IN THE SOFTWARE.
22  */
23 
24 #include "config.h"
25 
26 #include <limits.h>
27 #include <math.h>
28 #include <string.h>
29 
30 #include "evdev-mt-touchpad.h"
31 
32 /* Use a reasonably large threshold until locked into scrolling mode, to
33    avoid accidentally locking in scrolling mode when trying to use the entire
34    touchpad to move the pointer. The user can wait for the timeout to trigger
35    to do a small scroll. */
36 #define DEFAULT_SCROLL_THRESHOLD TP_MM_TO_DPI_NORMALIZED(3)
37 
38 enum scroll_event {
39 	SCROLL_EVENT_TOUCH,
40 	SCROLL_EVENT_MOTION,
41 	SCROLL_EVENT_RELEASE,
42 	SCROLL_EVENT_TIMEOUT,
43 	SCROLL_EVENT_POSTED,
44 };
45 
46 static inline const char*
edge_state_to_str(enum tp_edge_scroll_touch_state state)47 edge_state_to_str(enum tp_edge_scroll_touch_state state)
48 {
49 
50 	switch (state) {
51 	CASE_RETURN_STRING(EDGE_SCROLL_TOUCH_STATE_NONE);
52 	CASE_RETURN_STRING(EDGE_SCROLL_TOUCH_STATE_EDGE_NEW);
53 	CASE_RETURN_STRING(EDGE_SCROLL_TOUCH_STATE_EDGE);
54 	CASE_RETURN_STRING(EDGE_SCROLL_TOUCH_STATE_AREA);
55 	}
56 	return NULL;
57 }
58 
59 static inline const char*
edge_event_to_str(enum scroll_event event)60 edge_event_to_str(enum scroll_event event)
61 {
62 	switch (event) {
63 	CASE_RETURN_STRING(SCROLL_EVENT_TOUCH);
64 	CASE_RETURN_STRING(SCROLL_EVENT_MOTION);
65 	CASE_RETURN_STRING(SCROLL_EVENT_RELEASE);
66 	CASE_RETURN_STRING(SCROLL_EVENT_TIMEOUT);
67 	CASE_RETURN_STRING(SCROLL_EVENT_POSTED);
68 	}
69 	return NULL;
70 }
71 
72 uint32_t
tp_touch_get_edge(const struct tp_dispatch * tp,const struct tp_touch * t)73 tp_touch_get_edge(const struct tp_dispatch *tp, const struct tp_touch *t)
74 {
75 	uint32_t edge = EDGE_NONE;
76 
77 	if (tp->scroll.method != LIBINPUT_CONFIG_SCROLL_EDGE)
78 		return EDGE_NONE;
79 
80 	if (t->point.x > tp->scroll.right_edge)
81 		edge |= EDGE_RIGHT;
82 
83 	if (t->point.y > tp->scroll.bottom_edge)
84 		edge |= EDGE_BOTTOM;
85 
86 	return edge;
87 }
88 
89 static inline void
tp_edge_scroll_set_timer(struct tp_dispatch * tp,struct tp_touch * t,uint64_t time)90 tp_edge_scroll_set_timer(struct tp_dispatch *tp,
91 			 struct tp_touch *t,
92 			 uint64_t time)
93 {
94 	const int DEFAULT_SCROLL_LOCK_TIMEOUT = ms2us(300);
95 	/* if we use software buttons, we disable timeout-based
96 	 * edge scrolling. A finger resting on the button areas is
97 	 * likely there to trigger a button event.
98 	 */
99 	if (tp->buttons.click_method ==
100 	    LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS)
101 		return;
102 
103 	libinput_timer_set(&t->scroll.timer,
104 			   time + DEFAULT_SCROLL_LOCK_TIMEOUT);
105 }
106 
107 static void
tp_edge_scroll_set_state(struct tp_dispatch * tp,struct tp_touch * t,enum tp_edge_scroll_touch_state state,uint64_t time)108 tp_edge_scroll_set_state(struct tp_dispatch *tp,
109 			 struct tp_touch *t,
110 			 enum tp_edge_scroll_touch_state state,
111 			 uint64_t time)
112 {
113 	libinput_timer_cancel(&t->scroll.timer);
114 
115 	t->scroll.edge_state = state;
116 
117 	switch (state) {
118 	case EDGE_SCROLL_TOUCH_STATE_NONE:
119 		t->scroll.edge = EDGE_NONE;
120 		break;
121 	case EDGE_SCROLL_TOUCH_STATE_EDGE_NEW:
122 		t->scroll.edge = tp_touch_get_edge(tp, t);
123 		t->scroll.initial = t->point;
124 		tp_edge_scroll_set_timer(tp, t, time);
125 		break;
126 	case EDGE_SCROLL_TOUCH_STATE_EDGE:
127 		break;
128 	case EDGE_SCROLL_TOUCH_STATE_AREA:
129 		t->scroll.edge = EDGE_NONE;
130 		break;
131 	}
132 }
133 
134 static void
tp_edge_scroll_handle_none(struct tp_dispatch * tp,struct tp_touch * t,enum scroll_event event,uint64_t time)135 tp_edge_scroll_handle_none(struct tp_dispatch *tp,
136 			   struct tp_touch *t,
137 			   enum scroll_event event,
138 			   uint64_t time)
139 {
140 	switch (event) {
141 	case SCROLL_EVENT_TOUCH:
142 		if (tp_touch_get_edge(tp, t)) {
143 			tp_edge_scroll_set_state(tp,
144 						 t,
145 						 EDGE_SCROLL_TOUCH_STATE_EDGE_NEW,
146 						 time);
147 		} else {
148 			tp_edge_scroll_set_state(tp,
149 						 t,
150 						 EDGE_SCROLL_TOUCH_STATE_AREA,
151 						 time);
152 		}
153 		break;
154 	case SCROLL_EVENT_MOTION:
155 	case SCROLL_EVENT_RELEASE:
156 	case SCROLL_EVENT_TIMEOUT:
157 	case SCROLL_EVENT_POSTED:
158 		evdev_log_bug_libinput(tp->device,
159 				 "edge-scroll: touch %d: unexpected scroll event %d in none state\n",
160 				 t->index,
161 				 event);
162 		break;
163 	}
164 }
165 
166 static void
tp_edge_scroll_handle_edge_new(struct tp_dispatch * tp,struct tp_touch * t,enum scroll_event event,uint64_t time)167 tp_edge_scroll_handle_edge_new(struct tp_dispatch *tp,
168 			       struct tp_touch *t,
169 			       enum scroll_event event,
170 			       uint64_t time)
171 {
172 	switch (event) {
173 	case SCROLL_EVENT_TOUCH:
174 		evdev_log_bug_libinput(tp->device,
175 			       "edge-scroll: touch %d: unexpected scroll event %d in edge new state\n",
176 			       t->index,
177 			       event);
178 		break;
179 	case SCROLL_EVENT_MOTION:
180 		t->scroll.edge &= tp_touch_get_edge(tp, t);
181 		if (!t->scroll.edge)
182 			tp_edge_scroll_set_state(tp,
183 						 t,
184 						 EDGE_SCROLL_TOUCH_STATE_AREA,
185 						 time);
186 		break;
187 	case SCROLL_EVENT_RELEASE:
188 		tp_edge_scroll_set_state(tp,
189 					 t,
190 					 EDGE_SCROLL_TOUCH_STATE_NONE,
191 					 time);
192 		break;
193 	case SCROLL_EVENT_TIMEOUT:
194 	case SCROLL_EVENT_POSTED:
195 		tp_edge_scroll_set_state(tp,
196 					 t,
197 					 EDGE_SCROLL_TOUCH_STATE_EDGE,
198 					 time);
199 		break;
200 	}
201 }
202 
203 static void
tp_edge_scroll_handle_edge(struct tp_dispatch * tp,struct tp_touch * t,enum scroll_event event,uint64_t time)204 tp_edge_scroll_handle_edge(struct tp_dispatch *tp,
205 			   struct tp_touch *t,
206 			   enum scroll_event event,
207 			   uint64_t time)
208 {
209 	switch (event) {
210 	case SCROLL_EVENT_TOUCH:
211 	case SCROLL_EVENT_TIMEOUT:
212 		evdev_log_bug_libinput(tp->device,
213 				 "edge-scroll: touch %d: unexpected scroll event %d in edge state\n",
214 				 t->index,
215 				 event);
216 		break;
217 	case SCROLL_EVENT_MOTION:
218 		/* If started at the bottom right, decide in which dir to scroll */
219 		if (t->scroll.edge == (EDGE_RIGHT | EDGE_BOTTOM)) {
220 			t->scroll.edge &= tp_touch_get_edge(tp, t);
221 			if (!t->scroll.edge)
222 				tp_edge_scroll_set_state(tp,
223 							 t,
224 							 EDGE_SCROLL_TOUCH_STATE_AREA,
225 							 time);
226 		}
227 		break;
228 	case SCROLL_EVENT_RELEASE:
229 		tp_edge_scroll_set_state(tp,
230 					 t,
231 					 EDGE_SCROLL_TOUCH_STATE_NONE,
232 					 time);
233 		break;
234 	case SCROLL_EVENT_POSTED:
235 		break;
236 	}
237 }
238 
239 static void
tp_edge_scroll_handle_area(struct tp_dispatch * tp,struct tp_touch * t,enum scroll_event event,uint64_t time)240 tp_edge_scroll_handle_area(struct tp_dispatch *tp,
241 			   struct tp_touch *t,
242 			   enum scroll_event event,
243 			   uint64_t time)
244 {
245 	switch (event) {
246 	case SCROLL_EVENT_TOUCH:
247 	case SCROLL_EVENT_TIMEOUT:
248 	case SCROLL_EVENT_POSTED:
249 		evdev_log_bug_libinput(tp->device,
250 				 "unexpected scroll event %d in area state\n",
251 				 event);
252 		break;
253 	case SCROLL_EVENT_MOTION:
254 		break;
255 	case SCROLL_EVENT_RELEASE:
256 		tp_edge_scroll_set_state(tp,
257 					 t,
258 					 EDGE_SCROLL_TOUCH_STATE_NONE,
259 					 time);
260 		break;
261 	}
262 }
263 
264 static void
tp_edge_scroll_handle_event(struct tp_dispatch * tp,struct tp_touch * t,enum scroll_event event,uint64_t time)265 tp_edge_scroll_handle_event(struct tp_dispatch *tp,
266 			    struct tp_touch *t,
267 			    enum scroll_event event,
268 			    uint64_t time)
269 {
270 	enum tp_edge_scroll_touch_state current = t->scroll.edge_state;
271 
272 	switch (current) {
273 	case EDGE_SCROLL_TOUCH_STATE_NONE:
274 		tp_edge_scroll_handle_none(tp, t, event, time);
275 		break;
276 	case EDGE_SCROLL_TOUCH_STATE_EDGE_NEW:
277 		tp_edge_scroll_handle_edge_new(tp, t, event, time);
278 		break;
279 	case EDGE_SCROLL_TOUCH_STATE_EDGE:
280 		tp_edge_scroll_handle_edge(tp, t, event, time);
281 		break;
282 	case EDGE_SCROLL_TOUCH_STATE_AREA:
283 		tp_edge_scroll_handle_area(tp, t, event, time);
284 		break;
285 	}
286 
287 	if (current != t->scroll.edge_state)
288 		evdev_log_debug(tp->device,
289 				"edge-scroll: touch %d state %s → %s → %s\n",
290 				t->index,
291 				edge_state_to_str(current),
292 				edge_event_to_str(event),
293 				edge_state_to_str(t->scroll.edge_state));
294 }
295 
296 static void
tp_edge_scroll_handle_timeout(uint64_t now,void * data)297 tp_edge_scroll_handle_timeout(uint64_t now, void *data)
298 {
299 	struct tp_touch *t = data;
300 
301 	tp_edge_scroll_handle_event(t->tp, t, SCROLL_EVENT_TIMEOUT, now);
302 }
303 
304 void
tp_edge_scroll_init(struct tp_dispatch * tp,struct evdev_device * device)305 tp_edge_scroll_init(struct tp_dispatch *tp, struct evdev_device *device)
306 {
307 	struct tp_touch *t;
308 	double width, height;
309 	bool want_horiz_scroll = true;
310 	struct device_coords edges;
311 	struct phys_coords mm = { 0.0, 0.0 };
312 	int i;
313 
314 	evdev_device_get_size(device, &width, &height);
315 	/* Touchpads smaller than 40mm are not tall enough to have a
316 	   horizontal scroll area, it takes too much space away. But
317 	   clickpads have enough space here anyway because of the
318 	   software button area (and all these tiny clickpads were built
319 	   when software buttons were a thing, e.g. Lenovo *20 series)
320 	 */
321 	if (!tp->buttons.is_clickpad)
322 	    want_horiz_scroll = (height >= 40);
323 
324 	/* 7mm edge size */
325 	mm.x = width - 7;
326 	mm.y = height - 7;
327 	edges = evdev_device_mm_to_units(device, &mm);
328 
329 	tp->scroll.right_edge = edges.x;
330 	if (want_horiz_scroll)
331 		tp->scroll.bottom_edge = edges.y;
332 	else
333 		tp->scroll.bottom_edge = INT_MAX;
334 
335 	i = 0;
336 	tp_for_each_touch(tp, t) {
337 		char timer_name[64];
338 
339 		snprintf(timer_name,
340 			 sizeof(timer_name),
341 			 "%s (%d) edgescroll",
342 			 evdev_device_get_sysname(device),
343 			 i);
344 		t->scroll.direction = -1;
345 		libinput_timer_init(&t->scroll.timer,
346 				    tp_libinput_context(tp),
347 				    timer_name,
348 				    tp_edge_scroll_handle_timeout, t);
349 	}
350 }
351 
352 void
tp_remove_edge_scroll(struct tp_dispatch * tp)353 tp_remove_edge_scroll(struct tp_dispatch *tp)
354 {
355 	struct tp_touch *t;
356 
357 	tp_for_each_touch(tp, t) {
358 		libinput_timer_cancel(&t->scroll.timer);
359 		libinput_timer_destroy(&t->scroll.timer);
360 	}
361 }
362 
363 void
tp_edge_scroll_handle_state(struct tp_dispatch * tp,uint64_t time)364 tp_edge_scroll_handle_state(struct tp_dispatch *tp, uint64_t time)
365 {
366 	struct tp_touch *t;
367 
368 	if (tp->scroll.method != LIBINPUT_CONFIG_SCROLL_EDGE) {
369 		tp_for_each_touch(tp, t) {
370 			if (t->state == TOUCH_BEGIN)
371 				t->scroll.edge_state =
372 					EDGE_SCROLL_TOUCH_STATE_AREA;
373 			else if (t->state == TOUCH_END)
374 				t->scroll.edge_state =
375 					EDGE_SCROLL_TOUCH_STATE_NONE;
376 		}
377 		return;
378 	}
379 
380 	tp_for_each_touch(tp, t) {
381 		if (!t->dirty)
382 			continue;
383 
384 		switch (t->state) {
385 		case TOUCH_NONE:
386 		case TOUCH_HOVERING:
387 			break;
388 		case TOUCH_BEGIN:
389 			tp_edge_scroll_handle_event(tp,
390 						    t,
391 						    SCROLL_EVENT_TOUCH,
392 						    time);
393 			break;
394 		case TOUCH_UPDATE:
395 			tp_edge_scroll_handle_event(tp,
396 						    t,
397 						    SCROLL_EVENT_MOTION,
398 						    time);
399 			break;
400 		case TOUCH_MAYBE_END:
401 			/* This shouldn't happen we transfer to TOUCH_END
402 			 * before processing state */
403 			evdev_log_debug(tp->device,
404 					"touch %d: unexpected state %d\n",
405 					t->index,
406 					t->state);
407 			_fallthrough_;
408 		case TOUCH_END:
409 			tp_edge_scroll_handle_event(tp,
410 						    t,
411 						    SCROLL_EVENT_RELEASE,
412 						    time);
413 			break;
414 		}
415 	}
416 }
417 
418 int
tp_edge_scroll_post_events(struct tp_dispatch * tp,uint64_t time)419 tp_edge_scroll_post_events(struct tp_dispatch *tp, uint64_t time)
420 {
421 	struct evdev_device *device = tp->device;
422 	struct tp_touch *t;
423 	enum libinput_pointer_axis axis;
424 	double *delta;
425 	struct device_coords raw;
426 	struct device_float_coords fraw;
427 	struct normalized_coords normalized, tmp;
428 	const struct normalized_coords zero = { 0.0, 0.0 };
429 
430 	tp_for_each_touch(tp, t) {
431 		if (!t->dirty)
432 			continue;
433 
434 		if (t->palm.state != PALM_NONE || tp_thumb_ignored(tp, t))
435 			continue;
436 
437 		/* only scroll with the finger in the previous edge */
438 		if (t->scroll.edge &&
439 		    (tp_touch_get_edge(tp, t) & t->scroll.edge) == 0)
440 			continue;
441 
442 		switch (t->scroll.edge) {
443 			case EDGE_NONE:
444 				if (t->scroll.direction != -1) {
445 					/* Send stop scroll event */
446 					evdev_notify_axis_finger(device,
447 								 time,
448 								 bit(t->scroll.direction),
449 								 &zero);
450 					t->scroll.direction = -1;
451 				}
452 				continue;
453 			case EDGE_RIGHT:
454 				axis = LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL;
455 				delta = &normalized.y;
456 				break;
457 			case EDGE_BOTTOM:
458 				axis = LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL;
459 				delta = &normalized.x;
460 				break;
461 			default: /* EDGE_RIGHT | EDGE_BOTTOM */
462 				continue; /* Don't know direction yet, skip */
463 		}
464 
465 		raw = tp_get_delta(t);
466 		fraw.x = raw.x;
467 		fraw.y = raw.y;
468 		/* scroll is not accelerated */
469 		normalized = tp_filter_motion_unaccelerated(tp, &fraw, time);
470 
471 		switch (t->scroll.edge_state) {
472 		case EDGE_SCROLL_TOUCH_STATE_NONE:
473 		case EDGE_SCROLL_TOUCH_STATE_AREA:
474 			evdev_log_bug_libinput(device,
475 					 "unexpected scroll state %d\n",
476 					 t->scroll.edge_state);
477 			break;
478 		case EDGE_SCROLL_TOUCH_STATE_EDGE_NEW:
479 			tmp = normalized;
480 			normalized = tp_normalize_delta(tp,
481 					device_delta(t->point,
482 						     t->scroll.initial));
483 			if (fabs(*delta) < DEFAULT_SCROLL_THRESHOLD)
484 				normalized = zero;
485 			else
486 				normalized = tmp;
487 			break;
488 		case EDGE_SCROLL_TOUCH_STATE_EDGE:
489 			break;
490 		}
491 
492 		if (*delta == 0.0)
493 			continue;
494 
495 		evdev_notify_axis_finger(device, time,
496 					 bit(axis),
497 					 &normalized);
498 		t->scroll.direction = axis;
499 
500 		tp_edge_scroll_handle_event(tp, t, SCROLL_EVENT_POSTED, time);
501 	}
502 
503 	return 0; /* Edge touches are suppressed by edge_scroll_touch_active */
504 }
505 
506 void
tp_edge_scroll_stop_events(struct tp_dispatch * tp,uint64_t time)507 tp_edge_scroll_stop_events(struct tp_dispatch *tp, uint64_t time)
508 {
509 	struct evdev_device *device = tp->device;
510 	struct tp_touch *t;
511 	const struct normalized_coords zero = { 0.0, 0.0 };
512 
513 	tp_for_each_touch(tp, t) {
514 		if (t->scroll.direction != -1) {
515 			evdev_notify_axis_finger(device,
516 						 time,
517 						 bit(t->scroll.direction),
518 						 &zero);
519 			t->scroll.direction = -1;
520 			/* reset touch to area state, avoids loading the
521 			 * state machine with special case handling */
522 			t->scroll.edge = EDGE_NONE;
523 			t->scroll.edge_state = EDGE_SCROLL_TOUCH_STATE_AREA;
524 		}
525 	}
526 }
527 
528 int
tp_edge_scroll_touch_active(const struct tp_dispatch * tp,const struct tp_touch * t)529 tp_edge_scroll_touch_active(const struct tp_dispatch *tp,
530 			    const struct tp_touch *t)
531 {
532 	return t->scroll.edge_state == EDGE_SCROLL_TOUCH_STATE_AREA;
533 }
534