xref: /third_party/libinput/src/evdev-wheel.c (revision a46c0ec8)
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
36enum wheel_event {
37	WHEEL_EVENT_SCROLL_ACCUMULATED,
38	WHEEL_EVENT_SCROLL,
39	WHEEL_EVENT_SCROLL_TIMEOUT,
40	WHEEL_EVENT_SCROLL_DIR_CHANGED,
41};
42
43static inline const char *
44wheel_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
54static inline const char*
55wheel_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
66static inline void
67log_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
75static inline void
76wheel_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
82static inline void
83wheel_cancel_scroll_timer(struct fallback_dispatch *dispatch)
84{
85	libinput_timer_cancel(&dispatch->wheel.scroll_timer);
86}
87
88static void
89wheel_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
106static void
107wheel_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
128static void
129wheel_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
151static void
152wheel_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
181static void
182wheel_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
272static void
273wheel_handle_state_none(struct fallback_dispatch *dispatch,
274			struct evdev_device *device,
275			uint64_t time)
276{
277
278}
279
280static void
281wheel_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
294static void
295wheel_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
302static void
303wheel_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
326static void
327fallback_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
341void
342fallback_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
382void
383fallback_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
415static void
416wheel_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
425void
426fallback_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