• 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)90 tp_edge_scroll_set_timer(struct tp_dispatch *tp,
91 			 struct tp_touch *t)
92 {
93 	const int DEFAULT_SCROLL_LOCK_TIMEOUT = ms2us(300);
94 	/* if we use software buttons, we disable timeout-based
95 	 * edge scrolling. A finger resting on the button areas is
96 	 * likely there to trigger a button event.
97 	 */
98 	if (tp->buttons.click_method ==
99 	    LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS)
100 		return;
101 
102 	libinput_timer_set(&t->scroll.timer,
103 			   t->time + DEFAULT_SCROLL_LOCK_TIMEOUT);
104 }
105 
106 static void
tp_edge_scroll_set_state(struct tp_dispatch * tp,struct tp_touch * t,enum tp_edge_scroll_touch_state state)107 tp_edge_scroll_set_state(struct tp_dispatch *tp,
108 			 struct tp_touch *t,
109 			 enum tp_edge_scroll_touch_state state)
110 {
111 	libinput_timer_cancel(&t->scroll.timer);
112 
113 	t->scroll.edge_state = state;
114 
115 	switch (state) {
116 	case EDGE_SCROLL_TOUCH_STATE_NONE:
117 		t->scroll.edge = EDGE_NONE;
118 		break;
119 	case EDGE_SCROLL_TOUCH_STATE_EDGE_NEW:
120 		t->scroll.edge = tp_touch_get_edge(tp, t);
121 		t->scroll.initial = t->point;
122 		tp_edge_scroll_set_timer(tp, t);
123 		break;
124 	case EDGE_SCROLL_TOUCH_STATE_EDGE:
125 		break;
126 	case EDGE_SCROLL_TOUCH_STATE_AREA:
127 		t->scroll.edge = EDGE_NONE;
128 		break;
129 	}
130 }
131 
132 static void
tp_edge_scroll_handle_none(struct tp_dispatch * tp,struct tp_touch * t,enum scroll_event event)133 tp_edge_scroll_handle_none(struct tp_dispatch *tp,
134 			   struct tp_touch *t,
135 			   enum scroll_event event)
136 {
137 	switch (event) {
138 	case SCROLL_EVENT_TOUCH:
139 		if (tp_touch_get_edge(tp, t)) {
140 			tp_edge_scroll_set_state(tp, t,
141 					EDGE_SCROLL_TOUCH_STATE_EDGE_NEW);
142 		} else {
143 			tp_edge_scroll_set_state(tp, t,
144 					EDGE_SCROLL_TOUCH_STATE_AREA);
145 		}
146 		break;
147 	case SCROLL_EVENT_MOTION:
148 	case SCROLL_EVENT_RELEASE:
149 	case SCROLL_EVENT_TIMEOUT:
150 	case SCROLL_EVENT_POSTED:
151 		evdev_log_bug_libinput(tp->device,
152 				 "edge-scroll: touch %d: unexpected scroll event %d in none state\n",
153 				 t->index,
154 				 event);
155 		break;
156 	}
157 }
158 
159 static void
tp_edge_scroll_handle_edge_new(struct tp_dispatch * tp,struct tp_touch * t,enum scroll_event event)160 tp_edge_scroll_handle_edge_new(struct tp_dispatch *tp,
161 			       struct tp_touch *t,
162 			       enum scroll_event event)
163 {
164 	switch (event) {
165 	case SCROLL_EVENT_TOUCH:
166 		evdev_log_bug_libinput(tp->device,
167 			       "edge-scroll: touch %d: unexpected scroll event %d in edge new state\n",
168 			       t->index,
169 			       event);
170 		break;
171 	case SCROLL_EVENT_MOTION:
172 		t->scroll.edge &= tp_touch_get_edge(tp, t);
173 		if (!t->scroll.edge)
174 			tp_edge_scroll_set_state(tp, t,
175 					EDGE_SCROLL_TOUCH_STATE_AREA);
176 		break;
177 	case SCROLL_EVENT_RELEASE:
178 		tp_edge_scroll_set_state(tp, t, EDGE_SCROLL_TOUCH_STATE_NONE);
179 		break;
180 	case SCROLL_EVENT_TIMEOUT:
181 	case SCROLL_EVENT_POSTED:
182 		tp_edge_scroll_set_state(tp, t, EDGE_SCROLL_TOUCH_STATE_EDGE);
183 		break;
184 	}
185 }
186 
187 static void
tp_edge_scroll_handle_edge(struct tp_dispatch * tp,struct tp_touch * t,enum scroll_event event)188 tp_edge_scroll_handle_edge(struct tp_dispatch *tp,
189 			   struct tp_touch *t,
190 			   enum scroll_event event)
191 {
192 	switch (event) {
193 	case SCROLL_EVENT_TOUCH:
194 	case SCROLL_EVENT_TIMEOUT:
195 		evdev_log_bug_libinput(tp->device,
196 				 "edge-scroll: touch %d: unexpected scroll event %d in edge state\n",
197 				 t->index,
198 				 event);
199 		break;
200 	case SCROLL_EVENT_MOTION:
201 		/* If started at the bottom right, decide in which dir to scroll */
202 		if (t->scroll.edge == (EDGE_RIGHT | EDGE_BOTTOM)) {
203 			t->scroll.edge &= tp_touch_get_edge(tp, t);
204 			if (!t->scroll.edge)
205 				tp_edge_scroll_set_state(tp, t,
206 						EDGE_SCROLL_TOUCH_STATE_AREA);
207 		}
208 		break;
209 	case SCROLL_EVENT_RELEASE:
210 		tp_edge_scroll_set_state(tp, t, EDGE_SCROLL_TOUCH_STATE_NONE);
211 		break;
212 	case SCROLL_EVENT_POSTED:
213 		break;
214 	}
215 }
216 
217 static void
tp_edge_scroll_handle_area(struct tp_dispatch * tp,struct tp_touch * t,enum scroll_event event)218 tp_edge_scroll_handle_area(struct tp_dispatch *tp,
219 			   struct tp_touch *t,
220 			   enum scroll_event event)
221 {
222 	switch (event) {
223 	case SCROLL_EVENT_TOUCH:
224 	case SCROLL_EVENT_TIMEOUT:
225 	case SCROLL_EVENT_POSTED:
226 		evdev_log_bug_libinput(tp->device,
227 				 "unexpected scroll event %d in area state\n",
228 				 event);
229 		break;
230 	case SCROLL_EVENT_MOTION:
231 		break;
232 	case SCROLL_EVENT_RELEASE:
233 		tp_edge_scroll_set_state(tp, t, EDGE_SCROLL_TOUCH_STATE_NONE);
234 		break;
235 	}
236 }
237 
238 static void
tp_edge_scroll_handle_event(struct tp_dispatch * tp,struct tp_touch * t,enum scroll_event event)239 tp_edge_scroll_handle_event(struct tp_dispatch *tp,
240 			    struct tp_touch *t,
241 			    enum scroll_event event)
242 {
243 	enum tp_edge_scroll_touch_state current = t->scroll.edge_state;
244 
245 	switch (current) {
246 	case EDGE_SCROLL_TOUCH_STATE_NONE:
247 		tp_edge_scroll_handle_none(tp, t, event);
248 		break;
249 	case EDGE_SCROLL_TOUCH_STATE_EDGE_NEW:
250 		tp_edge_scroll_handle_edge_new(tp, t, event);
251 		break;
252 	case EDGE_SCROLL_TOUCH_STATE_EDGE:
253 		tp_edge_scroll_handle_edge(tp, t, event);
254 		break;
255 	case EDGE_SCROLL_TOUCH_STATE_AREA:
256 		tp_edge_scroll_handle_area(tp, t, event);
257 		break;
258 	}
259 
260 	if (current != t->scroll.edge_state)
261 		evdev_log_debug(tp->device,
262 				"edge-scroll: touch %d state %s → %s → %s\n",
263 				t->index,
264 				edge_state_to_str(current),
265 				edge_event_to_str(event),
266 				edge_state_to_str(t->scroll.edge_state));
267 }
268 
269 static void
tp_edge_scroll_handle_timeout(uint64_t now,void * data)270 tp_edge_scroll_handle_timeout(uint64_t now, void *data)
271 {
272 	struct tp_touch *t = data;
273 
274 	tp_edge_scroll_handle_event(t->tp, t, SCROLL_EVENT_TIMEOUT);
275 }
276 
277 void
tp_edge_scroll_init(struct tp_dispatch * tp,struct evdev_device * device)278 tp_edge_scroll_init(struct tp_dispatch *tp, struct evdev_device *device)
279 {
280 	struct tp_touch *t;
281 	double width, height;
282 	bool want_horiz_scroll = true;
283 	struct device_coords edges;
284 	struct phys_coords mm = { 0.0, 0.0 };
285 	int i;
286 
287 	evdev_device_get_size(device, &width, &height);
288 	/* Touchpads smaller than 40mm are not tall enough to have a
289 	   horizontal scroll area, it takes too much space away. But
290 	   clickpads have enough space here anyway because of the
291 	   software button area (and all these tiny clickpads were built
292 	   when software buttons were a thing, e.g. Lenovo *20 series)
293 	 */
294 	if (!tp->buttons.is_clickpad)
295 	    want_horiz_scroll = (height >= 40);
296 
297 	/* 7mm edge size */
298 	mm.x = width - 7;
299 	mm.y = height - 7;
300 	edges = evdev_device_mm_to_units(device, &mm);
301 
302 	tp->scroll.right_edge = edges.x;
303 	if (want_horiz_scroll)
304 		tp->scroll.bottom_edge = edges.y;
305 	else
306 		tp->scroll.bottom_edge = INT_MAX;
307 
308 	i = 0;
309 	tp_for_each_touch(tp, t) {
310 		char timer_name[64];
311 
312 		snprintf(timer_name,
313 			 sizeof(timer_name),
314 			 "%s (%d) edgescroll",
315 			 evdev_device_get_sysname(device),
316 			 i);
317 		t->scroll.direction = -1;
318 		libinput_timer_init(&t->scroll.timer,
319 				    tp_libinput_context(tp),
320 				    timer_name,
321 				    tp_edge_scroll_handle_timeout, t);
322 	}
323 }
324 
325 void
tp_remove_edge_scroll(struct tp_dispatch * tp)326 tp_remove_edge_scroll(struct tp_dispatch *tp)
327 {
328 	struct tp_touch *t;
329 
330 	tp_for_each_touch(tp, t) {
331 		libinput_timer_cancel(&t->scroll.timer);
332 		libinput_timer_destroy(&t->scroll.timer);
333 	}
334 }
335 
336 void
tp_edge_scroll_handle_state(struct tp_dispatch * tp,uint64_t time)337 tp_edge_scroll_handle_state(struct tp_dispatch *tp, uint64_t time)
338 {
339 	struct tp_touch *t;
340 
341 	if (tp->scroll.method != LIBINPUT_CONFIG_SCROLL_EDGE) {
342 		tp_for_each_touch(tp, t) {
343 			if (t->state == TOUCH_BEGIN)
344 				t->scroll.edge_state =
345 					EDGE_SCROLL_TOUCH_STATE_AREA;
346 			else if (t->state == TOUCH_END)
347 				t->scroll.edge_state =
348 					EDGE_SCROLL_TOUCH_STATE_NONE;
349 		}
350 		return;
351 	}
352 
353 	tp_for_each_touch(tp, t) {
354 		if (!t->dirty)
355 			continue;
356 
357 		switch (t->state) {
358 		case TOUCH_NONE:
359 		case TOUCH_HOVERING:
360 			break;
361 		case TOUCH_BEGIN:
362 			tp_edge_scroll_handle_event(tp, t, SCROLL_EVENT_TOUCH);
363 			break;
364 		case TOUCH_UPDATE:
365 			tp_edge_scroll_handle_event(tp, t, SCROLL_EVENT_MOTION);
366 			break;
367 		case TOUCH_MAYBE_END:
368 			/* This shouldn't happen we transfer to TOUCH_END
369 			 * before processing state */
370 			evdev_log_debug(tp->device,
371 					"touch %d: unexpected state %d\n",
372 					t->index,
373 					t->state);
374 			/* fallthrough */
375 		case TOUCH_END:
376 			tp_edge_scroll_handle_event(tp, t, SCROLL_EVENT_RELEASE);
377 			break;
378 		}
379 	}
380 }
381 
382 int
tp_edge_scroll_post_events(struct tp_dispatch * tp,uint64_t time)383 tp_edge_scroll_post_events(struct tp_dispatch *tp, uint64_t time)
384 {
385 	struct evdev_device *device = tp->device;
386 	struct tp_touch *t;
387 	enum libinput_pointer_axis axis;
388 	double *delta;
389 	struct device_coords raw;
390 	struct device_float_coords fraw;
391 	struct normalized_coords normalized, tmp;
392 	const struct normalized_coords zero = { 0.0, 0.0 };
393 	const struct discrete_coords zero_discrete = { 0.0, 0.0 };
394 
395 	tp_for_each_touch(tp, t) {
396 		if (!t->dirty)
397 			continue;
398 
399 		if (t->palm.state != PALM_NONE || tp_thumb_ignored(tp, t))
400 			continue;
401 
402 		/* only scroll with the finger in the previous edge */
403 		if (t->scroll.edge &&
404 		    (tp_touch_get_edge(tp, t) & t->scroll.edge) == 0)
405 			continue;
406 
407 		switch (t->scroll.edge) {
408 			case EDGE_NONE:
409 				if (t->scroll.direction != -1) {
410 					/* Send stop scroll event */
411 					evdev_notify_axis(device, time,
412 						bit(t->scroll.direction),
413 						LIBINPUT_POINTER_AXIS_SOURCE_FINGER,
414 						&zero,
415 						&zero_discrete);
416 					t->scroll.direction = -1;
417 				}
418 				continue;
419 			case EDGE_RIGHT:
420 				axis = LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL;
421 				delta = &normalized.y;
422 				break;
423 			case EDGE_BOTTOM:
424 				axis = LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL;
425 				delta = &normalized.x;
426 				break;
427 			default: /* EDGE_RIGHT | EDGE_BOTTOM */
428 				continue; /* Don't know direction yet, skip */
429 		}
430 
431 		raw = tp_get_delta(t);
432 		fraw.x = raw.x;
433 		fraw.y = raw.y;
434 		/* scroll is not accelerated */
435 		normalized = tp_filter_motion_unaccelerated(tp, &fraw, time);
436 
437 		switch (t->scroll.edge_state) {
438 		case EDGE_SCROLL_TOUCH_STATE_NONE:
439 		case EDGE_SCROLL_TOUCH_STATE_AREA:
440 			evdev_log_bug_libinput(device,
441 					 "unexpected scroll state %d\n",
442 					 t->scroll.edge_state);
443 			break;
444 		case EDGE_SCROLL_TOUCH_STATE_EDGE_NEW:
445 			tmp = normalized;
446 			normalized = tp_normalize_delta(tp,
447 					device_delta(t->point,
448 						     t->scroll.initial));
449 			if (fabs(*delta) < DEFAULT_SCROLL_THRESHOLD)
450 				normalized = zero;
451 			else
452 				normalized = tmp;
453 			break;
454 		case EDGE_SCROLL_TOUCH_STATE_EDGE:
455 			break;
456 		}
457 
458 		if (*delta == 0.0)
459 			continue;
460 
461 		evdev_notify_axis(device, time,
462 				  bit(axis),
463 				  LIBINPUT_POINTER_AXIS_SOURCE_FINGER,
464 				  &normalized,
465 				  &zero_discrete);
466 		t->scroll.direction = axis;
467 
468 		tp_edge_scroll_handle_event(tp, t, SCROLL_EVENT_POSTED);
469 	}
470 
471 	return 0; /* Edge touches are suppressed by edge_scroll_touch_active */
472 }
473 
474 void
tp_edge_scroll_stop_events(struct tp_dispatch * tp,uint64_t time)475 tp_edge_scroll_stop_events(struct tp_dispatch *tp, uint64_t time)
476 {
477 	struct evdev_device *device = tp->device;
478 	struct tp_touch *t;
479 	const struct normalized_coords zero = { 0.0, 0.0 };
480 	const struct discrete_coords zero_discrete = { 0.0, 0.0 };
481 
482 	tp_for_each_touch(tp, t) {
483 		if (t->scroll.direction != -1) {
484 			evdev_notify_axis(device, time,
485 					    bit(t->scroll.direction),
486 					    LIBINPUT_POINTER_AXIS_SOURCE_FINGER,
487 					    &zero,
488 					    &zero_discrete);
489 			t->scroll.direction = -1;
490 			/* reset touch to area state, avoids loading the
491 			 * state machine with special case handling */
492 			t->scroll.edge = EDGE_NONE;
493 			t->scroll.edge_state = EDGE_SCROLL_TOUCH_STATE_AREA;
494 		}
495 	}
496 }
497 
498 int
tp_edge_scroll_touch_active(const struct tp_dispatch * tp,const struct tp_touch * t)499 tp_edge_scroll_touch_active(const struct tp_dispatch *tp,
500 			    const struct tp_touch *t)
501 {
502 	return t->scroll.edge_state == EDGE_SCROLL_TOUCH_STATE_AREA;
503 }
504