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