/* * Copyright © 2010 Intel Corporation * Copyright © 2013 Jonas Ådahl * Copyright © 2013-2017 Red Hat, Inc. * Copyright © 2017 James Ye * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include #include #include #include "linux/input.h" #include #include #include #include #include #include #include "libinput.h" #include "evdev.h" #include "filter.h" #include "libinput-private.h" #include "quirks.h" #if HAVE_LIBWACOM #include #endif #define DEFAULT_WHEEL_CLICK_ANGLE 15 #define DEFAULT_BUTTON_SCROLL_TIMEOUT ms2us(200) enum evdev_device_udev_tags { EVDEV_UDEV_TAG_INPUT = (1 << 0), EVDEV_UDEV_TAG_KEYBOARD = (1 << 1), EVDEV_UDEV_TAG_MOUSE = (1 << 2), EVDEV_UDEV_TAG_TOUCHPAD = (1 << 3), EVDEV_UDEV_TAG_TOUCHSCREEN = (1 << 4), EVDEV_UDEV_TAG_TABLET = (1 << 5), EVDEV_UDEV_TAG_JOYSTICK = (1 << 6), EVDEV_UDEV_TAG_ACCELEROMETER = (1 << 7), EVDEV_UDEV_TAG_TABLET_PAD = (1 << 8), EVDEV_UDEV_TAG_POINTINGSTICK = (1 << 9), EVDEV_UDEV_TAG_TRACKBALL = (1 << 10), EVDEV_UDEV_TAG_SWITCH = (1 << 11), }; struct evdev_udev_tag_match { const char *name; enum evdev_device_udev_tags tag; }; static const struct evdev_udev_tag_match evdev_udev_tag_matches[] = { {"ID_INPUT", EVDEV_UDEV_TAG_INPUT}, {"ID_INPUT_KEYBOARD", EVDEV_UDEV_TAG_KEYBOARD}, {"ID_INPUT_KEY", EVDEV_UDEV_TAG_KEYBOARD}, {"ID_INPUT_MOUSE", EVDEV_UDEV_TAG_MOUSE}, {"ID_INPUT_TOUCHPAD", EVDEV_UDEV_TAG_TOUCHPAD}, {"ID_INPUT_TOUCHSCREEN", EVDEV_UDEV_TAG_TOUCHSCREEN}, {"ID_INPUT_TABLET", EVDEV_UDEV_TAG_TABLET}, {"ID_INPUT_TABLET_PAD", EVDEV_UDEV_TAG_TABLET_PAD}, {"ID_INPUT_JOYSTICK", EVDEV_UDEV_TAG_JOYSTICK}, {"ID_INPUT_ACCELEROMETER", EVDEV_UDEV_TAG_ACCELEROMETER}, {"ID_INPUT_POINTINGSTICK", EVDEV_UDEV_TAG_POINTINGSTICK}, {"ID_INPUT_TRACKBALL", EVDEV_UDEV_TAG_TRACKBALL}, {"ID_INPUT_SWITCH", EVDEV_UDEV_TAG_SWITCH}, }; static inline bool parse_udev_flag(struct evdev_device *device, struct udev_device *udev_device, const char *property) { const char *val; val = udev_device_get_property_value(udev_device, property); if (!val) return false; if (streq(val, "1")) return true; if (!streq(val, "0")) evdev_log_error(device, "property %s has invalid value '%s'\n", property, val); return false; } int evdev_update_key_down_count(struct evdev_device *device, int code, int pressed) { int key_count; assert(code >= 0 && code < KEY_CNT); if (pressed) { key_count = ++device->key_count[code]; } else { assert(device->key_count[code] > 0); key_count = --device->key_count[code]; } if (key_count > 32) { evdev_log_bug_libinput(device, "key count for %s reached abnormal values\n", libevdev_event_code_get_name(EV_KEY, code)); } return key_count; } enum libinput_switch_state evdev_device_switch_get_state(struct evdev_device *device, enum libinput_switch sw) { struct evdev_dispatch *dispatch = device->dispatch; assert(dispatch->interface->get_switch_state); return dispatch->interface->get_switch_state(dispatch, sw); } void evdev_pointer_notify_physical_button(struct evdev_device *device, uint64_t time, int button, enum libinput_button_state state) { if (evdev_middlebutton_filter_button(device, time, button, state)) return; evdev_pointer_notify_button(device, time, (unsigned int)button, state); } static void evdev_pointer_post_button(struct evdev_device *device, uint64_t time, unsigned int button, enum libinput_button_state state) { int down_count; down_count = evdev_update_key_down_count(device, button, state); if ((state == LIBINPUT_BUTTON_STATE_PRESSED && down_count == 1) || (state == LIBINPUT_BUTTON_STATE_RELEASED && down_count == 0)) { pointer_notify_button(&device->base, time, button, state); if (state == LIBINPUT_BUTTON_STATE_RELEASED) { if (device->left_handed.change_to_enabled) device->left_handed.change_to_enabled(device); if (device->scroll.change_scroll_method) device->scroll.change_scroll_method(device); } } } static void evdev_button_scroll_timeout(uint64_t time, void *data) { struct evdev_device *device = data; device->scroll.button_scroll_state = BUTTONSCROLL_READY; } static void evdev_button_scroll_button(struct evdev_device *device, uint64_t time, int is_press) { if (is_press) { enum timer_flags flags = TIMER_FLAG_NONE; device->scroll.button_scroll_state = BUTTONSCROLL_BUTTON_DOWN; /* Special case: if middle button emulation is enabled and * our scroll button is the left or right button, we only * get here *after* the middle button timeout has expired * for that button press. The time passed is the button-down * time though (which is in the past), so we have to allow * for a negative timer to be set. */ if (device->middlebutton.enabled && (device->scroll.button == BTN_LEFT || device->scroll.button == BTN_RIGHT)) { flags = TIMER_FLAG_ALLOW_NEGATIVE; } libinput_timer_set_flags(&device->scroll.timer, time + DEFAULT_BUTTON_SCROLL_TIMEOUT, flags); device->scroll.button_down_time = time; evdev_log_debug(device, "btnscroll: down\n"); } else { libinput_timer_cancel(&device->scroll.timer); switch(device->scroll.button_scroll_state) { case BUTTONSCROLL_IDLE: evdev_log_bug_libinput(device, "invalid state IDLE for button up\n"); break; case BUTTONSCROLL_BUTTON_DOWN: case BUTTONSCROLL_READY: evdev_log_debug(device, "btnscroll: cancel\n"); /* If the button is released quickly enough or * without scroll events, emit the * button press/release events. */ evdev_pointer_post_button(device, device->scroll.button_down_time, device->scroll.button, LIBINPUT_BUTTON_STATE_PRESSED); evdev_pointer_post_button(device, time, device->scroll.button, LIBINPUT_BUTTON_STATE_RELEASED); break; case BUTTONSCROLL_SCROLLING: evdev_log_debug(device, "btnscroll: up\n"); evdev_stop_scroll(device, time, LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS); break; } device->scroll.button_scroll_state = BUTTONSCROLL_IDLE; } } void evdev_pointer_notify_button(struct evdev_device *device, uint64_t time, unsigned int button, enum libinput_button_state state) { if (device->scroll.method == LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN && button == device->scroll.button) { evdev_button_scroll_button(device, time, state); return; } evdev_pointer_post_button(device, time, button, state); } void evdev_device_led_update(struct evdev_device *device, enum libinput_led leds) { static const struct { enum libinput_led libinput; int evdev; } map[] = { { LIBINPUT_LED_NUM_LOCK, LED_NUML }, { LIBINPUT_LED_CAPS_LOCK, LED_CAPSL }, { LIBINPUT_LED_SCROLL_LOCK, LED_SCROLLL }, }; struct input_event ev[ARRAY_LENGTH(map) + 1]; unsigned int i; if (!(device->seat_caps & EVDEV_DEVICE_KEYBOARD)) return; memset(ev, 0, sizeof(ev)); for (i = 0; i < ARRAY_LENGTH(map); i++) { ev[i].type = EV_LED; ev[i].code = map[i].evdev; ev[i].value = !!(leds & map[i].libinput); } ev[i].type = EV_SYN; ev[i].code = SYN_REPORT; i = write(device->fd, ev, sizeof ev); (void)i; /* no, we really don't care about the return value */ } void evdev_transform_absolute(struct evdev_device *device, struct device_coords *point) { if (!device->abs.apply_calibration) return; matrix_mult_vec(&device->abs.calibration, &point->x, &point->y); } void evdev_transform_relative(struct evdev_device *device, struct device_coords *point) { struct matrix rel_matrix; if (!device->abs.apply_calibration) return; matrix_to_relative(&rel_matrix, &device->abs.calibration); matrix_mult_vec(&rel_matrix, &point->x, &point->y); } static inline double scale_axis(const struct input_absinfo *absinfo, double val, double to_range) { return (val - absinfo->minimum) * to_range / (absinfo->maximum - absinfo->minimum + 1); } double evdev_device_transform_x(struct evdev_device *device, double x, uint32_t width) { return scale_axis(device->abs.absinfo_x, x, width); } double evdev_device_transform_y(struct evdev_device *device, double y, uint32_t height) { return scale_axis(device->abs.absinfo_y, y, height); } void evdev_notify_axis(struct evdev_device *device, uint64_t time, uint32_t axes, enum libinput_pointer_axis_source source, const struct normalized_coords *delta_in, const struct discrete_coords *discrete_in) { struct normalized_coords delta = *delta_in; struct discrete_coords discrete = *discrete_in; if (device->scroll.natural_scrolling_enabled) { delta.x *= -1; delta.y *= -1; discrete.x *= -1; discrete.y *= -1; } pointer_notify_axis(&device->base, time, axes, source, &delta, &discrete); } static void evdev_tag_external_mouse(struct evdev_device *device, struct udev_device *udev_device) { int bustype; bustype = libevdev_get_id_bustype(device->evdev); if (bustype == BUS_USB || bustype == BUS_BLUETOOTH) device->tags |= EVDEV_TAG_EXTERNAL_MOUSE; } static void evdev_tag_trackpoint(struct evdev_device *device, struct udev_device *udev_device) { if (libevdev_has_property(device->evdev, INPUT_PROP_POINTING_STICK) || parse_udev_flag(device, udev_device, "ID_INPUT_POINTINGSTICK")) device->tags |= EVDEV_TAG_TRACKPOINT; } static inline void evdev_tag_keyboard_internal(struct evdev_device *device) { device->tags |= EVDEV_TAG_INTERNAL_KEYBOARD; device->tags &= ~EVDEV_TAG_EXTERNAL_KEYBOARD; } static inline void evdev_tag_keyboard_external(struct evdev_device *device) { device->tags |= EVDEV_TAG_EXTERNAL_KEYBOARD; device->tags &= ~EVDEV_TAG_INTERNAL_KEYBOARD; } static void evdev_tag_keyboard(struct evdev_device *device, struct udev_device *udev_device) { struct quirks_context *quirks; struct quirks *q; char *prop; int code; if (!libevdev_has_event_type(device->evdev, EV_KEY)) return; for (code = KEY_Q; code <= KEY_P; code++) { if (!libevdev_has_event_code(device->evdev, EV_KEY, code)) return; } quirks = evdev_libinput_context(device)->quirks; q = quirks_fetch_for_device(quirks, device->udev_device); if (q && quirks_get_string(q, QUIRK_ATTR_KEYBOARD_INTEGRATION, &prop)) { if (streq(prop, "internal")) { evdev_tag_keyboard_internal(device); } else if (streq(prop, "external")) { evdev_tag_keyboard_external(device); } else { evdev_log_info(device, "tagged with unknown value %s\n", prop); } } quirks_unref(q); device->tags |= EVDEV_TAG_KEYBOARD; } static void evdev_tag_tablet_touchpad(struct evdev_device *device) { device->tags |= EVDEV_TAG_TABLET_TOUCHPAD; } static int evdev_calibration_has_matrix(struct libinput_device *libinput_device) { struct evdev_device *device = evdev_device(libinput_device); return device->abs.absinfo_x && device->abs.absinfo_y; } static enum libinput_config_status evdev_calibration_set_matrix(struct libinput_device *libinput_device, const float matrix[6]) { struct evdev_device *device = evdev_device(libinput_device); evdev_device_calibrate(device, matrix); return LIBINPUT_CONFIG_STATUS_SUCCESS; } static int evdev_calibration_get_matrix(struct libinput_device *libinput_device, float matrix[6]) { struct evdev_device *device = evdev_device(libinput_device); matrix_to_farray6(&device->abs.usermatrix, matrix); return !matrix_is_identity(&device->abs.usermatrix); } static int evdev_calibration_get_default_matrix(struct libinput_device *libinput_device, float matrix[6]) { struct evdev_device *device = evdev_device(libinput_device); matrix_to_farray6(&device->abs.default_calibration, matrix); return !matrix_is_identity(&device->abs.default_calibration); } static uint32_t evdev_sendevents_get_modes(struct libinput_device *device) { return LIBINPUT_CONFIG_SEND_EVENTS_DISABLED; } static enum libinput_config_status evdev_sendevents_set_mode(struct libinput_device *device, enum libinput_config_send_events_mode mode) { struct evdev_device *evdev = evdev_device(device); struct evdev_dispatch *dispatch = evdev->dispatch; if (mode == dispatch->sendevents.current_mode) return LIBINPUT_CONFIG_STATUS_SUCCESS; switch(mode) { case LIBINPUT_CONFIG_SEND_EVENTS_ENABLED: evdev_device_resume(evdev); break; case LIBINPUT_CONFIG_SEND_EVENTS_DISABLED: evdev_device_suspend(evdev); break; default: /* no support for combined modes yet */ return LIBINPUT_CONFIG_STATUS_UNSUPPORTED; } dispatch->sendevents.current_mode = mode; return LIBINPUT_CONFIG_STATUS_SUCCESS; } static enum libinput_config_send_events_mode evdev_sendevents_get_mode(struct libinput_device *device) { struct evdev_device *evdev = evdev_device(device); struct evdev_dispatch *dispatch = evdev->dispatch; return dispatch->sendevents.current_mode; } static enum libinput_config_send_events_mode evdev_sendevents_get_default_mode(struct libinput_device *device) { return LIBINPUT_CONFIG_SEND_EVENTS_ENABLED; } static int evdev_left_handed_has(struct libinput_device *device) { /* This is only hooked up when we have left-handed configuration, so we * can hardcode 1 here */ return 1; } static enum libinput_config_status evdev_left_handed_set(struct libinput_device *device, int left_handed) { struct evdev_device *evdev = evdev_device(device); evdev->left_handed.want_enabled = left_handed ? true : false; evdev->left_handed.change_to_enabled(evdev); return LIBINPUT_CONFIG_STATUS_SUCCESS; } static int evdev_left_handed_get(struct libinput_device *device) { struct evdev_device *evdev = evdev_device(device); /* return the wanted configuration, even if it hasn't taken * effect yet! */ return evdev->left_handed.want_enabled; } static int evdev_left_handed_get_default(struct libinput_device *device) { return 0; } void evdev_init_left_handed(struct evdev_device *device, void (*change_to_left_handed)(struct evdev_device *)) { device->left_handed.config.has = evdev_left_handed_has; device->left_handed.config.set = evdev_left_handed_set; device->left_handed.config.get = evdev_left_handed_get; device->left_handed.config.get_default = evdev_left_handed_get_default; device->base.config.left_handed = &device->left_handed.config; device->left_handed.enabled = false; device->left_handed.want_enabled = false; device->left_handed.change_to_enabled = change_to_left_handed; } static uint32_t evdev_scroll_get_methods(struct libinput_device *device) { return LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN; } static enum libinput_config_status evdev_scroll_set_method(struct libinput_device *device, enum libinput_config_scroll_method method) { struct evdev_device *evdev = evdev_device(device); evdev->scroll.want_method = method; evdev->scroll.change_scroll_method(evdev); return LIBINPUT_CONFIG_STATUS_SUCCESS; } static enum libinput_config_scroll_method evdev_scroll_get_method(struct libinput_device *device) { struct evdev_device *evdev = evdev_device(device); /* return the wanted configuration, even if it hasn't taken * effect yet! */ return evdev->scroll.want_method; } static enum libinput_config_scroll_method evdev_scroll_get_default_method(struct libinput_device *device) { struct evdev_device *evdev = evdev_device(device); if (evdev->tags & EVDEV_TAG_TRACKPOINT) return LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN; /* Mice without a scroll wheel but with middle button have on-button * scrolling by default */ if (!libevdev_has_event_code(evdev->evdev, EV_REL, REL_WHEEL) && !libevdev_has_event_code(evdev->evdev, EV_REL, REL_HWHEEL) && libevdev_has_event_code(evdev->evdev, EV_KEY, BTN_MIDDLE)) return LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN; return LIBINPUT_CONFIG_SCROLL_NO_SCROLL; } static enum libinput_config_status evdev_scroll_set_button(struct libinput_device *device, uint32_t button) { struct evdev_device *evdev = evdev_device(device); evdev->scroll.want_button = button; evdev->scroll.change_scroll_method(evdev); return LIBINPUT_CONFIG_STATUS_SUCCESS; } static uint32_t evdev_scroll_get_button(struct libinput_device *device) { struct evdev_device *evdev = evdev_device(device); /* return the wanted configuration, even if it hasn't taken * effect yet! */ return evdev->scroll.want_button; } static uint32_t evdev_scroll_get_default_button(struct libinput_device *device) { struct evdev_device *evdev = evdev_device(device); unsigned int code; if (libevdev_has_event_code(evdev->evdev, EV_KEY, BTN_MIDDLE)) return BTN_MIDDLE; for (code = BTN_SIDE; code <= BTN_TASK; code++) { if (libevdev_has_event_code(evdev->evdev, EV_KEY, code)) return code; } if (libevdev_has_event_code(evdev->evdev, EV_KEY, BTN_RIGHT)) return BTN_RIGHT; return 0; } void evdev_init_button_scroll(struct evdev_device *device, void (*change_scroll_method)(struct evdev_device *)) { char timer_name[64]; snprintf(timer_name, sizeof(timer_name), "%s btnscroll", evdev_device_get_sysname(device)); libinput_timer_init(&device->scroll.timer, evdev_libinput_context(device), timer_name, evdev_button_scroll_timeout, device); device->scroll.config.get_methods = evdev_scroll_get_methods; device->scroll.config.set_method = evdev_scroll_set_method; device->scroll.config.get_method = evdev_scroll_get_method; device->scroll.config.get_default_method = evdev_scroll_get_default_method; device->scroll.config.set_button = evdev_scroll_set_button; device->scroll.config.get_button = evdev_scroll_get_button; device->scroll.config.get_default_button = evdev_scroll_get_default_button; device->base.config.scroll_method = &device->scroll.config; device->scroll.method = evdev_scroll_get_default_method((struct libinput_device *)device); device->scroll.want_method = device->scroll.method; device->scroll.button = evdev_scroll_get_default_button((struct libinput_device *)device); device->scroll.want_button = device->scroll.button; device->scroll.change_scroll_method = change_scroll_method; } void evdev_init_calibration(struct evdev_device *device, struct libinput_device_config_calibration *calibration) { device->base.config.calibration = calibration; calibration->has_matrix = evdev_calibration_has_matrix; calibration->set_matrix = evdev_calibration_set_matrix; calibration->get_matrix = evdev_calibration_get_matrix; calibration->get_default_matrix = evdev_calibration_get_default_matrix; } void evdev_init_sendevents(struct evdev_device *device, struct evdev_dispatch *dispatch) { device->base.config.sendevents = &dispatch->sendevents.config; dispatch->sendevents.current_mode = LIBINPUT_CONFIG_SEND_EVENTS_ENABLED; dispatch->sendevents.config.get_modes = evdev_sendevents_get_modes; dispatch->sendevents.config.set_mode = evdev_sendevents_set_mode; dispatch->sendevents.config.get_mode = evdev_sendevents_get_mode; dispatch->sendevents.config.get_default_mode = evdev_sendevents_get_default_mode; } static int evdev_scroll_config_natural_has(struct libinput_device *device) { return 1; } static enum libinput_config_status evdev_scroll_config_natural_set(struct libinput_device *device, int enabled) { struct evdev_device *dev = evdev_device(device); dev->scroll.natural_scrolling_enabled = enabled ? true : false; return LIBINPUT_CONFIG_STATUS_SUCCESS; } static int evdev_scroll_config_natural_get(struct libinput_device *device) { struct evdev_device *dev = evdev_device(device); return dev->scroll.natural_scrolling_enabled ? 1 : 0; } static int evdev_scroll_config_natural_get_default(struct libinput_device *device) { /* could enable this on Apple touchpads. could do that, could * very well do that... */ return 0; } void evdev_init_natural_scroll(struct evdev_device *device) { device->scroll.config_natural.has = evdev_scroll_config_natural_has; device->scroll.config_natural.set_enabled = evdev_scroll_config_natural_set; device->scroll.config_natural.get_enabled = evdev_scroll_config_natural_get; device->scroll.config_natural.get_default_enabled = evdev_scroll_config_natural_get_default; device->scroll.natural_scrolling_enabled = false; device->base.config.natural_scroll = &device->scroll.config_natural; } int evdev_need_mtdev(struct evdev_device *device) { struct libevdev *evdev = device->evdev; return (libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_X) && libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_Y) && !libevdev_has_event_code(evdev, EV_ABS, ABS_MT_SLOT)); } /* Fake MT devices have the ABS_MT_SLOT bit set because of the limited ABS_* range - they aren't MT devices, they just have too many ABS_ axes */ bool evdev_is_fake_mt_device(struct evdev_device *device) { struct libevdev *evdev = device->evdev; return libevdev_has_event_code(evdev, EV_ABS, ABS_MT_SLOT) && libevdev_get_num_slots(evdev) == -1; } enum switch_reliability evdev_read_switch_reliability_prop(struct evdev_device *device) { enum switch_reliability r; struct quirks_context *quirks; struct quirks *q; char *prop; quirks = evdev_libinput_context(device)->quirks; q = quirks_fetch_for_device(quirks, device->udev_device); if (!q || !quirks_get_string(q, QUIRK_ATTR_LID_SWITCH_RELIABILITY, &prop)) { r = RELIABILITY_UNKNOWN; } else if (!parse_switch_reliability_property(prop, &r)) { evdev_log_error(device, "%s: switch reliability set to unknown value '%s'\n", device->devname, prop); r = RELIABILITY_UNKNOWN; } else if (r == RELIABILITY_WRITE_OPEN) { evdev_log_info(device, "will write switch open events\n"); } quirks_unref(q); return r; } static inline void evdev_print_event(struct evdev_device *device, const struct input_event *e) { static uint32_t offset = 0; static uint32_t last_time = 0; uint32_t time = us2ms(tv2us(&e->time)); if (offset == 0) { offset = time; last_time = time - offset; } time -= offset; if (libevdev_event_is_code(e, EV_SYN, SYN_REPORT)) { evdev_log_debug(device, "%u.%03u -------------- EV_SYN ------------ +%ums\n", time / 1000, time % 1000, time - last_time); last_time = time; } else { evdev_log_debug(device, "%u.%03u %-16s %-20s %4d\n", time / 1000, time % 1000, libevdev_event_type_get_name(e->type), libevdev_event_code_get_name(e->type, e->code), e->value); } } static inline void evdev_process_event(struct evdev_device *device, struct input_event *e) { struct evdev_dispatch *dispatch = device->dispatch; uint64_t time = tv2us(&e->time); #if 0 evdev_print_event(device, e); #endif libinput_timer_flush(evdev_libinput_context(device), time); dispatch->interface->process(dispatch, device, e, time); } static inline void evdev_device_dispatch_one(struct evdev_device *device, struct input_event *ev) { if (!device->mtdev) { evdev_process_event(device, ev); } else { mtdev_put_event(device->mtdev, ev); if (libevdev_event_is_code(ev, EV_SYN, SYN_REPORT)) { while (!mtdev_empty(device->mtdev)) { struct input_event e; mtdev_get_event(device->mtdev, &e); evdev_process_event(device, &e); } } } } static int evdev_sync_device(struct evdev_device *device) { struct input_event ev; int rc; do { rc = libevdev_next_event(device->evdev, LIBEVDEV_READ_FLAG_SYNC, &ev); if (rc < 0) break; evdev_device_dispatch_one(device, &ev); } while (rc == LIBEVDEV_READ_STATUS_SYNC); return rc == -EAGAIN ? 0 : rc; } static void evdev_device_dispatch(void *data) { struct evdev_device *device = data; struct libinput *libinput = evdev_libinput_context(device); struct input_event ev; int rc; /* If the compositor is repainting, this function is called only once * per frame and we have to process all the events available on the * fd, otherwise there will be input lag. */ do { rc = libevdev_next_event(device->evdev, LIBEVDEV_READ_FLAG_NORMAL, &ev); if (rc == LIBEVDEV_READ_STATUS_SYNC) { evdev_log_info_ratelimit(device, &device->syn_drop_limit, "SYN_DROPPED event - some input events have been lost.\n"); /* send one more sync event so we handle all currently pending events before we sync up to the current state */ ev.code = SYN_REPORT; evdev_device_dispatch_one(device, &ev); rc = evdev_sync_device(device); if (rc == 0) rc = LIBEVDEV_READ_STATUS_SUCCESS; } else if (rc == LIBEVDEV_READ_STATUS_SUCCESS) { evdev_device_dispatch_one(device, &ev); } } while (rc == LIBEVDEV_READ_STATUS_SUCCESS); if (rc != -EAGAIN && rc != -EINTR) { libinput_remove_source(libinput, device->source); device->source = NULL; } } static inline bool evdev_init_accel(struct evdev_device *device, enum libinput_config_accel_profile which) { struct motion_filter *filter; if (which == LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT) filter = create_pointer_accelerator_filter_flat(device->dpi); else if (device->tags & EVDEV_TAG_TRACKPOINT) filter = create_pointer_accelerator_filter_trackpoint(device->trackpoint_multiplier, device->use_velocity_averaging); else if (device->dpi < DEFAULT_MOUSE_DPI) filter = create_pointer_accelerator_filter_linear_low_dpi(device->dpi, device->use_velocity_averaging); else filter = create_pointer_accelerator_filter_linear(device->dpi, device->use_velocity_averaging); if (!filter) return false; evdev_device_init_pointer_acceleration(device, filter); return true; } static int evdev_accel_config_available(struct libinput_device *device) { /* this function is only called if we set up ptraccel, so we can reply with a resounding "Yes" */ return 1; } static enum libinput_config_status evdev_accel_config_set_speed(struct libinput_device *device, double speed) { struct evdev_device *dev = evdev_device(device); if (!filter_set_speed(dev->pointer.filter, speed)) return LIBINPUT_CONFIG_STATUS_INVALID; return LIBINPUT_CONFIG_STATUS_SUCCESS; } static double evdev_accel_config_get_speed(struct libinput_device *device) { struct evdev_device *dev = evdev_device(device); return filter_get_speed(dev->pointer.filter); } static double evdev_accel_config_get_default_speed(struct libinput_device *device) { return 0.0; } static uint32_t evdev_accel_config_get_profiles(struct libinput_device *libinput_device) { struct evdev_device *device = evdev_device(libinput_device); if (!device->pointer.filter) return LIBINPUT_CONFIG_ACCEL_PROFILE_NONE; return LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE | LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT; } static enum libinput_config_status evdev_accel_config_set_profile(struct libinput_device *libinput_device, enum libinput_config_accel_profile profile) { struct evdev_device *device = evdev_device(libinput_device); struct motion_filter *filter; double speed; filter = device->pointer.filter; if (filter_get_type(filter) == profile) return LIBINPUT_CONFIG_STATUS_SUCCESS; speed = filter_get_speed(filter); device->pointer.filter = NULL; if (evdev_init_accel(device, profile)) { evdev_accel_config_set_speed(libinput_device, speed); filter_destroy(filter); } else { device->pointer.filter = filter; return LIBINPUT_CONFIG_STATUS_UNSUPPORTED; } return LIBINPUT_CONFIG_STATUS_SUCCESS; } static enum libinput_config_accel_profile evdev_accel_config_get_profile(struct libinput_device *libinput_device) { struct evdev_device *device = evdev_device(libinput_device); return filter_get_type(device->pointer.filter); } static enum libinput_config_accel_profile evdev_accel_config_get_default_profile(struct libinput_device *libinput_device) { struct evdev_device *device = evdev_device(libinput_device); if (!device->pointer.filter) return LIBINPUT_CONFIG_ACCEL_PROFILE_NONE; /* No device has a flat profile as default */ return LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE; } void evdev_device_init_pointer_acceleration(struct evdev_device *device, struct motion_filter *filter) { device->pointer.filter = filter; if (device->base.config.accel == NULL) { double default_speed; device->pointer.config.available = evdev_accel_config_available; device->pointer.config.set_speed = evdev_accel_config_set_speed; device->pointer.config.get_speed = evdev_accel_config_get_speed; device->pointer.config.get_default_speed = evdev_accel_config_get_default_speed; device->pointer.config.get_profiles = evdev_accel_config_get_profiles; device->pointer.config.set_profile = evdev_accel_config_set_profile; device->pointer.config.get_profile = evdev_accel_config_get_profile; device->pointer.config.get_default_profile = evdev_accel_config_get_default_profile; device->base.config.accel = &device->pointer.config; default_speed = evdev_accel_config_get_default_speed(&device->base); evdev_accel_config_set_speed(&device->base, default_speed); } } static inline bool evdev_read_wheel_click_prop(struct evdev_device *device, const char *prop, double *angle) { int val; *angle = DEFAULT_WHEEL_CLICK_ANGLE; prop = udev_device_get_property_value(device->udev_device, prop); if (!prop) return false; val = parse_mouse_wheel_click_angle_property(prop); if (val) { *angle = val; return true; } evdev_log_error(device, "mouse wheel click angle is present but invalid, " "using %d degrees instead\n", DEFAULT_WHEEL_CLICK_ANGLE); return false; } static inline bool evdev_read_wheel_click_count_prop(struct evdev_device *device, const char *prop, double *angle) { int val; prop = udev_device_get_property_value(device->udev_device, prop); if (!prop) return false; val = parse_mouse_wheel_click_angle_property(prop); if (val) { *angle = 360.0/val; return true; } evdev_log_error(device, "mouse wheel click count is present but invalid, " "using %d degrees for angle instead instead\n", DEFAULT_WHEEL_CLICK_ANGLE); *angle = DEFAULT_WHEEL_CLICK_ANGLE; return false; } static inline struct wheel_angle evdev_read_wheel_click_props(struct evdev_device *device) { struct wheel_angle angles; const char *wheel_count = "MOUSE_WHEEL_CLICK_COUNT"; const char *wheel_angle = "MOUSE_WHEEL_CLICK_ANGLE"; const char *hwheel_count = "MOUSE_WHEEL_CLICK_COUNT_HORIZONTAL"; const char *hwheel_angle = "MOUSE_WHEEL_CLICK_ANGLE_HORIZONTAL"; /* CLICK_COUNT overrides CLICK_ANGLE */ if (evdev_read_wheel_click_count_prop(device, wheel_count, &angles.y) || evdev_read_wheel_click_prop(device, wheel_angle, &angles.y)) { evdev_log_debug(device, "wheel: vert click angle: %.2f\n", angles.y); } if (evdev_read_wheel_click_count_prop(device, hwheel_count, &angles.x) || evdev_read_wheel_click_prop(device, hwheel_angle, &angles.x)) { evdev_log_debug(device, "wheel: horizontal click angle: %.2f\n", angles.y); } else { angles.x = angles.y; } return angles; } static inline struct wheel_tilt_flags evdev_read_wheel_tilt_props(struct evdev_device *device) { struct wheel_tilt_flags flags; flags.vertical = parse_udev_flag(device, device->udev_device, "MOUSE_WHEEL_TILT_VERTICAL"); flags.horizontal = parse_udev_flag(device, device->udev_device, "MOUSE_WHEEL_TILT_HORIZONTAL"); return flags; } static inline double evdev_get_trackpoint_multiplier(struct evdev_device *device) { struct quirks_context *quirks; struct quirks *q; double multiplier = 1.0; if (!(device->tags & EVDEV_TAG_TRACKPOINT)) return 1.0; quirks = evdev_libinput_context(device)->quirks; q = quirks_fetch_for_device(quirks, device->udev_device); if (q) { quirks_get_double(q, QUIRK_ATTR_TRACKPOINT_MULTIPLIER, &multiplier); quirks_unref(q); } if (multiplier <= 0.0) { evdev_log_bug_libinput(device, "trackpoint multiplier %.2f is invalid\n", multiplier); multiplier = 1.0; } if (multiplier != 1.0) evdev_log_info(device, "trackpoint multiplier is %.2f\n", multiplier); return multiplier; } static inline bool evdev_need_velocity_averaging(struct evdev_device *device) { struct quirks_context *quirks; struct quirks *q; bool use_velocity_averaging = false; /* default off unless we have quirk */ quirks = evdev_libinput_context(device)->quirks; q = quirks_fetch_for_device(quirks, device->udev_device); if (q) { quirks_get_bool(q, QUIRK_ATTR_USE_VELOCITY_AVERAGING, &use_velocity_averaging); quirks_unref(q); } if (use_velocity_averaging) evdev_log_info(device, "velocity averaging is turned on\n"); return use_velocity_averaging; } static inline int evdev_read_dpi_prop(struct evdev_device *device) { const char *mouse_dpi; int dpi = DEFAULT_MOUSE_DPI; if (device->tags & EVDEV_TAG_TRACKPOINT) return DEFAULT_MOUSE_DPI; mouse_dpi = udev_device_get_property_value(device->udev_device, "MOUSE_DPI"); if (mouse_dpi) { dpi = parse_mouse_dpi_property(mouse_dpi); if (!dpi) { evdev_log_error(device, "mouse DPI property is present but invalid, " "using %d DPI instead\n", DEFAULT_MOUSE_DPI); dpi = DEFAULT_MOUSE_DPI; } evdev_log_info(device, "device set to %d DPI\n", dpi); } return dpi; } static inline uint32_t evdev_read_model_flags(struct evdev_device *device) { const struct model_map { enum quirk quirk; enum evdev_device_model model; } model_map[] = { #define MODEL(name) { QUIRK_MODEL_##name, EVDEV_MODEL_##name } MODEL(WACOM_TOUCHPAD), MODEL(SYNAPTICS_SERIAL_TOUCHPAD), MODEL(LENOVO_T450_TOUCHPAD), MODEL(TRACKBALL), MODEL(APPLE_TOUCHPAD_ONEBUTTON), MODEL(LENOVO_SCROLLPOINT), #undef MODEL { 0, 0 }, }; const struct model_map *m = model_map; uint32_t model_flags = 0; uint32_t all_model_flags = 0; struct quirks_context *quirks; struct quirks *q; quirks = evdev_libinput_context(device)->quirks; q = quirks_fetch_for_device(quirks, device->udev_device); while (q && m->quirk) { bool is_set; /* Check for flag re-use */ assert((all_model_flags & m->model) == 0); all_model_flags |= m->model; if (quirks_get_bool(q, m->quirk, &is_set)) { if (is_set) { evdev_log_debug(device, "tagged as %s\n", quirk_get_name(m->quirk)); model_flags |= m->model; } else { evdev_log_debug(device, "untagged as %s\n", quirk_get_name(m->quirk)); model_flags &= ~m->model; } } m++; } quirks_unref(q); if (parse_udev_flag(device, device->udev_device, "ID_INPUT_TRACKBALL")) { evdev_log_debug(device, "tagged as trackball\n"); model_flags |= EVDEV_MODEL_TRACKBALL; } /** * Device is 6 years old at the time of writing this and this was * one of the few udev properties that wasn't reserved for private * usage, so we need to keep this for backwards compat. */ if (parse_udev_flag(device, device->udev_device, "LIBINPUT_MODEL_LENOVO_X220_TOUCHPAD_FW81")) { evdev_log_debug(device, "tagged as trackball\n"); model_flags |= EVDEV_MODEL_LENOVO_X220_TOUCHPAD_FW81; } if (parse_udev_flag(device, device->udev_device, "LIBINPUT_TEST_DEVICE")) { evdev_log_debug(device, "is a test device\n"); model_flags |= EVDEV_MODEL_TEST_DEVICE; } return model_flags; } static inline bool evdev_read_attr_res_prop(struct evdev_device *device, size_t *xres, size_t *yres) { struct quirks_context *quirks; struct quirks *q; struct quirk_dimensions dim; bool rc = false; quirks = evdev_libinput_context(device)->quirks; q = quirks_fetch_for_device(quirks, device->udev_device); if (!q) return false; rc = quirks_get_dimensions(q, QUIRK_ATTR_RESOLUTION_HINT, &dim); if (rc) { *xres = dim.x; *yres = dim.y; } quirks_unref(q); return rc; } static inline bool evdev_read_attr_size_prop(struct evdev_device *device, size_t *size_x, size_t *size_y) { struct quirks_context *quirks; struct quirks *q; struct quirk_dimensions dim; bool rc = false; quirks = evdev_libinput_context(device)->quirks; q = quirks_fetch_for_device(quirks, device->udev_device); if (!q) return false; rc = quirks_get_dimensions(q, QUIRK_ATTR_SIZE_HINT, &dim); if (rc) { *size_x = dim.x; *size_y = dim.y; } quirks_unref(q); return rc; } /* Return 1 if the device is set to the fake resolution or 0 otherwise */ static inline int evdev_fix_abs_resolution(struct evdev_device *device, unsigned int xcode, unsigned int ycode) { struct libevdev *evdev = device->evdev; const struct input_absinfo *absx, *absy; size_t widthmm = 0, heightmm = 0; size_t xres = EVDEV_FAKE_RESOLUTION, yres = EVDEV_FAKE_RESOLUTION; if (!(xcode == ABS_X && ycode == ABS_Y) && !(xcode == ABS_MT_POSITION_X && ycode == ABS_MT_POSITION_Y)) { evdev_log_bug_libinput(device, "invalid x/y code combination %d/%d\n", xcode, ycode); return 0; } absx = libevdev_get_abs_info(evdev, xcode); absy = libevdev_get_abs_info(evdev, ycode); if (absx->resolution != 0 || absy->resolution != 0) return 0; /* Note: we *do not* override resolutions if provided by the kernel. * If a device needs this, add it to 60-evdev.hwdb. The libinput * property is only for general size hints where we can make * educated guesses but don't know better. */ if (!evdev_read_attr_res_prop(device, &xres, &yres) && evdev_read_attr_size_prop(device, &widthmm, &heightmm)) { xres = (absx->maximum - absx->minimum)/widthmm; yres = (absy->maximum - absy->minimum)/heightmm; } /* libevdev_set_abs_resolution() changes the absinfo we already have a pointer to, no need to fetch it again */ libevdev_set_abs_resolution(evdev, xcode, xres); libevdev_set_abs_resolution(evdev, ycode, yres); return xres == EVDEV_FAKE_RESOLUTION; } static enum evdev_device_udev_tags evdev_device_get_udev_tags(struct evdev_device *device, struct udev_device *udev_device) { enum evdev_device_udev_tags tags = 0; int i; for (i = 0; i < 2 && udev_device; i++) { unsigned j; for (j = 0; j < ARRAY_LENGTH(evdev_udev_tag_matches); j++) { const struct evdev_udev_tag_match match = evdev_udev_tag_matches[j]; if (parse_udev_flag(device, udev_device, match.name)) tags |= match.tag; } udev_device = udev_device_get_parent(udev_device); } return tags; } static inline void evdev_fix_android_mt(struct evdev_device *device) { struct libevdev *evdev = device->evdev; if (libevdev_has_event_code(evdev, EV_ABS, ABS_X) || libevdev_has_event_code(evdev, EV_ABS, ABS_Y)) return; if (!libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_X) || !libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_Y) || evdev_is_fake_mt_device(device)) return; libevdev_enable_event_code(evdev, EV_ABS, ABS_X, libevdev_get_abs_info(evdev, ABS_MT_POSITION_X)); libevdev_enable_event_code(evdev, EV_ABS, ABS_Y, libevdev_get_abs_info(evdev, ABS_MT_POSITION_Y)); } static inline bool evdev_check_min_max(struct evdev_device *device, unsigned int code) { struct libevdev *evdev = device->evdev; const struct input_absinfo *absinfo; if (!libevdev_has_event_code(evdev, EV_ABS, code)) return true; absinfo = libevdev_get_abs_info(evdev, code); if (absinfo->minimum == absinfo->maximum) { /* Some devices have a sort-of legitimate min/max of 0 for * ABS_MISC and above (e.g. Roccat Kone XTD). Don't ignore * them, simply disable the axes so we won't get events, * we don't know what to do with them anyway. */ if (absinfo->minimum == 0 && code >= ABS_MISC && code < ABS_MT_SLOT) { evdev_log_info(device, "disabling EV_ABS %#x on device (min == max == 0)\n", code); libevdev_disable_event_code(device->evdev, EV_ABS, code); } else { evdev_log_bug_kernel(device, "device has min == max on %s\n", libevdev_event_code_get_name(EV_ABS, code)); return false; } } return true; } static bool evdev_reject_device(struct evdev_device *device) { struct libevdev *evdev = device->evdev; unsigned int code; const struct input_absinfo *absx, *absy; if (libevdev_has_event_code(evdev, EV_ABS, ABS_X) ^ libevdev_has_event_code(evdev, EV_ABS, ABS_Y)) return true; if (libevdev_has_event_code(evdev, EV_REL, REL_X) ^ libevdev_has_event_code(evdev, EV_REL, REL_Y)) return true; if (!evdev_is_fake_mt_device(device) && libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_X) ^ libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_Y)) return true; if (libevdev_has_event_code(evdev, EV_ABS, ABS_X)) { absx = libevdev_get_abs_info(evdev, ABS_X); absy = libevdev_get_abs_info(evdev, ABS_Y); if ((absx->resolution == 0 && absy->resolution != 0) || (absx->resolution != 0 && absy->resolution == 0)) { evdev_log_bug_kernel(device, "kernel has only x or y resolution, not both.\n"); return true; } } if (!evdev_is_fake_mt_device(device) && libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_X)) { absx = libevdev_get_abs_info(evdev, ABS_MT_POSITION_X); absy = libevdev_get_abs_info(evdev, ABS_MT_POSITION_Y); if ((absx->resolution == 0 && absy->resolution != 0) || (absx->resolution != 0 && absy->resolution == 0)) { evdev_log_bug_kernel(device, "kernel has only x or y MT resolution, not both.\n"); return true; } } for (code = 0; code < ABS_CNT; code++) { switch (code) { case ABS_MISC: case ABS_MT_SLOT: case ABS_MT_TOOL_TYPE: break; default: if (!evdev_check_min_max(device, code)) return true; } } return false; } static void evdev_extract_abs_axes(struct evdev_device *device) { struct libevdev *evdev = device->evdev; int fuzz; if (!libevdev_has_event_code(evdev, EV_ABS, ABS_X) || !libevdev_has_event_code(evdev, EV_ABS, ABS_Y)) return; if (evdev_fix_abs_resolution(device, ABS_X, ABS_Y)) device->abs.is_fake_resolution = true; if ((fuzz = evdev_read_fuzz_prop(device, ABS_X))) libevdev_set_abs_fuzz(evdev, ABS_X, fuzz); if ((fuzz = evdev_read_fuzz_prop(device, ABS_Y))) libevdev_set_abs_fuzz(evdev, ABS_Y, fuzz); device->abs.absinfo_x = libevdev_get_abs_info(evdev, ABS_X); device->abs.absinfo_y = libevdev_get_abs_info(evdev, ABS_Y); device->abs.dimensions.x = abs(device->abs.absinfo_x->maximum - device->abs.absinfo_x->minimum); device->abs.dimensions.y = abs(device->abs.absinfo_y->maximum - device->abs.absinfo_y->minimum); if (evdev_is_fake_mt_device(device) || !libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_X) || !libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_Y)) return; if (evdev_fix_abs_resolution(device, ABS_MT_POSITION_X, ABS_MT_POSITION_Y)) device->abs.is_fake_resolution = true; if ((fuzz = evdev_read_fuzz_prop(device, ABS_MT_POSITION_X))) libevdev_set_abs_fuzz(evdev, ABS_MT_POSITION_X, fuzz); if ((fuzz = evdev_read_fuzz_prop(device, ABS_MT_POSITION_Y))) libevdev_set_abs_fuzz(evdev, ABS_MT_POSITION_Y, fuzz); device->abs.absinfo_x = libevdev_get_abs_info(evdev, ABS_MT_POSITION_X); device->abs.absinfo_y = libevdev_get_abs_info(evdev, ABS_MT_POSITION_Y); device->abs.dimensions.x = abs(device->abs.absinfo_x->maximum - device->abs.absinfo_x->minimum); device->abs.dimensions.y = abs(device->abs.absinfo_y->maximum - device->abs.absinfo_y->minimum); device->is_mt = 1; } static void evdev_disable_accelerometer_axes(struct evdev_device *device) { struct libevdev *evdev = device->evdev; libevdev_disable_event_code(evdev, EV_ABS, ABS_X); libevdev_disable_event_code(evdev, EV_ABS, ABS_Y); libevdev_disable_event_code(evdev, EV_ABS, ABS_Z); libevdev_disable_event_code(evdev, EV_ABS, REL_X); libevdev_disable_event_code(evdev, EV_ABS, REL_Y); libevdev_disable_event_code(evdev, EV_ABS, REL_Z); } static struct evdev_dispatch * evdev_configure_device(struct evdev_device *device) { struct libevdev *evdev = device->evdev; enum evdev_device_udev_tags udev_tags; unsigned int tablet_tags; struct evdev_dispatch *dispatch; udev_tags = evdev_device_get_udev_tags(device, device->udev_device); if ((udev_tags & EVDEV_UDEV_TAG_INPUT) == 0 || (udev_tags & ~EVDEV_UDEV_TAG_INPUT) == 0) { evdev_log_info(device, "not tagged as supported input device\n"); return NULL; } evdev_log_info(device, "is tagged by udev as:%s%s%s%s%s%s%s%s%s%s%s\n", udev_tags & EVDEV_UDEV_TAG_KEYBOARD ? " Keyboard" : "", udev_tags & EVDEV_UDEV_TAG_MOUSE ? " Mouse" : "", udev_tags & EVDEV_UDEV_TAG_TOUCHPAD ? " Touchpad" : "", udev_tags & EVDEV_UDEV_TAG_TOUCHSCREEN ? " Touchscreen" : "", udev_tags & EVDEV_UDEV_TAG_TABLET ? " Tablet" : "", udev_tags & EVDEV_UDEV_TAG_POINTINGSTICK ? " Pointingstick" : "", udev_tags & EVDEV_UDEV_TAG_JOYSTICK ? " Joystick" : "", udev_tags & EVDEV_UDEV_TAG_ACCELEROMETER ? " Accelerometer" : "", udev_tags & EVDEV_UDEV_TAG_TABLET_PAD ? " TabletPad" : "", udev_tags & EVDEV_UDEV_TAG_TRACKBALL ? " Trackball" : "", udev_tags & EVDEV_UDEV_TAG_SWITCH ? " Switch" : ""); /* Ignore pure accelerometers, but accept devices that are * accelerometers with other axes */ if (udev_tags == (EVDEV_UDEV_TAG_INPUT|EVDEV_UDEV_TAG_ACCELEROMETER)) { evdev_log_info(device, "device is an accelerometer, ignoring\n"); return NULL; } else if (udev_tags & EVDEV_UDEV_TAG_ACCELEROMETER) { evdev_disable_accelerometer_axes(device); } /* libwacom *adds* TABLET, TOUCHPAD but leaves JOYSTICK in place, so make sure we only ignore real joystick devices */ if (udev_tags == (EVDEV_UDEV_TAG_INPUT|EVDEV_UDEV_TAG_JOYSTICK)) { evdev_log_info(device, "device is a joystick, ignoring\n"); return NULL; } if (evdev_reject_device(device)) { evdev_log_info(device, "was rejected\n"); return NULL; } if (!evdev_is_fake_mt_device(device)) evdev_fix_android_mt(device); if (libevdev_has_event_code(evdev, EV_ABS, ABS_X)) { evdev_extract_abs_axes(device); if (evdev_is_fake_mt_device(device)) udev_tags &= ~EVDEV_UDEV_TAG_TOUCHSCREEN; } /* libwacom assigns touchpad (or touchscreen) _and_ tablet to the tablet touch bits, so make sure we don't initialize the tablet interface for the touch device */ tablet_tags = EVDEV_UDEV_TAG_TABLET | EVDEV_UDEV_TAG_TOUCHPAD | EVDEV_UDEV_TAG_TOUCHSCREEN; /* libwacom assigns tablet _and_ tablet_pad to the pad devices */ if (udev_tags & EVDEV_UDEV_TAG_TABLET_PAD) { dispatch = evdev_tablet_pad_create(device); device->seat_caps |= EVDEV_DEVICE_TABLET_PAD; evdev_log_info(device, "device is a tablet pad\n"); return dispatch; } else if ((udev_tags & tablet_tags) == EVDEV_UDEV_TAG_TABLET) { dispatch = evdev_tablet_create(device); device->seat_caps |= EVDEV_DEVICE_TABLET; evdev_log_info(device, "device is a tablet\n"); return dispatch; } if (udev_tags & EVDEV_UDEV_TAG_TOUCHPAD) { if (udev_tags & EVDEV_UDEV_TAG_TABLET) evdev_tag_tablet_touchpad(device); /* whether velocity should be averaged, false by default */ device->use_velocity_averaging = evdev_need_velocity_averaging(device); dispatch = evdev_mt_touchpad_create(device); evdev_log_info(device, "device is a touchpad\n"); return dispatch; } if (udev_tags & EVDEV_UDEV_TAG_MOUSE || udev_tags & EVDEV_UDEV_TAG_POINTINGSTICK) { evdev_tag_external_mouse(device, device->udev_device); evdev_tag_trackpoint(device, device->udev_device); device->dpi = evdev_read_dpi_prop(device); device->trackpoint_multiplier = evdev_get_trackpoint_multiplier(device); /* whether velocity should be averaged, false by default */ device->use_velocity_averaging = evdev_need_velocity_averaging(device); device->seat_caps |= EVDEV_DEVICE_POINTER; evdev_log_info(device, "device is a pointer\n"); /* want left-handed config option */ device->left_handed.want_enabled = true; /* want natural-scroll config option */ device->scroll.natural_scrolling_enabled = true; /* want button scrolling config option */ if (libevdev_has_event_code(evdev, EV_REL, REL_X) || libevdev_has_event_code(evdev, EV_REL, REL_Y)) device->scroll.want_button = 1; } if (udev_tags & EVDEV_UDEV_TAG_KEYBOARD) { device->seat_caps |= EVDEV_DEVICE_KEYBOARD; evdev_log_info(device, "device is a keyboard\n"); /* want natural-scroll config option */ if (libevdev_has_event_code(evdev, EV_REL, REL_WHEEL) || libevdev_has_event_code(evdev, EV_REL, REL_HWHEEL)) { device->scroll.natural_scrolling_enabled = true; device->seat_caps |= EVDEV_DEVICE_POINTER; } evdev_tag_keyboard(device, device->udev_device); } if (udev_tags & EVDEV_UDEV_TAG_TOUCHSCREEN) { device->seat_caps |= EVDEV_DEVICE_TOUCH; evdev_log_info(device, "device is a touch device\n"); } if (udev_tags & EVDEV_UDEV_TAG_SWITCH) { if (libevdev_has_event_code(evdev, EV_SW, SW_LID)) { device->seat_caps |= EVDEV_DEVICE_SWITCH; device->tags |= EVDEV_TAG_LID_SWITCH; evdev_log_info(device, "device is a switch device\n"); } if (libevdev_has_event_code(evdev, EV_SW, SW_TABLET_MODE)) { device->seat_caps |= EVDEV_DEVICE_SWITCH; device->tags |= EVDEV_TAG_TABLET_MODE_SWITCH; evdev_log_info(device, "device is a switch device\n"); } } if (device->seat_caps & EVDEV_DEVICE_POINTER && libevdev_has_event_code(evdev, EV_REL, REL_X) && libevdev_has_event_code(evdev, EV_REL, REL_Y) && !evdev_init_accel(device, LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE)) { evdev_log_error(device, "failed to initialize pointer acceleration\n"); return NULL; } return fallback_dispatch_create(&device->base); } static void evdev_notify_added_device(struct evdev_device *device) { struct libinput_device *dev; list_for_each(dev, &device->base.seat->devices_list, link) { struct evdev_device *d = evdev_device(dev); if (dev == &device->base) continue; /* Notify existing device d about addition of device */ if (d->dispatch->interface->device_added) d->dispatch->interface->device_added(d, device); /* Notify new device about existing device d */ if (device->dispatch->interface->device_added) device->dispatch->interface->device_added(device, d); /* Notify new device if existing device d is suspended */ if (d->is_suspended && device->dispatch->interface->device_suspended) device->dispatch->interface->device_suspended(device, d); } notify_added_device(&device->base); if (device->dispatch->interface->post_added) device->dispatch->interface->post_added(device, device->dispatch); } static bool evdev_device_have_same_syspath(struct udev_device *udev_device, int fd) { struct udev *udev = udev_device_get_udev(udev_device); struct udev_device *udev_device_new = NULL; struct stat st; bool rc = false; if (fstat(fd, &st) < 0) goto out; udev_device_new = udev_device_new_from_devnum(udev, 'c', st.st_rdev); if (!udev_device_new) goto out; rc = streq(udev_device_get_syspath(udev_device_new), udev_device_get_syspath(udev_device)); out: if (udev_device_new) udev_device_unref(udev_device_new); return rc; } static bool evdev_set_device_group(struct evdev_device *device, struct udev_device *udev_device) { struct libinput *libinput = evdev_libinput_context(device); struct libinput_device_group *group = NULL; const char *udev_group; udev_group = udev_device_get_property_value(udev_device, "LIBINPUT_DEVICE_GROUP"); if (udev_group) group = libinput_device_group_find_group(libinput, udev_group); if (!group) { group = libinput_device_group_create(libinput, udev_group); if (!group) return false; libinput_device_set_device_group(&device->base, group); libinput_device_group_unref(group); } else { libinput_device_set_device_group(&device->base, group); } return true; } static inline void evdev_drain_fd(int fd) { struct input_event ev[24]; size_t sz = sizeof ev; while (read(fd, &ev, sz) == (int)sz) { /* discard all pending events */ } } static inline void evdev_pre_configure_model_quirks(struct evdev_device *device) { struct quirks_context *quirks; struct quirks *q; const struct quirk_tuples *t; char *prop; /* Touchpad is a clickpad but INPUT_PROP_BUTTONPAD is not set, see * fdo bug 97147. Remove when RMI4 is commonplace */ if (evdev_device_has_model_quirk(device, QUIRK_MODEL_HP_STREAM11_TOUCHPAD)) libevdev_enable_property(device->evdev, INPUT_PROP_BUTTONPAD); /* Touchpad is a clickpad but INPUT_PROP_BUTTONPAD is not set, see * https://gitlab.freedesktop.org/libinput/libinput/issues/177 */ if (evdev_device_has_model_quirk(device, QUIRK_MODEL_LENOVO_T480S_TOUCHPAD)) libevdev_enable_property(device->evdev, INPUT_PROP_BUTTONPAD); /* Touchpad claims to have 4 slots but only ever sends 2 * https://bugs.freedesktop.org/show_bug.cgi?id=98100 */ if (evdev_device_has_model_quirk(device, QUIRK_MODEL_HP_ZBOOK_STUDIO_G3)) libevdev_set_abs_maximum(device->evdev, ABS_MT_SLOT, 1); /* Generally we don't care about MSC_TIMESTAMP and it can cause * unnecessary wakeups but on some devices we need to watch it for * pointer jumps */ quirks = evdev_libinput_context(device)->quirks; q = quirks_fetch_for_device(quirks, device->udev_device); if (!q || !quirks_get_string(q, QUIRK_ATTR_MSC_TIMESTAMP, &prop) || !streq(prop, "watch")) { libevdev_disable_event_code(device->evdev, EV_MSC, MSC_TIMESTAMP); } if (q && quirks_get_tuples(q, QUIRK_ATTR_EVENT_CODE_DISABLE, &t)) { int type, code; for (size_t i = 0; i < t->ntuples; i++) { type = t->tuples[i].first; code = t->tuples[i].second; if (code == EVENT_CODE_UNDEFINED) libevdev_disable_event_type(device->evdev, type); else libevdev_disable_event_code(device->evdev, type, code); evdev_log_debug(device, "quirks: disabling %s %s (%#x %#x)\n", libevdev_event_type_get_name(type), libevdev_event_code_get_name(type, code), type, code); } } quirks_unref(q); } static void libevdev_log_func(const struct libevdev *evdev, enum libevdev_log_priority priority, void *data, const char *file, int line, const char *func, const char *format, va_list args) { struct libinput *libinput = data; enum libinput_log_priority pri = LIBEVDEV_LOG_ERROR; const char prefix[] = "libevdev: "; char fmt[strlen(format) + strlen(prefix) + 1]; switch (priority) { case LIBEVDEV_LOG_ERROR: pri = LIBINPUT_LOG_PRIORITY_ERROR; break; case LIBEVDEV_LOG_INFO: pri = LIBINPUT_LOG_PRIORITY_INFO; break; case LIBEVDEV_LOG_DEBUG: pri = LIBINPUT_LOG_PRIORITY_DEBUG; break; } snprintf(fmt, sizeof(fmt), "%s%s", prefix, format); log_msg_va(libinput, pri, fmt, args); } static bool udev_device_should_be_ignored(struct udev_device *udev_device) { const char *value; value = udev_device_get_property_value(udev_device, "LIBINPUT_IGNORE_DEVICE"); return value && !streq(value, "0"); } struct evdev_device * evdev_device_create(struct libinput_seat *seat, struct udev_device *udev_device) { struct libinput *libinput = seat->libinput; struct evdev_device *device = NULL; int rc; int fd; int unhandled_device = 0; const char *devnode = udev_device_get_devnode(udev_device); const char *sysname = udev_device_get_sysname(udev_device); if (!devnode) { log_info(libinput, "%s: no device node associated\n", sysname); return NULL; } if (udev_device_should_be_ignored(udev_device)) { log_debug(libinput, "%s: device is ignored\n", sysname); return NULL; } /* Use non-blocking mode so that we can loop on read on * evdev_device_data() until all events on the fd are * read. mtdev_get() also expects this. */ fd = open_restricted(libinput, devnode, O_RDWR | O_NONBLOCK | O_CLOEXEC); if (fd < 0) { log_info(libinput, "%s: opening input device '%s' failed (%s).\n", sysname, devnode, strerror(-fd)); return NULL; } if (!evdev_device_have_same_syspath(udev_device, fd)) goto err; device = zalloc(sizeof *device); libinput_device_init(&device->base, seat); libinput_seat_ref(seat); evdev_drain_fd(fd); rc = libevdev_new_from_fd(fd, &device->evdev); if (rc != 0) goto err; libevdev_set_clock_id(device->evdev, CLOCK_MONOTONIC); libevdev_set_device_log_function(device->evdev, libevdev_log_func, LIBEVDEV_LOG_ERROR, libinput); device->seat_caps = 0; device->is_mt = 0; device->mtdev = NULL; device->udev_device = udev_device_ref(udev_device); device->dispatch = NULL; device->fd = fd; device->devname = libevdev_get_name(device->evdev); device->scroll.threshold = 5.0; /* Default may be overridden */ device->scroll.direction_lock_threshold = 5.0; /* Default may be overridden */ device->scroll.direction = 0; device->scroll.wheel_click_angle = evdev_read_wheel_click_props(device); device->scroll.is_tilt = evdev_read_wheel_tilt_props(device); device->model_flags = evdev_read_model_flags(device); device->dpi = DEFAULT_MOUSE_DPI; /* at most 5 SYN_DROPPED log-messages per 30s */ ratelimit_init(&device->syn_drop_limit, s2us(30), 5); /* at most 5 log-messages per 5s */ ratelimit_init(&device->nonpointer_rel_limit, s2us(5), 5); matrix_init_identity(&device->abs.calibration); matrix_init_identity(&device->abs.usermatrix); matrix_init_identity(&device->abs.default_calibration); evdev_pre_configure_model_quirks(device); device->dispatch = evdev_configure_device(device); if (device->dispatch == NULL) { if (device->seat_caps == 0) unhandled_device = 1; goto err; } device->source = libinput_add_fd(libinput, fd, evdev_device_dispatch, device); if (!device->source) goto err; if (!evdev_set_device_group(device, udev_device)) goto err; list_insert(seat->devices_list.prev, &device->base.link); evdev_notify_added_device(device); return device; err: if (fd >= 0) close_restricted(libinput, fd); if (device) evdev_device_destroy(device); return unhandled_device ? EVDEV_UNHANDLED_DEVICE : NULL; } const char * evdev_device_get_output(struct evdev_device *device) { return device->output_name; } const char * evdev_device_get_sysname(struct evdev_device *device) { return udev_device_get_sysname(device->udev_device); } const char * evdev_device_get_name(struct evdev_device *device) { return device->devname; } unsigned int evdev_device_get_id_product(struct evdev_device *device) { return libevdev_get_id_product(device->evdev); } unsigned int evdev_device_get_id_vendor(struct evdev_device *device) { return libevdev_get_id_vendor(device->evdev); } struct udev_device * evdev_device_get_udev_device(struct evdev_device *device) { return udev_device_ref(device->udev_device); } void evdev_device_set_default_calibration(struct evdev_device *device, const float calibration[6]) { matrix_from_farray6(&device->abs.default_calibration, calibration); evdev_device_calibrate(device, calibration); } void evdev_device_calibrate(struct evdev_device *device, const float calibration[6]) { struct matrix scale, translate, transform; double sx, sy; matrix_from_farray6(&transform, calibration); device->abs.apply_calibration = !matrix_is_identity(&transform); if (!device->abs.apply_calibration) { matrix_init_identity(&device->abs.calibration); return; } sx = device->abs.absinfo_x->maximum - device->abs.absinfo_x->minimum + 1; sy = device->abs.absinfo_y->maximum - device->abs.absinfo_y->minimum + 1; /* The transformation matrix is in the form: * [ a b c ] * [ d e f ] * [ 0 0 1 ] * Where a, e are the scale components, a, b, d, e are the rotation * component (combined with scale) and c and f are the translation * component. The translation component in the input matrix must be * normalized to multiples of the device width and height, * respectively. e.g. c == 1 shifts one device-width to the right. * * We pre-calculate a single matrix to apply to event coordinates: * M = Un-Normalize * Calibration * Normalize * * Normalize: scales the device coordinates to [0,1] * Calibration: user-supplied matrix * Un-Normalize: scales back up to device coordinates * Matrix maths requires the normalize/un-normalize in reverse * order. */ /* back up the user matrix so we can return it on request */ matrix_from_farray6(&device->abs.usermatrix, calibration); /* Un-Normalize */ matrix_init_translate(&translate, device->abs.absinfo_x->minimum, device->abs.absinfo_y->minimum); matrix_init_scale(&scale, sx, sy); matrix_mult(&scale, &translate, &scale); /* Calibration */ matrix_mult(&transform, &scale, &transform); /* Normalize */ matrix_init_translate(&translate, -device->abs.absinfo_x->minimum/sx, -device->abs.absinfo_y->minimum/sy); matrix_init_scale(&scale, 1.0/sx, 1.0/sy); matrix_mult(&scale, &translate, &scale); /* store final matrix in device */ matrix_mult(&device->abs.calibration, &transform, &scale); } void evdev_read_calibration_prop(struct evdev_device *device) { const char *prop; float calibration[6]; prop = udev_device_get_property_value(device->udev_device, "LIBINPUT_CALIBRATION_MATRIX"); if (prop == NULL) return; if (!device->abs.absinfo_x || !device->abs.absinfo_y) return; if (!parse_calibration_property(prop, calibration)) return; evdev_device_set_default_calibration(device, calibration); evdev_log_info(device, "applying calibration: %f %f %f %f %f %f\n", calibration[0], calibration[1], calibration[2], calibration[3], calibration[4], calibration[5]); } int evdev_read_fuzz_prop(struct evdev_device *device, unsigned int code) { const char *prop; char name[32]; int rc; int fuzz = 0; rc = snprintf(name, sizeof(name), "LIBINPUT_FUZZ_%02x", code); if (rc == -1) return 0; prop = udev_device_get_property_value(device->udev_device, name); if (prop == NULL) return 0; rc = safe_atoi(prop, &fuzz); if (rc == -1 || fuzz < 0) { evdev_log_bug_libinput(device, "invalid LIBINPUT_FUZZ property value: %s\n", prop); return 0; } return fuzz; } bool evdev_device_has_capability(struct evdev_device *device, enum libinput_device_capability capability) { switch (capability) { case LIBINPUT_DEVICE_CAP_POINTER: return !!(device->seat_caps & EVDEV_DEVICE_POINTER); case LIBINPUT_DEVICE_CAP_KEYBOARD: return !!(device->seat_caps & EVDEV_DEVICE_KEYBOARD); case LIBINPUT_DEVICE_CAP_TOUCH: return !!(device->seat_caps & EVDEV_DEVICE_TOUCH); case LIBINPUT_DEVICE_CAP_GESTURE: return !!(device->seat_caps & EVDEV_DEVICE_GESTURE); case LIBINPUT_DEVICE_CAP_TABLET_TOOL: return !!(device->seat_caps & EVDEV_DEVICE_TABLET); case LIBINPUT_DEVICE_CAP_TABLET_PAD: return !!(device->seat_caps & EVDEV_DEVICE_TABLET_PAD); case LIBINPUT_DEVICE_CAP_SWITCH: return !!(device->seat_caps & EVDEV_DEVICE_SWITCH); default: return false; } } int evdev_device_get_size(const struct evdev_device *device, double *width, double *height) { const struct input_absinfo *x, *y; x = libevdev_get_abs_info(device->evdev, ABS_X); y = libevdev_get_abs_info(device->evdev, ABS_Y); if (!x || !y || device->abs.is_fake_resolution || !x->resolution || !y->resolution) return -1; *width = evdev_convert_to_mm(x, x->maximum); *height = evdev_convert_to_mm(y, y->maximum); return 0; } int evdev_device_has_button(struct evdev_device *device, uint32_t code) { if (!(device->seat_caps & EVDEV_DEVICE_POINTER)) return -1; return libevdev_has_event_code(device->evdev, EV_KEY, code); } int evdev_device_has_key(struct evdev_device *device, uint32_t code) { if (!(device->seat_caps & EVDEV_DEVICE_KEYBOARD)) return -1; return libevdev_has_event_code(device->evdev, EV_KEY, code); } int evdev_device_get_touch_count(struct evdev_device *device) { int ntouches; if (!(device->seat_caps & EVDEV_DEVICE_TOUCH)) return -1; ntouches = libevdev_get_num_slots(device->evdev); if (ntouches == -1) { /* mtdev devices have multitouch but we don't know * how many. Otherwise, any touch device with num_slots of * -1 is a single-touch device */ if (device->mtdev) ntouches = 0; else ntouches = 1; } return ntouches; } int evdev_device_has_switch(struct evdev_device *device, enum libinput_switch sw) { unsigned int code; if (!(device->seat_caps & EVDEV_DEVICE_SWITCH)) return -1; switch (sw) { case LIBINPUT_SWITCH_LID: code = SW_LID; break; case LIBINPUT_SWITCH_TABLET_MODE: code = SW_TABLET_MODE; break; default: return -1; } return libevdev_has_event_code(device->evdev, EV_SW, code); } static inline bool evdev_is_scrolling(const struct evdev_device *device, enum libinput_pointer_axis axis) { assert(axis == LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL || axis == LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL); return (device->scroll.direction & AS_MASK(axis)) != 0; } static inline void evdev_start_scrolling(struct evdev_device *device, enum libinput_pointer_axis axis) { assert(axis == LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL || axis == LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL); device->scroll.direction |= AS_MASK(axis); } void evdev_post_scroll(struct evdev_device *device, uint64_t time, enum libinput_pointer_axis_source source, const struct normalized_coords *delta) { const struct normalized_coords *trigger; struct normalized_coords event; if (!evdev_is_scrolling(device, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL)) device->scroll.buildup.y += delta->y; if (!evdev_is_scrolling(device, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL)) device->scroll.buildup.x += delta->x; trigger = &device->scroll.buildup; /* If we're not scrolling yet, use a distance trigger: moving past a certain distance starts scrolling */ if (!evdev_is_scrolling(device, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL) && !evdev_is_scrolling(device, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL)) { if (fabs(trigger->y) >= device->scroll.threshold) evdev_start_scrolling(device, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL); if (fabs(trigger->x) >= device->scroll.threshold) evdev_start_scrolling(device, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL); /* We're already scrolling in one direction. Require some trigger speed to start scrolling in the other direction */ } else if (!evdev_is_scrolling(device, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL)) { if (fabs(delta->y) >= device->scroll.direction_lock_threshold) evdev_start_scrolling(device, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL); } else if (!evdev_is_scrolling(device, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL)) { if (fabs(delta->x) >= device->scroll.direction_lock_threshold) evdev_start_scrolling(device, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL); } event = *delta; /* We use the trigger to enable, but the delta from this event for * the actual scroll movement. Otherwise we get a jump once * scrolling engages */ if (!evdev_is_scrolling(device, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL)) event.y = 0.0; if (!evdev_is_scrolling(device, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL)) event.x = 0.0; if (!normalized_is_zero(event)) { const struct discrete_coords zero_discrete = { 0.0, 0.0 }; uint32_t axes = device->scroll.direction; if (event.y == 0.0) axes &= ~AS_MASK(LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL); if (event.x == 0.0) axes &= ~AS_MASK(LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL); evdev_notify_axis(device, time, axes, source, &event, &zero_discrete); } } void evdev_stop_scroll(struct evdev_device *device, uint64_t time, enum libinput_pointer_axis_source source) { const struct normalized_coords zero = { 0.0, 0.0 }; const struct discrete_coords zero_discrete = { 0.0, 0.0 }; /* terminate scrolling with a zero scroll event */ if (device->scroll.direction != 0) pointer_notify_axis(&device->base, time, device->scroll.direction, source, &zero, &zero_discrete); device->scroll.buildup.x = 0; device->scroll.buildup.y = 0; device->scroll.direction = 0; } void evdev_notify_suspended_device(struct evdev_device *device) { struct libinput_device *it; if (device->is_suspended) return; list_for_each(it, &device->base.seat->devices_list, link) { struct evdev_device *d = evdev_device(it); if (it == &device->base) continue; if (d->dispatch->interface->device_suspended) d->dispatch->interface->device_suspended(d, device); } device->is_suspended = true; } void evdev_notify_resumed_device(struct evdev_device *device) { struct libinput_device *it; if (!device->is_suspended) return; list_for_each(it, &device->base.seat->devices_list, link) { struct evdev_device *d = evdev_device(it); if (it == &device->base) continue; if (d->dispatch->interface->device_resumed) d->dispatch->interface->device_resumed(d, device); } device->is_suspended = false; } void evdev_device_suspend(struct evdev_device *device) { struct libinput *libinput = evdev_libinput_context(device); evdev_notify_suspended_device(device); if (device->dispatch->interface->suspend) device->dispatch->interface->suspend(device->dispatch, device); if (device->source) { libinput_remove_source(libinput, device->source); device->source = NULL; } if (device->mtdev) { mtdev_close_delete(device->mtdev); device->mtdev = NULL; } if (device->fd != -1) { close_restricted(libinput, device->fd); device->fd = -1; } } int evdev_device_resume(struct evdev_device *device) { struct libinput *libinput = evdev_libinput_context(device); int fd; const char *devnode; struct input_event ev; enum libevdev_read_status status; if (device->fd != -1) return 0; if (device->was_removed) return -ENODEV; devnode = udev_device_get_devnode(device->udev_device); if (!devnode) return -ENODEV; fd = open_restricted(libinput, devnode, O_RDWR | O_NONBLOCK | O_CLOEXEC); if (fd < 0) return -errno; if (!evdev_device_have_same_syspath(device->udev_device, fd)) { close_restricted(libinput, fd); return -ENODEV; } evdev_drain_fd(fd); device->fd = fd; if (evdev_need_mtdev(device)) { device->mtdev = mtdev_new_open(device->fd); if (!device->mtdev) return -ENODEV; } libevdev_change_fd(device->evdev, fd); libevdev_set_clock_id(device->evdev, CLOCK_MONOTONIC); /* re-sync libevdev's view of the device, but discard the actual events. Our device is in a neutral state already */ libevdev_next_event(device->evdev, LIBEVDEV_READ_FLAG_FORCE_SYNC, &ev); do { status = libevdev_next_event(device->evdev, LIBEVDEV_READ_FLAG_SYNC, &ev); } while (status == LIBEVDEV_READ_STATUS_SYNC); device->source = libinput_add_fd(libinput, fd, evdev_device_dispatch, device); if (!device->source) { mtdev_close_delete(device->mtdev); return -ENOMEM; } evdev_notify_resumed_device(device); return 0; } void evdev_device_remove(struct evdev_device *device) { struct libinput_device *dev; evdev_log_info(device, "device removed\n"); list_for_each(dev, &device->base.seat->devices_list, link) { struct evdev_device *d = evdev_device(dev); if (dev == &device->base) continue; if (d->dispatch->interface->device_removed) d->dispatch->interface->device_removed(d, device); } evdev_device_suspend(device); if (device->dispatch->interface->remove) device->dispatch->interface->remove(device->dispatch); /* A device may be removed while suspended, mark it to * skip re-opening a different device with the same node */ device->was_removed = true; list_remove(&device->base.link); notify_removed_device(&device->base); libinput_device_unref(&device->base); } void evdev_device_destroy(struct evdev_device *device) { struct evdev_dispatch *dispatch; dispatch = device->dispatch; if (dispatch) dispatch->interface->destroy(dispatch); if (device->base.group) libinput_device_group_unref(device->base.group); free(device->output_name); filter_destroy(device->pointer.filter); libinput_timer_destroy(&device->scroll.timer); libinput_timer_destroy(&device->middlebutton.timer); libinput_seat_unref(device->base.seat); libevdev_free(device->evdev); udev_device_unref(device->udev_device); free(device); } bool evdev_tablet_has_left_handed(struct evdev_device *device) { bool has_left_handed = false; #if HAVE_LIBWACOM WacomDeviceDatabase *db; WacomDevice *d = NULL; WacomError *error; const char *devnode; db = libwacom_database_new(); if (!db) { evdev_log_info(device, "failed to initialize libwacom context.\n"); goto out; } error = libwacom_error_new(); devnode = udev_device_get_devnode(device->udev_device); d = libwacom_new_from_path(db, devnode, WFALLBACK_NONE, error); if (d) { if (libwacom_is_reversible(d)) has_left_handed = true; } else if (libwacom_error_get_code(error) == WERROR_UNKNOWN_MODEL) { evdev_log_info(device, "tablet '%s' unknown to libwacom\n", device->devname); } else { evdev_log_error(device, "libwacom error: %s\n", libwacom_error_get_message(error)); } if (error) libwacom_error_free(&error); if (d) libwacom_destroy(d); libwacom_database_destroy(db); out: #endif return has_left_handed; }