• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright © 2010 Intel Corporation
3  * Copyright © 2013 Jonas Ådahl
4  * Copyright © 2013-2017 Red Hat, Inc.
5  * Copyright © 2017 James Ye <jye836@gmail.com>
6  * Copyright © 2021 José Expósito
7  *
8  * Permission is hereby granted, free of charge, to any person obtaining a
9  * copy of this software and associated documentation files (the "Software"),
10  * to deal in the Software without restriction, including without limitation
11  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
12  * and/or sell copies of the Software, and to permit persons to whom the
13  * Software is furnished to do so, subject to the following conditions:
14  *
15  * The above copyright notice and this permission notice (including the next
16  * paragraph) shall be included in all copies or substantial portions of the
17  * Software.
18  *
19  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
22  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25  * DEALINGS IN THE SOFTWARE.
26  */
27 
28 #include "config.h"
29 
30 #include "evdev-fallback.h"
31 #include "util-input-event.h"
32 
33 #define ACC_V120_THRESHOLD 60
34 #define WHEEL_SCROLL_TIMEOUT ms2us(500)
35 
36 enum wheel_event {
37 	WHEEL_EVENT_SCROLL_ACCUMULATED,
38 	WHEEL_EVENT_SCROLL,
39 	WHEEL_EVENT_SCROLL_TIMEOUT,
40 	WHEEL_EVENT_SCROLL_DIR_CHANGED,
41 };
42 
43 static inline const char *
wheel_state_to_str(enum wheel_state state)44 wheel_state_to_str(enum wheel_state state)
45 {
46 	switch(state) {
47 	CASE_RETURN_STRING(WHEEL_STATE_NONE);
48 	CASE_RETURN_STRING(WHEEL_STATE_ACCUMULATING_SCROLL);
49 	CASE_RETURN_STRING(WHEEL_STATE_SCROLLING);
50 	}
51 	return NULL;
52 }
53 
54 static inline const char*
wheel_event_to_str(enum wheel_event event)55 wheel_event_to_str(enum wheel_event event)
56 {
57 	switch(event) {
58 	CASE_RETURN_STRING(WHEEL_EVENT_SCROLL_ACCUMULATED);
59 	CASE_RETURN_STRING(WHEEL_EVENT_SCROLL);
60 	CASE_RETURN_STRING(WHEEL_EVENT_SCROLL_TIMEOUT);
61 	CASE_RETURN_STRING(WHEEL_EVENT_SCROLL_DIR_CHANGED);
62 	}
63 	return NULL;
64 }
65 
66 static inline void
log_wheel_bug(struct fallback_dispatch * dispatch,enum wheel_event event)67 log_wheel_bug(struct fallback_dispatch *dispatch, enum wheel_event event)
68 {
69 	evdev_log_bug_libinput(dispatch->device,
70 			       "invalid wheel event %s in state %s\n",
71 			       wheel_event_to_str(event),
72 			       wheel_state_to_str(dispatch->wheel.state));
73 }
74 
75 static inline void
wheel_set_scroll_timer(struct fallback_dispatch * dispatch,uint64_t time)76 wheel_set_scroll_timer(struct fallback_dispatch *dispatch, uint64_t time)
77 {
78 	libinput_timer_set(&dispatch->wheel.scroll_timer,
79 			   time + WHEEL_SCROLL_TIMEOUT);
80 }
81 
82 static inline void
wheel_cancel_scroll_timer(struct fallback_dispatch * dispatch)83 wheel_cancel_scroll_timer(struct fallback_dispatch *dispatch)
84 {
85 	libinput_timer_cancel(&dispatch->wheel.scroll_timer);
86 }
87 
88 static void
wheel_handle_event_on_state_none(struct fallback_dispatch * dispatch,enum wheel_event event,uint64_t time)89 wheel_handle_event_on_state_none(struct fallback_dispatch *dispatch,
90 				 enum wheel_event event,
91 				 uint64_t time)
92 {
93 	switch (event) {
94 	case WHEEL_EVENT_SCROLL:
95 		dispatch->wheel.state = WHEEL_STATE_ACCUMULATING_SCROLL;
96 		break;
97 	case WHEEL_EVENT_SCROLL_DIR_CHANGED:
98 		break;
99 	case WHEEL_EVENT_SCROLL_ACCUMULATED:
100 	case WHEEL_EVENT_SCROLL_TIMEOUT:
101 		log_wheel_bug(dispatch, event);
102 		break;
103 	}
104 }
105 
106 static void
wheel_handle_event_on_state_accumulating_scroll(struct fallback_dispatch * dispatch,enum wheel_event event,uint64_t time)107 wheel_handle_event_on_state_accumulating_scroll(struct fallback_dispatch *dispatch,
108 						enum wheel_event event,
109 						uint64_t time)
110 {
111 	switch (event) {
112 	case WHEEL_EVENT_SCROLL_ACCUMULATED:
113 		dispatch->wheel.state = WHEEL_STATE_SCROLLING;
114 		wheel_set_scroll_timer(dispatch, time);
115 		break;
116 	case WHEEL_EVENT_SCROLL:
117 		/* Ignore scroll while accumulating deltas */
118 		break;
119 	case WHEEL_EVENT_SCROLL_DIR_CHANGED:
120 		dispatch->wheel.state = WHEEL_STATE_NONE;
121 		break;
122 	case WHEEL_EVENT_SCROLL_TIMEOUT:
123 		log_wheel_bug(dispatch, event);
124 		break;
125 	}
126 }
127 
128 static void
wheel_handle_event_on_state_scrolling(struct fallback_dispatch * dispatch,enum wheel_event event,uint64_t time)129 wheel_handle_event_on_state_scrolling(struct fallback_dispatch *dispatch,
130 				      enum wheel_event event,
131 				      uint64_t time)
132 {
133 	switch (event) {
134 	case WHEEL_EVENT_SCROLL:
135 		wheel_cancel_scroll_timer(dispatch);
136 		wheel_set_scroll_timer(dispatch, time);
137 		break;
138 	case WHEEL_EVENT_SCROLL_TIMEOUT:
139 		dispatch->wheel.state = WHEEL_STATE_NONE;
140 		break;
141 	case WHEEL_EVENT_SCROLL_DIR_CHANGED:
142 		wheel_cancel_scroll_timer(dispatch);
143 		dispatch->wheel.state = WHEEL_STATE_NONE;
144 		break;
145 	case WHEEL_EVENT_SCROLL_ACCUMULATED:
146 		log_wheel_bug(dispatch, event);
147 		break;
148 	}
149 }
150 
151 static void
wheel_handle_event(struct fallback_dispatch * dispatch,enum wheel_event event,uint64_t time)152 wheel_handle_event(struct fallback_dispatch *dispatch,
153 		   enum wheel_event event,
154 		   uint64_t time)
155 {
156 	enum wheel_state oldstate = dispatch->wheel.state;
157 
158 	switch (oldstate) {
159 	case WHEEL_STATE_NONE:
160 		wheel_handle_event_on_state_none(dispatch, event, time);
161 		break;
162 	case WHEEL_STATE_ACCUMULATING_SCROLL:
163 		wheel_handle_event_on_state_accumulating_scroll(dispatch,
164 								event,
165 								time);
166 		break;
167 	case WHEEL_STATE_SCROLLING:
168 		wheel_handle_event_on_state_scrolling(dispatch, event, time);
169 		break;
170 	}
171 
172 	if (oldstate != dispatch->wheel.state) {
173 		evdev_log_debug(dispatch->device,
174 				"wheel state %s → %s → %s\n",
175 				wheel_state_to_str(oldstate),
176 				wheel_event_to_str(event),
177 				wheel_state_to_str(dispatch->wheel.state));
178 	}
179 }
180 
181 static void
wheel_flush_scroll(struct fallback_dispatch * dispatch,struct evdev_device * device,uint64_t time)182 wheel_flush_scroll(struct fallback_dispatch *dispatch,
183 		   struct evdev_device *device,
184 		   uint64_t time)
185 {
186 	struct normalized_coords wheel_degrees = { 0.0, 0.0 };
187 	struct discrete_coords discrete = { 0.0, 0.0 };
188 	struct wheel_v120 v120 = { 0.0, 0.0 };
189 
190 	/* This mouse has a trackstick instead of a mouse wheel and sends
191 	 * trackstick data via REL_WHEEL. Normalize it like normal x/y coordinates.
192 	 */
193 	if (device->model_flags & EVDEV_MODEL_LENOVO_SCROLLPOINT) {
194 		const struct device_float_coords raw = {
195 			.x = dispatch->wheel.lo_res.x,
196 			.y = dispatch->wheel.lo_res.y * -1,
197 		};
198 		const struct normalized_coords normalized =
199 				filter_dispatch_scroll(device->pointer.filter,
200 						       &raw,
201 						       device,
202 						       time);
203 		evdev_post_scroll(device,
204 				  time,
205 				  LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS,
206 				  &normalized);
207 		dispatch->wheel.hi_res.x = 0;
208 		dispatch->wheel.hi_res.y = 0;
209 		dispatch->wheel.lo_res.x = 0;
210 		dispatch->wheel.lo_res.y = 0;
211 
212 		return;
213 	}
214 
215 	if (dispatch->wheel.hi_res.y != 0) {
216 		int value = dispatch->wheel.hi_res.y;
217 
218 		v120.y = -1 * value;
219 		wheel_degrees.y = -1 * value/120.0 * device->scroll.wheel_click_angle.y;
220 		evdev_notify_axis_wheel(
221 			device,
222 			time,
223 			bit(LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL),
224 			&wheel_degrees,
225 			&v120);
226 		dispatch->wheel.hi_res.y = 0;
227 	}
228 
229 	if (dispatch->wheel.lo_res.y != 0) {
230 		int value = dispatch->wheel.lo_res.y;
231 
232 		wheel_degrees.y = -1 * value * device->scroll.wheel_click_angle.y;
233 		discrete.y = -1 * value;
234 		evdev_notify_axis_legacy_wheel(
235 			device,
236 			time,
237 			bit(LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL),
238 			&wheel_degrees,
239 			&discrete);
240 		dispatch->wheel.lo_res.y = 0;
241 	}
242 
243 	if (dispatch->wheel.hi_res.x != 0) {
244 		int value = dispatch->wheel.hi_res.x;
245 
246 		v120.x = value;
247 		wheel_degrees.x = value/120.0 * device->scroll.wheel_click_angle.x;
248 		evdev_notify_axis_wheel(
249 			device,
250 			time,
251 			bit(LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL),
252 			&wheel_degrees,
253 			&v120);
254 		dispatch->wheel.hi_res.x = 0;
255 	}
256 
257 	if (dispatch->wheel.lo_res.x != 0) {
258 		int value = dispatch->wheel.lo_res.x;
259 
260 		wheel_degrees.x = value * device->scroll.wheel_click_angle.x;
261 		discrete.x = value;
262 		evdev_notify_axis_legacy_wheel(
263 			device,
264 			time,
265 			bit(LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL),
266 			&wheel_degrees,
267 			&discrete);
268 		dispatch->wheel.lo_res.x = 0;
269 	}
270 }
271 
272 static void
wheel_handle_state_none(struct fallback_dispatch * dispatch,struct evdev_device * device,uint64_t time)273 wheel_handle_state_none(struct fallback_dispatch *dispatch,
274 			struct evdev_device *device,
275 			uint64_t time)
276 {
277 
278 }
279 
280 static void
wheel_handle_state_accumulating_scroll(struct fallback_dispatch * dispatch,struct evdev_device * device,uint64_t time)281 wheel_handle_state_accumulating_scroll(struct fallback_dispatch *dispatch,
282 				       struct evdev_device *device,
283 				       uint64_t time)
284 {
285 	if (abs(dispatch->wheel.hi_res.x) >= ACC_V120_THRESHOLD ||
286 	    abs(dispatch->wheel.hi_res.y) >= ACC_V120_THRESHOLD) {
287 		wheel_handle_event(dispatch,
288 				   WHEEL_EVENT_SCROLL_ACCUMULATED,
289 				   time);
290 		wheel_flush_scroll(dispatch, device, time);
291 	}
292 }
293 
294 static void
wheel_handle_state_scrolling(struct fallback_dispatch * dispatch,struct evdev_device * device,uint64_t time)295 wheel_handle_state_scrolling(struct fallback_dispatch *dispatch,
296 			     struct evdev_device *device,
297 			     uint64_t time)
298 {
299 	wheel_flush_scroll(dispatch, device, time);
300 }
301 
302 static void
wheel_handle_direction_change(struct fallback_dispatch * dispatch,struct input_event * e,uint64_t time)303 wheel_handle_direction_change(struct fallback_dispatch *dispatch,
304 			      struct input_event *e,
305 			      uint64_t time)
306 {
307 	enum wheel_direction new_dir = WHEEL_DIR_UNKNOW;
308 
309 	switch (e->code) {
310 	case REL_WHEEL_HI_RES:
311 		new_dir = (e->value > 0) ? WHEEL_DIR_VPOS : WHEEL_DIR_VNEG;
312 		break;
313 	case REL_HWHEEL_HI_RES:
314 		new_dir = (e->value > 0) ? WHEEL_DIR_HPOS : WHEEL_DIR_HNEG;
315 		break;
316 	}
317 
318 	if (new_dir != WHEEL_DIR_UNKNOW && new_dir != dispatch->wheel.dir) {
319 		dispatch->wheel.dir = new_dir;
320 		wheel_handle_event(dispatch,
321 				   WHEEL_EVENT_SCROLL_DIR_CHANGED,
322 				   time);
323 	}
324 }
325 
326 static void
fallback_rotate_wheel(struct fallback_dispatch * dispatch,struct evdev_device * device,struct input_event * e)327 fallback_rotate_wheel(struct fallback_dispatch *dispatch,
328 		      struct evdev_device *device,
329 		      struct input_event *e)
330 {
331 	/* Special case: if we're upside down (-ish),
332 	 * swap the direction of the wheels so that user-down
333 	 * means scroll down. This isn't done for any other angle
334 	 * since it's not clear what the heuristics should be.*/
335 	if (dispatch->rotation.angle >= 160.0 &&
336 	    dispatch->rotation.angle <= 220.0) {
337 		e->value *= -1;
338 	}
339 }
340 
341 void
fallback_wheel_process_relative(struct fallback_dispatch * dispatch,struct evdev_device * device,struct input_event * e,uint64_t time)342 fallback_wheel_process_relative(struct fallback_dispatch *dispatch,
343 				struct evdev_device *device,
344 				struct input_event *e, uint64_t time)
345 {
346 	switch (e->code) {
347 	case REL_WHEEL:
348 		fallback_rotate_wheel(dispatch, device, e);
349 		dispatch->wheel.lo_res.y += e->value;
350 		if (dispatch->wheel.emulate_hi_res_wheel)
351 			dispatch->wheel.hi_res.y += e->value * 120;
352 		dispatch->pending_event |= EVDEV_WHEEL;
353 		wheel_handle_event(dispatch, WHEEL_EVENT_SCROLL, time);
354 		break;
355 	case REL_HWHEEL:
356 		fallback_rotate_wheel(dispatch, device, e);
357 		dispatch->wheel.lo_res.x += e->value;
358 		if (dispatch->wheel.emulate_hi_res_wheel)
359 			dispatch->wheel.hi_res.x += e->value * 120;
360 		dispatch->pending_event |= EVDEV_WHEEL;
361 		wheel_handle_event(dispatch, WHEEL_EVENT_SCROLL, time);
362 		break;
363 	case REL_WHEEL_HI_RES:
364 		fallback_rotate_wheel(dispatch, device, e);
365 		dispatch->wheel.hi_res.y += e->value;
366 		dispatch->wheel.hi_res_event_received = true;
367 		dispatch->pending_event |= EVDEV_WHEEL;
368 		wheel_handle_direction_change(dispatch, e, time);
369 		wheel_handle_event(dispatch, WHEEL_EVENT_SCROLL, time);
370 		break;
371 	case REL_HWHEEL_HI_RES:
372 		fallback_rotate_wheel(dispatch, device, e);
373 		dispatch->wheel.hi_res.x += e->value;
374 		dispatch->wheel.hi_res_event_received = true;
375 		dispatch->pending_event |= EVDEV_WHEEL;
376 		wheel_handle_direction_change(dispatch, e, time);
377 		wheel_handle_event(dispatch, WHEEL_EVENT_SCROLL, time);
378 		break;
379 	}
380 }
381 
382 void
fallback_wheel_handle_state(struct fallback_dispatch * dispatch,struct evdev_device * device,uint64_t time)383 fallback_wheel_handle_state(struct fallback_dispatch *dispatch,
384 			    struct evdev_device *device,
385 			    uint64_t time)
386 {
387 	if (!(device->seat_caps & EVDEV_DEVICE_POINTER))
388 		return;
389 
390 	if (!dispatch->wheel.emulate_hi_res_wheel &&
391 	    !dispatch->wheel.hi_res_event_received &&
392 	    (dispatch->wheel.lo_res.x != 0 || dispatch->wheel.lo_res.y != 0)) {
393 		evdev_log_bug_kernel(device,
394 				     "device supports high-resolution scroll but only low-resolution events have been received.\n"
395 				     "See %s/incorrectly-enabled-hires.html for details\n",
396 				     HTTP_DOC_LINK);
397 		dispatch->wheel.emulate_hi_res_wheel = true;
398 		dispatch->wheel.hi_res.x = dispatch->wheel.lo_res.x * 120;
399 		dispatch->wheel.hi_res.y = dispatch->wheel.lo_res.y * 120;
400 	}
401 
402 	switch (dispatch->wheel.state) {
403 	case WHEEL_STATE_NONE:
404 		wheel_handle_state_none(dispatch, device, time);
405 		break;
406 	case WHEEL_STATE_ACCUMULATING_SCROLL:
407 		wheel_handle_state_accumulating_scroll(dispatch, device, time);
408 		break;
409 	case WHEEL_STATE_SCROLLING:
410 		wheel_handle_state_scrolling(dispatch, device, time);
411 		break;
412 	}
413 }
414 
415 static void
wheel_init_scroll_timer(uint64_t now,void * data)416 wheel_init_scroll_timer(uint64_t now, void *data)
417 {
418 	struct evdev_device *device = data;
419 	struct fallback_dispatch *dispatch =
420 		fallback_dispatch(device->dispatch);
421 
422 	wheel_handle_event(dispatch, WHEEL_EVENT_SCROLL_TIMEOUT, now);
423 }
424 
425 void
fallback_init_wheel(struct fallback_dispatch * dispatch,struct evdev_device * device)426 fallback_init_wheel(struct fallback_dispatch *dispatch,
427 		    struct evdev_device *device)
428 {
429 	char timer_name[64];
430 
431 	dispatch->wheel.state = WHEEL_STATE_NONE;
432 	dispatch->wheel.dir = WHEEL_DIR_UNKNOW;
433 
434 	/* On kernel < 5.0 we need to emulate high-resolution
435 	   wheel scroll events */
436 	if ((libevdev_has_event_code(device->evdev,
437 				     EV_REL,
438 				     REL_WHEEL) &&
439 	     !libevdev_has_event_code(device->evdev,
440 				      EV_REL,
441 				      REL_WHEEL_HI_RES)) ||
442 	    (libevdev_has_event_code(device->evdev,
443 				     EV_REL,
444 				     REL_HWHEEL) &&
445 	     !libevdev_has_event_code(device->evdev,
446 				      EV_REL,
447 				      REL_HWHEEL_HI_RES)))
448 		dispatch->wheel.emulate_hi_res_wheel = true;
449 
450 	snprintf(timer_name,
451 		 sizeof(timer_name),
452 		 "%s wheel scroll",
453 		 evdev_device_get_sysname(device));
454 	libinput_timer_init(&dispatch->wheel.scroll_timer,
455 			    evdev_libinput_context(device),
456 			    timer_name,
457 			    wheel_init_scroll_timer,
458 			    device);
459 }
460