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