/* * 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 "evdev-fallback.h" static void fallback_keyboard_notify_key(struct fallback_dispatch *dispatch, struct evdev_device *device, uint64_t time, int key, enum libinput_key_state state) { int down_count; down_count = evdev_update_key_down_count(device, key, state); if ((state == LIBINPUT_KEY_STATE_PRESSED && down_count == 1) || (state == LIBINPUT_KEY_STATE_RELEASED && down_count == 0)) keyboard_notify_key(&device->base, time, key, state); } static void fallback_lid_notify_toggle(struct fallback_dispatch *dispatch, struct evdev_device *device, uint64_t time) { if (dispatch->lid.is_closed ^ dispatch->lid.is_closed_client_state) { switch_notify_toggle(&device->base, time, LIBINPUT_SWITCH_LID, dispatch->lid.is_closed); dispatch->lid.is_closed_client_state = dispatch->lid.is_closed; } } static enum libinput_switch_state fallback_interface_get_switch_state(struct evdev_dispatch *evdev_dispatch, enum libinput_switch sw) { struct fallback_dispatch *dispatch = fallback_dispatch(evdev_dispatch); switch (sw) { case LIBINPUT_SWITCH_TABLET_MODE: break; default: /* Internal function only, so we can abort here */ abort(); } return dispatch->tablet_mode.sw.state ? LIBINPUT_SWITCH_STATE_ON : LIBINPUT_SWITCH_STATE_OFF; } static inline void normalize_delta(struct evdev_device *device, const struct device_coords *delta, struct normalized_coords *normalized) { normalized->x = delta->x * DEFAULT_MOUSE_DPI / (double)device->dpi; normalized->y = delta->y * DEFAULT_MOUSE_DPI / (double)device->dpi; } static inline bool post_trackpoint_scroll(struct evdev_device *device, struct normalized_coords unaccel, uint64_t time) { if (device->scroll.method != LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN) return false; switch(device->scroll.button_scroll_state) { case BUTTONSCROLL_IDLE: return false; case BUTTONSCROLL_BUTTON_DOWN: /* if the button is down but scroll is not active, we're within the timeout where swallow motion events but don't post scroll buttons */ evdev_log_debug(device, "btnscroll: discarding\n"); return true; case BUTTONSCROLL_READY: device->scroll.button_scroll_state = BUTTONSCROLL_SCROLLING; /* fallthrough */ case BUTTONSCROLL_SCROLLING: evdev_post_scroll(device, time, LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS, &unaccel); return true; } assert(!"invalid scroll button state"); } static inline bool fallback_filter_defuzz_touch(struct fallback_dispatch *dispatch, struct evdev_device *device, struct mt_slot *slot) { struct device_coords point; if (!dispatch->mt.want_hysteresis) return false; point = evdev_hysteresis(&slot->point, &slot->hysteresis_center, &dispatch->mt.hysteresis_margin); slot->point = point; if (point.x == slot->hysteresis_center.x && point.y == slot->hysteresis_center.y) return true; slot->hysteresis_center = point; return false; } static inline void fallback_rotate_relative(struct fallback_dispatch *dispatch, struct evdev_device *device) { struct device_coords rel = dispatch->rel; if (!device->base.config.rotation) return; /* loss of precision for non-90 degrees, but we only support 90 deg * right now anyway */ matrix_mult_vec(&dispatch->rotation.matrix, &rel.x, &rel.y); dispatch->rel = rel; } static void fallback_flush_relative_motion(struct fallback_dispatch *dispatch, struct evdev_device *device, uint64_t time) { struct libinput_device *base = &device->base; struct normalized_coords accel, unaccel; struct device_float_coords raw; if (!(device->seat_caps & EVDEV_DEVICE_POINTER)) return; fallback_rotate_relative(dispatch, device); normalize_delta(device, &dispatch->rel, &unaccel); raw.x = dispatch->rel.x; raw.y = dispatch->rel.y; dispatch->rel.x = 0; dispatch->rel.y = 0; /* Use unaccelerated deltas for pointing stick scroll */ if (post_trackpoint_scroll(device, unaccel, time)) return; if (device->pointer.filter) { /* Apply pointer acceleration. */ accel = filter_dispatch(device->pointer.filter, &raw, device, time); } else { evdev_log_bug_libinput(device, "accel filter missing\n"); accel = unaccel; } if (normalized_is_zero(accel) && normalized_is_zero(unaccel)) return; pointer_notify_motion(base, time, &accel, &raw); } static void fallback_flush_wheels(struct fallback_dispatch *dispatch, struct evdev_device *device, uint64_t time) { struct normalized_coords wheel_degrees = { 0.0, 0.0 }; struct discrete_coords discrete = { 0.0, 0.0 }; enum libinput_pointer_axis_source source; if (!(device->seat_caps & EVDEV_DEVICE_POINTER)) return; if (device->model_flags & EVDEV_MODEL_LENOVO_SCROLLPOINT) { struct normalized_coords unaccel = { 0.0, 0.0 }; dispatch->wheel.y *= -1; normalize_delta(device, &dispatch->wheel, &unaccel); evdev_post_scroll(device, time, LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS, &unaccel); dispatch->wheel.x = 0; dispatch->wheel.y = 0; return; } if (dispatch->wheel.y != 0) { wheel_degrees.y = -1 * dispatch->wheel.y * device->scroll.wheel_click_angle.y; discrete.y = -1 * dispatch->wheel.y; source = device->scroll.is_tilt.vertical ? LIBINPUT_POINTER_AXIS_SOURCE_WHEEL_TILT: LIBINPUT_POINTER_AXIS_SOURCE_WHEEL; evdev_notify_axis( device, time, AS_MASK(LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL), source, &wheel_degrees, &discrete); dispatch->wheel.y = 0; } if (dispatch->wheel.x != 0) { wheel_degrees.x = dispatch->wheel.x * device->scroll.wheel_click_angle.x; discrete.x = dispatch->wheel.x; source = device->scroll.is_tilt.horizontal ? LIBINPUT_POINTER_AXIS_SOURCE_WHEEL_TILT: LIBINPUT_POINTER_AXIS_SOURCE_WHEEL; evdev_notify_axis( device, time, AS_MASK(LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL), source, &wheel_degrees, &discrete); dispatch->wheel.x = 0; } } static void fallback_flush_absolute_motion(struct fallback_dispatch *dispatch, struct evdev_device *device, uint64_t time) { struct libinput_device *base = &device->base; struct device_coords point; if (!(device->seat_caps & EVDEV_DEVICE_POINTER)) return; point = dispatch->abs.point; evdev_transform_absolute(device, &point); pointer_notify_motion_absolute(base, time, &point); } static bool fallback_flush_mt_down(struct fallback_dispatch *dispatch, struct evdev_device *device, int slot_idx, uint64_t time) { struct libinput_device *base = &device->base; struct libinput_seat *seat = base->seat; struct device_coords point; struct mt_slot *slot; int seat_slot; if (!(device->seat_caps & EVDEV_DEVICE_TOUCH)) return false; slot = &dispatch->mt.slots[slot_idx]; if (slot->seat_slot != -1) { evdev_log_bug_kernel(device, "driver sent multiple touch down for the same slot"); return false; } seat_slot = ffs(~seat->slot_map) - 1; slot->seat_slot = seat_slot; if (seat_slot == -1) return false; seat->slot_map |= 1 << seat_slot; point = slot->point; slot->hysteresis_center = point; evdev_transform_absolute(device, &point); touch_notify_touch_down(base, time, slot_idx, seat_slot, &point); return true; } static bool fallback_flush_mt_motion(struct fallback_dispatch *dispatch, struct evdev_device *device, int slot_idx, uint64_t time) { struct libinput_device *base = &device->base; struct device_coords point; struct mt_slot *slot; int seat_slot; if (!(device->seat_caps & EVDEV_DEVICE_TOUCH)) return false; slot = &dispatch->mt.slots[slot_idx]; seat_slot = slot->seat_slot; point = slot->point; if (seat_slot == -1) return false; if (fallback_filter_defuzz_touch(dispatch, device, slot)) return false; evdev_transform_absolute(device, &point); touch_notify_touch_motion(base, time, slot_idx, seat_slot, &point); return true; } static bool fallback_flush_mt_up(struct fallback_dispatch *dispatch, struct evdev_device *device, int slot_idx, uint64_t time) { struct libinput_device *base = &device->base; struct libinput_seat *seat = base->seat; struct mt_slot *slot; int seat_slot; if (!(device->seat_caps & EVDEV_DEVICE_TOUCH)) return false; slot = &dispatch->mt.slots[slot_idx]; seat_slot = slot->seat_slot; slot->seat_slot = -1; if (seat_slot == -1) return false; seat->slot_map &= ~(1 << seat_slot); touch_notify_touch_up(base, time, slot_idx, seat_slot); return true; } static bool fallback_flush_st_down(struct fallback_dispatch *dispatch, struct evdev_device *device, uint64_t time) { struct libinput_device *base = &device->base; struct libinput_seat *seat = base->seat; struct device_coords point; int seat_slot; if (!(device->seat_caps & EVDEV_DEVICE_TOUCH)) return false; if (dispatch->abs.seat_slot != -1) { evdev_log_bug_kernel(device, "driver sent multiple touch down for the same slot"); return false; } seat_slot = ffs(~seat->slot_map) - 1; dispatch->abs.seat_slot = seat_slot; if (seat_slot == -1) return false; seat->slot_map |= 1 << seat_slot; point = dispatch->abs.point; evdev_transform_absolute(device, &point); touch_notify_touch_down(base, time, -1, seat_slot, &point); return true; } static bool fallback_flush_st_motion(struct fallback_dispatch *dispatch, struct evdev_device *device, uint64_t time) { struct libinput_device *base = &device->base; struct device_coords point; int seat_slot; point = dispatch->abs.point; evdev_transform_absolute(device, &point); seat_slot = dispatch->abs.seat_slot; if (seat_slot == -1) return false; touch_notify_touch_motion(base, time, -1, seat_slot, &point); return true; } static bool fallback_flush_st_up(struct fallback_dispatch *dispatch, struct evdev_device *device, uint64_t time) { struct libinput_device *base = &device->base; struct libinput_seat *seat = base->seat; int seat_slot; if (!(device->seat_caps & EVDEV_DEVICE_TOUCH)) return false; seat_slot = dispatch->abs.seat_slot; dispatch->abs.seat_slot = -1; if (seat_slot == -1) return false; seat->slot_map &= ~(1 << seat_slot); touch_notify_touch_up(base, time, -1, seat_slot); return true; } static void fallback_process_touch_button(struct fallback_dispatch *dispatch, struct evdev_device *device, uint64_t time, int value) { dispatch->pending_event |= (value) ? EVDEV_ABSOLUTE_TOUCH_DOWN : EVDEV_ABSOLUTE_TOUCH_UP; } static inline void fallback_process_key(struct fallback_dispatch *dispatch, struct evdev_device *device, struct input_event *e, uint64_t time) { enum key_type type; /* ignore kernel key repeat */ if (e->value == 2) return; if (e->code == BTN_TOUCH) { if (!device->is_mt) fallback_process_touch_button(dispatch, device, time, e->value); return; } type = get_key_type(e->code); /* Ignore key release events from the kernel for keys that libinput * never got a pressed event for or key presses for keys that we * think are still down */ switch (type) { case KEY_TYPE_NONE: break; case KEY_TYPE_KEY: case KEY_TYPE_BUTTON: if ((e->value && hw_is_key_down(dispatch, e->code)) || (e->value == 0 && !hw_is_key_down(dispatch, e->code))) return; dispatch->pending_event |= EVDEV_KEY; break; } hw_set_key_down(dispatch, e->code, e->value); switch (type) { case KEY_TYPE_NONE: break; case KEY_TYPE_KEY: fallback_keyboard_notify_key( dispatch, device, time, e->code, e->value ? LIBINPUT_KEY_STATE_PRESSED : LIBINPUT_KEY_STATE_RELEASED); break; case KEY_TYPE_BUTTON: break; } } static void fallback_process_touch(struct fallback_dispatch *dispatch, struct evdev_device *device, struct input_event *e, uint64_t time) { struct mt_slot *slot = &dispatch->mt.slots[dispatch->mt.slot]; if (e->code == ABS_MT_SLOT) { if ((size_t)e->value >= dispatch->mt.slots_len) { evdev_log_bug_libinput(device, "exceeded slot count (%d of max %zd)\n", e->value, dispatch->mt.slots_len); e->value = dispatch->mt.slots_len - 1; } dispatch->mt.slot = e->value; return; } switch (e->code) { case ABS_MT_TRACKING_ID: if (e->value >= 0) { dispatch->pending_event |= EVDEV_ABSOLUTE_MT; slot->state = SLOT_STATE_BEGIN; } else { dispatch->pending_event |= EVDEV_ABSOLUTE_MT; slot->state = SLOT_STATE_END; } slot->dirty = true; break; case ABS_MT_POSITION_X: evdev_device_check_abs_axis_range(device, e->code, e->value); dispatch->mt.slots[dispatch->mt.slot].point.x = e->value; dispatch->pending_event |= EVDEV_ABSOLUTE_MT; slot->dirty = true; break; case ABS_MT_POSITION_Y: evdev_device_check_abs_axis_range(device, e->code, e->value); dispatch->mt.slots[dispatch->mt.slot].point.y = e->value; dispatch->pending_event |= EVDEV_ABSOLUTE_MT; slot->dirty = true; break; } } static inline void fallback_process_absolute_motion(struct fallback_dispatch *dispatch, struct evdev_device *device, struct input_event *e) { switch (e->code) { case ABS_X: evdev_device_check_abs_axis_range(device, e->code, e->value); dispatch->abs.point.x = e->value; dispatch->pending_event |= EVDEV_ABSOLUTE_MOTION; break; case ABS_Y: evdev_device_check_abs_axis_range(device, e->code, e->value); dispatch->abs.point.y = e->value; dispatch->pending_event |= EVDEV_ABSOLUTE_MOTION; break; } } static void fallback_lid_keyboard_event(uint64_t time, struct libinput_event *event, void *data) { struct fallback_dispatch *dispatch = fallback_dispatch(data); if (!dispatch->lid.is_closed) return; if (event->type != LIBINPUT_EVENT_KEYBOARD_KEY) return; if (dispatch->lid.reliability == RELIABILITY_WRITE_OPEN) { int fd = libevdev_get_fd(dispatch->device->evdev); int rc; struct input_event ev[2] = { {{ 0, 0 }, EV_SW, SW_LID, 0 }, {{ 0, 0 }, EV_SYN, SYN_REPORT, 0 }, }; rc = write(fd, ev, sizeof(ev)); if (rc < 0) evdev_log_error(dispatch->device, "failed to write SW_LID state (%s)", strerror(errno)); /* In case write() fails, we sync the lid state manually * regardless. */ } /* Posting the event here means we preempt the keyboard events that * caused us to wake up, so the lid event is always passed on before * the key event. */ dispatch->lid.is_closed = false; fallback_lid_notify_toggle(dispatch, dispatch->device, time); } static void fallback_lid_toggle_keyboard_listener(struct fallback_dispatch *dispatch, struct evdev_paired_keyboard *kbd, bool is_closed) { assert(kbd->device); libinput_device_remove_event_listener(&kbd->listener); if (is_closed) { libinput_device_add_event_listener( &kbd->device->base, &kbd->listener, fallback_lid_keyboard_event, dispatch); } else { libinput_device_init_event_listener(&kbd->listener); } } static void fallback_lid_toggle_keyboard_listeners(struct fallback_dispatch *dispatch, bool is_closed) { struct evdev_paired_keyboard *kbd; list_for_each(kbd, &dispatch->lid.paired_keyboard_list, link) { if (!kbd->device) continue; fallback_lid_toggle_keyboard_listener(dispatch, kbd, is_closed); } } static inline void fallback_process_switch(struct fallback_dispatch *dispatch, struct evdev_device *device, struct input_event *e, uint64_t time) { enum libinput_switch_state state; bool is_closed; /* TODO: this should to move to handle_state */ switch (e->code) { case SW_LID: is_closed = !!e->value; fallback_lid_toggle_keyboard_listeners(dispatch, is_closed); if (dispatch->lid.is_closed == is_closed) return; dispatch->lid.is_closed = is_closed; fallback_lid_notify_toggle(dispatch, device, time); break; case SW_TABLET_MODE: if (dispatch->tablet_mode.sw.state == e->value) return; dispatch->tablet_mode.sw.state = e->value; if (e->value) state = LIBINPUT_SWITCH_STATE_ON; else state = LIBINPUT_SWITCH_STATE_OFF; switch_notify_toggle(&device->base, time, LIBINPUT_SWITCH_TABLET_MODE, state); break; } } static inline bool fallback_reject_relative(struct evdev_device *device, const struct input_event *e, uint64_t time) { if ((e->code == REL_X || e->code == REL_Y) && (device->seat_caps & EVDEV_DEVICE_POINTER) == 0) { evdev_log_bug_libinput_ratelimit(device, &device->nonpointer_rel_limit, "REL_X/Y from a non-pointer device\n"); return true; } return false; } static inline void fallback_process_relative(struct fallback_dispatch *dispatch, struct evdev_device *device, struct input_event *e, uint64_t time) { if (fallback_reject_relative(device, e, time)) return; switch (e->code) { case REL_X: dispatch->rel.x += e->value; dispatch->pending_event |= EVDEV_RELATIVE_MOTION; break; case REL_Y: dispatch->rel.y += e->value; dispatch->pending_event |= EVDEV_RELATIVE_MOTION; break; case REL_WHEEL: dispatch->wheel.y += e->value; dispatch->pending_event |= EVDEV_WHEEL; break; case REL_HWHEEL: dispatch->wheel.x += e->value; dispatch->pending_event |= EVDEV_WHEEL; break; } } static inline void fallback_process_absolute(struct fallback_dispatch *dispatch, struct evdev_device *device, struct input_event *e, uint64_t time) { if (device->is_mt) { fallback_process_touch(dispatch, device, e, time); } else { fallback_process_absolute_motion(dispatch, device, e); } } static inline bool fallback_any_button_down(struct fallback_dispatch *dispatch, struct evdev_device *device) { unsigned int button; for (button = BTN_LEFT; button < BTN_JOYSTICK; button++) { if (libevdev_has_event_code(device->evdev, EV_KEY, button) && hw_is_key_down(dispatch, button)) return true; } return false; } static void fallback_handle_state(struct fallback_dispatch *dispatch, struct evdev_device *device, uint64_t time) { bool need_touch_frame = false; /* Relative motion */ if (dispatch->pending_event & EVDEV_RELATIVE_MOTION) fallback_flush_relative_motion(dispatch, device, time); /* Single touch or absolute pointer devices */ if (dispatch->pending_event & EVDEV_ABSOLUTE_TOUCH_DOWN) { if (fallback_flush_st_down(dispatch, device, time)) need_touch_frame = true; } if (dispatch->pending_event & EVDEV_ABSOLUTE_MOTION) { if (device->seat_caps & EVDEV_DEVICE_TOUCH) { if (fallback_flush_st_motion(dispatch, device, time)) need_touch_frame = true; } else if (device->seat_caps & EVDEV_DEVICE_POINTER) { fallback_flush_absolute_motion(dispatch, device, time); } } if (dispatch->pending_event & EVDEV_ABSOLUTE_TOUCH_UP) { if (fallback_flush_st_up(dispatch, device, time)) need_touch_frame = true; } /* Multitouch devices */ if (dispatch->pending_event & EVDEV_ABSOLUTE_MT) { bool sent = false; for (size_t i = 0; i < dispatch->mt.slots_len; i++) { struct mt_slot *slot = &dispatch->mt.slots[i]; if (!slot->dirty) continue; if (slot->state == SLOT_STATE_BEGIN) { sent = fallback_flush_mt_down(dispatch, device, i, time); slot->state = SLOT_STATE_UPDATE; } else if (slot->state == SLOT_STATE_UPDATE) { sent = fallback_flush_mt_motion(dispatch, device, i, time); } else if (slot->state == SLOT_STATE_END) { sent = fallback_flush_mt_up(dispatch, device, i, time); slot->state = SLOT_STATE_NONE; } slot->dirty = false; } need_touch_frame = sent; } if (need_touch_frame) touch_notify_frame(&device->base, time); fallback_flush_wheels(dispatch, device, time); /* Buttons and keys */ if (dispatch->pending_event & EVDEV_KEY) { bool want_debounce = false; for (unsigned int code = 0; code <= KEY_MAX; code++) { if (!hw_key_has_changed(dispatch, code)) continue; if (get_key_type(code) == KEY_TYPE_BUTTON) { want_debounce = true; break; } } if (want_debounce) fallback_debounce_handle_state(dispatch, time); hw_key_update_last_state(dispatch); } dispatch->pending_event = EVDEV_NONE; } static void fallback_interface_process(struct evdev_dispatch *evdev_dispatch, struct evdev_device *device, struct input_event *event, uint64_t time) { struct fallback_dispatch *dispatch = fallback_dispatch(evdev_dispatch); if (dispatch->ignore_events) return; switch (event->type) { case EV_REL: fallback_process_relative(dispatch, device, event, time); break; case EV_ABS: fallback_process_absolute(dispatch, device, event, time); break; case EV_KEY: fallback_process_key(dispatch, device, event, time); break; case EV_SW: fallback_process_switch(dispatch, device, event, time); break; case EV_SYN: fallback_handle_state(dispatch, device, time); break; } } static void release_touches(struct fallback_dispatch *dispatch, struct evdev_device *device, uint64_t time) { unsigned int idx; bool need_frame = false; need_frame = fallback_flush_st_up(dispatch, device, time); for (idx = 0; idx < dispatch->mt.slots_len; idx++) { struct mt_slot *slot = &dispatch->mt.slots[idx]; if (slot->seat_slot == -1) continue; if (fallback_flush_mt_up(dispatch, device, idx, time)) need_frame = true; } if (need_frame) touch_notify_frame(&device->base, time); } static void release_pressed_keys(struct fallback_dispatch *dispatch, struct evdev_device *device, uint64_t time) { int code; for (code = 0; code < KEY_CNT; code++) { int count = get_key_down_count(device, code); if (count == 0) continue; if (count > 1) { evdev_log_bug_libinput(device, "key %d is down %d times.\n", code, count); } switch (get_key_type(code)) { case KEY_TYPE_NONE: break; case KEY_TYPE_KEY: fallback_keyboard_notify_key( dispatch, device, time, code, LIBINPUT_KEY_STATE_RELEASED); break; case KEY_TYPE_BUTTON: evdev_pointer_notify_physical_button( device, time, evdev_to_left_handed(device, code), LIBINPUT_BUTTON_STATE_RELEASED); break; } count = get_key_down_count(device, code); if (count != 0) { evdev_log_bug_libinput(device, "releasing key %d failed.\n", code); break; } } } static void fallback_return_to_neutral_state(struct fallback_dispatch *dispatch, struct evdev_device *device) { struct libinput *libinput = evdev_libinput_context(device); uint64_t time; if ((time = libinput_now(libinput)) == 0) return; release_touches(dispatch, device, time); release_pressed_keys(dispatch, device, time); memset(dispatch->hw_key_mask, 0, sizeof(dispatch->hw_key_mask)); memset(dispatch->hw_key_mask, 0, sizeof(dispatch->last_hw_key_mask)); } static void fallback_interface_suspend(struct evdev_dispatch *evdev_dispatch, struct evdev_device *device) { struct fallback_dispatch *dispatch = fallback_dispatch(evdev_dispatch); fallback_return_to_neutral_state(dispatch, device); } static void fallback_interface_remove(struct evdev_dispatch *evdev_dispatch) { struct fallback_dispatch *dispatch = fallback_dispatch(evdev_dispatch); struct evdev_paired_keyboard *kbd, *tmp; libinput_device_remove_event_listener(&dispatch->tablet_mode.other.listener); list_for_each_safe(kbd, tmp, &dispatch->lid.paired_keyboard_list, link) { evdev_paired_keyboard_destroy(kbd); } } static void fallback_interface_sync_initial_state(struct evdev_device *device, struct evdev_dispatch *evdev_dispatch) { struct fallback_dispatch *dispatch = fallback_dispatch(evdev_dispatch); uint64_t time = libinput_now(evdev_libinput_context(device)); if (device->tags & EVDEV_TAG_LID_SWITCH) { struct libevdev *evdev = device->evdev; dispatch->lid.is_closed = libevdev_get_event_value(evdev, EV_SW, SW_LID); dispatch->lid.is_closed_client_state = false; /* For the initial state sync, we depend on whether the lid switch * is reliable. If we know it's reliable, we sync as expected. * If we're not sure, we ignore the initial state and only sync on * the first future lid close event. Laptops with a broken switch * that always have the switch in 'on' state thus don't mess up our * touchpad. */ if (dispatch->lid.is_closed && dispatch->lid.reliability == RELIABILITY_RELIABLE) { fallback_lid_notify_toggle(dispatch, device, time); } } if (dispatch->tablet_mode.sw.state) { switch_notify_toggle(&device->base, time, LIBINPUT_SWITCH_TABLET_MODE, LIBINPUT_SWITCH_STATE_ON); } } static void fallback_interface_toggle_touch(struct evdev_dispatch *evdev_dispatch, struct evdev_device *device, bool enable, uint64_t time) { struct fallback_dispatch *dispatch = fallback_dispatch(evdev_dispatch); bool ignore_events = !enable; if (ignore_events == dispatch->ignore_events) return; if (ignore_events) fallback_return_to_neutral_state(dispatch, device); dispatch->ignore_events = ignore_events; } static void fallback_interface_destroy(struct evdev_dispatch *evdev_dispatch) { struct fallback_dispatch *dispatch = fallback_dispatch(evdev_dispatch); libinput_timer_cancel(&dispatch->debounce.timer); libinput_timer_destroy(&dispatch->debounce.timer); libinput_timer_cancel(&dispatch->debounce.timer_short); libinput_timer_destroy(&dispatch->debounce.timer_short); free(dispatch->mt.slots); free(dispatch); } static void fallback_lid_pair_keyboard(struct evdev_device *lid_switch, struct evdev_device *keyboard) { struct fallback_dispatch *dispatch = fallback_dispatch(lid_switch->dispatch); struct evdev_paired_keyboard *kbd; size_t count = 0; if ((keyboard->tags & EVDEV_TAG_KEYBOARD) == 0 || (lid_switch->tags & EVDEV_TAG_LID_SWITCH) == 0) return; if ((keyboard->tags & EVDEV_TAG_INTERNAL_KEYBOARD) == 0) return; list_for_each(kbd, &dispatch->lid.paired_keyboard_list, link) { count++; if (count > 3) { evdev_log_info(lid_switch, "lid: too many internal keyboards\n"); break; } } kbd = zalloc(sizeof(*kbd)); kbd->device = keyboard; libinput_device_init_event_listener(&kbd->listener); list_insert(&dispatch->lid.paired_keyboard_list, &kbd->link); evdev_log_debug(lid_switch, "lid: keyboard paired with %s<->%s\n", lid_switch->devname, keyboard->devname); /* We need to init the event listener now only if the * reported state is closed. */ if (dispatch->lid.is_closed) fallback_lid_toggle_keyboard_listener(dispatch, kbd, dispatch->lid.is_closed); } static void fallback_resume(struct fallback_dispatch *dispatch, struct evdev_device *device) { if (dispatch->base.sendevents.current_mode == LIBINPUT_CONFIG_SEND_EVENTS_DISABLED) return; evdev_device_resume(device); } static void fallback_suspend(struct fallback_dispatch *dispatch, struct evdev_device *device) { evdev_device_suspend(device); } static void fallback_tablet_mode_switch_event(uint64_t time, struct libinput_event *event, void *data) { struct fallback_dispatch *dispatch = data; struct evdev_device *device = dispatch->device; struct libinput_event_switch *swev; if (libinput_event_get_type(event) != LIBINPUT_EVENT_SWITCH_TOGGLE) return; swev = libinput_event_get_switch_event(event); if (libinput_event_switch_get_switch(swev) != LIBINPUT_SWITCH_TABLET_MODE) return; switch (libinput_event_switch_get_switch_state(swev)) { case LIBINPUT_SWITCH_STATE_OFF: fallback_resume(dispatch, device); evdev_log_debug(device, "tablet-mode: resuming device\n"); break; case LIBINPUT_SWITCH_STATE_ON: fallback_suspend(dispatch, device); evdev_log_debug(device, "tablet-mode: suspending device\n"); break; } } static void fallback_keyboard_pair_tablet_mode(struct evdev_device *keyboard, struct evdev_device *tablet_mode_switch) { struct fallback_dispatch *dispatch = fallback_dispatch(keyboard->dispatch); if ((keyboard->tags & (EVDEV_TAG_TRACKPOINT|EVDEV_TAG_INTERNAL_KEYBOARD)) == 0) return; if (keyboard->model_flags & EVDEV_MODEL_TABLET_MODE_NO_SUSPEND) return; if ((tablet_mode_switch->tags & EVDEV_TAG_TABLET_MODE_SWITCH) == 0) return; if (dispatch->tablet_mode.other.sw_device) return; evdev_log_debug(keyboard, "tablet_mode_switch: activated for %s<->%s\n", keyboard->devname, tablet_mode_switch->devname); libinput_device_add_event_listener(&tablet_mode_switch->base, &dispatch->tablet_mode.other.listener, fallback_tablet_mode_switch_event, dispatch); dispatch->tablet_mode.other.sw_device = tablet_mode_switch; if (evdev_device_switch_get_state(tablet_mode_switch, LIBINPUT_SWITCH_TABLET_MODE) == LIBINPUT_SWITCH_STATE_ON) fallback_suspend(dispatch, keyboard); } static void fallback_interface_device_added(struct evdev_device *device, struct evdev_device *added_device) { fallback_lid_pair_keyboard(device, added_device); fallback_keyboard_pair_tablet_mode(device, added_device); } static void fallback_interface_device_removed(struct evdev_device *device, struct evdev_device *removed_device) { struct fallback_dispatch *dispatch = fallback_dispatch(device->dispatch); struct evdev_paired_keyboard *kbd, *tmp; list_for_each_safe(kbd, tmp, &dispatch->lid.paired_keyboard_list, link) { if (!kbd->device) continue; if (kbd->device != removed_device) continue; evdev_paired_keyboard_destroy(kbd); } if (removed_device == dispatch->tablet_mode.other.sw_device) { libinput_device_remove_event_listener( &dispatch->tablet_mode.other.listener); libinput_device_init_event_listener( &dispatch->tablet_mode.other.listener); dispatch->tablet_mode.other.sw_device = NULL; } } struct evdev_dispatch_interface fallback_interface = { .process = fallback_interface_process, .suspend = fallback_interface_suspend, .remove = fallback_interface_remove, .destroy = fallback_interface_destroy, .device_added = fallback_interface_device_added, .device_removed = fallback_interface_device_removed, .device_suspended = fallback_interface_device_removed, /* treat as remove */ .device_resumed = fallback_interface_device_added, /* treat as add */ .post_added = fallback_interface_sync_initial_state, .toggle_touch = fallback_interface_toggle_touch, .get_switch_state = fallback_interface_get_switch_state, }; static void fallback_change_to_left_handed(struct evdev_device *device) { struct fallback_dispatch *dispatch = fallback_dispatch(device->dispatch); if (device->left_handed.want_enabled == device->left_handed.enabled) return; if (fallback_any_button_down(dispatch, device)) return; device->left_handed.enabled = device->left_handed.want_enabled; } static void fallback_change_scroll_method(struct evdev_device *device) { struct fallback_dispatch *dispatch = fallback_dispatch(device->dispatch); if (device->scroll.want_method == device->scroll.method && device->scroll.want_button == device->scroll.button) return; if (fallback_any_button_down(dispatch, device)) return; device->scroll.method = device->scroll.want_method; device->scroll.button = device->scroll.want_button; } static int fallback_rotation_config_is_available(struct libinput_device *device) { /* This function only gets called when we support rotation */ return 1; } static enum libinput_config_status fallback_rotation_config_set_angle(struct libinput_device *libinput_device, unsigned int degrees_cw) { struct evdev_device *device = evdev_device(libinput_device); struct fallback_dispatch *dispatch = fallback_dispatch(device->dispatch); dispatch->rotation.angle = degrees_cw; matrix_init_rotate(&dispatch->rotation.matrix, degrees_cw); return LIBINPUT_CONFIG_STATUS_SUCCESS; } static unsigned int fallback_rotation_config_get_angle(struct libinput_device *libinput_device) { struct evdev_device *device = evdev_device(libinput_device); struct fallback_dispatch *dispatch = fallback_dispatch(device->dispatch); return dispatch->rotation.angle; } static unsigned int fallback_rotation_config_get_default_angle(struct libinput_device *device) { return 0; } static void fallback_init_rotation(struct fallback_dispatch *dispatch, struct evdev_device *device) { if ((device->model_flags & EVDEV_MODEL_TRACKBALL) == 0) return; dispatch->rotation.config.is_available = fallback_rotation_config_is_available; dispatch->rotation.config.set_angle = fallback_rotation_config_set_angle; dispatch->rotation.config.get_angle = fallback_rotation_config_get_angle; dispatch->rotation.config.get_default_angle = fallback_rotation_config_get_default_angle; dispatch->rotation.is_enabled = false; matrix_init_identity(&dispatch->rotation.matrix); device->base.config.rotation = &dispatch->rotation.config; } static inline int fallback_dispatch_init_slots(struct fallback_dispatch *dispatch, struct evdev_device *device) { struct libevdev *evdev = device->evdev; struct mt_slot *slots; int num_slots; int active_slot; int slot; 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 0; /* We only handle the slotted Protocol B in libinput. Devices with ABS_MT_POSITION_* but not ABS_MT_SLOT require mtdev for conversion. */ if (evdev_need_mtdev(device)) { device->mtdev = mtdev_new_open(device->fd); if (!device->mtdev) return -1; /* pick 10 slots as default for type A devices. */ num_slots = 10; active_slot = device->mtdev->caps.slot.value; } else { num_slots = libevdev_get_num_slots(device->evdev); active_slot = libevdev_get_current_slot(evdev); } slots = zalloc(num_slots * sizeof(struct mt_slot)); for (slot = 0; slot < num_slots; ++slot) { slots[slot].seat_slot = -1; if (evdev_need_mtdev(device)) continue; slots[slot].point.x = libevdev_get_slot_value(evdev, slot, ABS_MT_POSITION_X); slots[slot].point.y = libevdev_get_slot_value(evdev, slot, ABS_MT_POSITION_Y); } dispatch->mt.slots = slots; dispatch->mt.slots_len = num_slots; dispatch->mt.slot = active_slot; if (device->abs.absinfo_x->fuzz || device->abs.absinfo_y->fuzz) { dispatch->mt.want_hysteresis = true; dispatch->mt.hysteresis_margin.x = device->abs.absinfo_x->fuzz/2; dispatch->mt.hysteresis_margin.y = device->abs.absinfo_y->fuzz/2; } return 0; } static inline void fallback_dispatch_init_rel(struct fallback_dispatch *dispatch, struct evdev_device *device) { dispatch->rel.x = 0; dispatch->rel.y = 0; } static inline void fallback_dispatch_init_abs(struct fallback_dispatch *dispatch, struct evdev_device *device) { if (!libevdev_has_event_code(device->evdev, EV_ABS, ABS_X)) return; dispatch->abs.point.x = device->abs.absinfo_x->value; dispatch->abs.point.y = device->abs.absinfo_y->value; dispatch->abs.seat_slot = -1; evdev_device_init_abs_range_warnings(device); } static inline void fallback_dispatch_init_switch(struct fallback_dispatch *dispatch, struct evdev_device *device) { int val; list_init(&dispatch->lid.paired_keyboard_list); if (device->tags & EVDEV_TAG_LID_SWITCH) { dispatch->lid.reliability = evdev_read_switch_reliability_prop(device); dispatch->lid.is_closed = false; } if (device->tags & EVDEV_TAG_TABLET_MODE_SWITCH) { val = libevdev_get_event_value(device->evdev, EV_SW, SW_TABLET_MODE); dispatch->tablet_mode.sw.state = val; } libinput_device_init_event_listener(&dispatch->tablet_mode.other.listener); } struct evdev_dispatch * fallback_dispatch_create(struct libinput_device *libinput_device) { struct evdev_device *device = evdev_device(libinput_device); struct fallback_dispatch *dispatch; dispatch = zalloc(sizeof *dispatch); dispatch->device = evdev_device(libinput_device); dispatch->base.dispatch_type = DISPATCH_FALLBACK; dispatch->base.interface = &fallback_interface; dispatch->pending_event = EVDEV_NONE; list_init(&dispatch->lid.paired_keyboard_list); fallback_dispatch_init_rel(dispatch, device); fallback_dispatch_init_abs(dispatch, device); if (fallback_dispatch_init_slots(dispatch, device) == -1) { free(dispatch); return NULL; } fallback_dispatch_init_switch(dispatch, device); if (device->left_handed.want_enabled) evdev_init_left_handed(device, fallback_change_to_left_handed); if (device->scroll.want_button) evdev_init_button_scroll(device, fallback_change_scroll_method); if (device->scroll.natural_scrolling_enabled) evdev_init_natural_scroll(device); evdev_init_calibration(device, &dispatch->calibration); evdev_init_sendevents(device, &dispatch->base); fallback_init_rotation(dispatch, device); /* BTN_MIDDLE is set on mice even when it's not present. So * we can only use the absence of BTN_MIDDLE to mean something, i.e. * we enable it by default on anything that only has L&R. * If we have L&R and no middle, we don't expose it as config * option */ if (libevdev_has_event_code(device->evdev, EV_KEY, BTN_LEFT) && libevdev_has_event_code(device->evdev, EV_KEY, BTN_RIGHT)) { bool has_middle = libevdev_has_event_code(device->evdev, EV_KEY, BTN_MIDDLE); bool want_config = has_middle; bool enable_by_default = !has_middle; evdev_init_middlebutton(device, enable_by_default, want_config); } fallback_init_debounce(dispatch); return &dispatch->base; }