mirror of
https://gitlab.freedesktop.org/libinput/libinput.git
synced 2025-12-20 13:50:15 +01:00
Adds a dedicated scroll movement type to the custom acceleration profile. Supported by physical mouse and touchpad. Other profiles remain the same by using the same unaccelerated filter for the scroll filter. Signed-off-by: Yinon Burgansky <51504-Yinon@users.noreply.gitlab.freedesktop.org>
1700 lines
44 KiB
C
1700 lines
44 KiB
C
/*
|
|
* Copyright © 2010 Intel Corporation
|
|
* Copyright © 2013 Jonas Ådahl
|
|
* Copyright © 2013-2017 Red Hat, Inc.
|
|
* Copyright © 2017 James Ye <jye836@gmail.com>
|
|
*
|
|
* 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 <mtdev-plumbing.h>
|
|
|
|
#include "evdev-fallback.h"
|
|
#include "util-input-event.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;
|
|
}
|
|
}
|
|
|
|
void
|
|
fallback_notify_physical_button(struct fallback_dispatch *dispatch,
|
|
struct evdev_device *device,
|
|
uint64_t time,
|
|
int button,
|
|
enum libinput_button_state state)
|
|
{
|
|
evdev_pointer_notify_physical_button(device, time, button, state);
|
|
}
|
|
|
|
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 bool
|
|
post_button_scroll(struct evdev_device *device,
|
|
struct device_float_coords raw,
|
|
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 we 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:
|
|
{
|
|
const struct normalized_coords normalized =
|
|
filter_dispatch_scroll(device->pointer.filter,
|
|
&raw,
|
|
device,
|
|
time);
|
|
evdev_post_scroll(device, time,
|
|
LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS,
|
|
&normalized);
|
|
}
|
|
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 struct device_float_coords
|
|
fallback_rotate_relative(struct fallback_dispatch *dispatch,
|
|
struct evdev_device *device)
|
|
{
|
|
struct device_float_coords rel = { dispatch->rel.x, dispatch->rel.y };
|
|
|
|
if (!device->base.config.rotation)
|
|
return rel;
|
|
|
|
matrix_mult_vec_double(&dispatch->rotation.matrix, &rel.x, &rel.y);
|
|
|
|
return 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;
|
|
|
|
if (!(device->seat_caps & EVDEV_DEVICE_POINTER))
|
|
return;
|
|
|
|
struct device_float_coords raw = fallback_rotate_relative(dispatch, device);
|
|
|
|
dispatch->rel.x = 0;
|
|
dispatch->rel.y = 0;
|
|
|
|
/* Use unaccelerated deltas for pointing stick scroll */
|
|
if (post_button_scroll(device, raw, 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.x = accel.y = 0;
|
|
}
|
|
|
|
if (normalized_is_zero(accel))
|
|
return;
|
|
|
|
pointer_notify_motion(base, time, &accel, &raw);
|
|
}
|
|
|
|
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 |= bit(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 &= ~bit(seat_slot);
|
|
|
|
touch_notify_touch_up(base, time, slot_idx, seat_slot);
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
fallback_flush_mt_cancel(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 &= ~bit(seat_slot);
|
|
|
|
touch_notify_touch_cancel(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 |= bit(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 &= ~bit(seat_slot);
|
|
|
|
touch_notify_touch_up(base, time, -1, seat_slot);
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
fallback_flush_st_cancel(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 &= ~bit(seat_slot);
|
|
|
|
touch_notify_touch_cancel(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;
|
|
if (dispatch->mt.has_palm) {
|
|
int v;
|
|
v = libevdev_get_slot_value(device->evdev,
|
|
dispatch->mt.slot,
|
|
ABS_MT_TOOL_TYPE);
|
|
switch (v) {
|
|
case MT_TOOL_PALM:
|
|
/* new touch, no cancel needed */
|
|
slot->palm_state = PALM_WAS_PALM;
|
|
break;
|
|
default:
|
|
slot->palm_state = PALM_NONE;
|
|
break;
|
|
}
|
|
} else {
|
|
slot->palm_state = PALM_NONE;
|
|
}
|
|
} 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;
|
|
case ABS_MT_TOOL_TYPE:
|
|
/* The transitions matter - we (may) need to send a touch
|
|
* cancel event if we just switched to a palm touch. And the
|
|
* kernel may switch back to finger but we keep the touch as
|
|
* palm - but then we need to reset correctly on a new touch
|
|
* sequence.
|
|
*/
|
|
switch (e->value) {
|
|
case MT_TOOL_PALM:
|
|
if (slot->palm_state == PALM_NONE)
|
|
slot->palm_state = PALM_NEW;
|
|
break;
|
|
default:
|
|
if (slot->palm_state == PALM_IS_PALM)
|
|
slot->palm_state = PALM_WAS_PALM;
|
|
break;
|
|
}
|
|
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];
|
|
|
|
ev[0] = input_event_init(0, EV_SW, SW_LID, 0);
|
|
ev[1] = input_event_init(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;
|
|
}
|
|
|
|
fallback_wheel_process_relative(dispatch, device, e, time);
|
|
}
|
|
|
|
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 inline bool
|
|
fallback_arbitrate_touch(struct fallback_dispatch *dispatch,
|
|
struct mt_slot *slot)
|
|
{
|
|
bool discard = false;
|
|
struct device_coords point = slot->point;
|
|
evdev_transform_absolute(dispatch->device, &point);
|
|
|
|
if (dispatch->arbitration.state == ARBITRATION_IGNORE_RECT &&
|
|
point_in_rect(&point, &dispatch->arbitration.rect)) {
|
|
slot->palm_state = PALM_IS_PALM;
|
|
discard = true;
|
|
}
|
|
|
|
return discard;
|
|
}
|
|
|
|
static inline bool
|
|
fallback_flush_mt_events(struct fallback_dispatch *dispatch,
|
|
struct evdev_device *device,
|
|
uint64_t time)
|
|
{
|
|
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;
|
|
|
|
slot->dirty = false;
|
|
|
|
/* Any palm state other than PALM_NEW means we've either
|
|
* already cancelled the touch or the touch was never
|
|
* a finger anyway and we didn't send the begin.
|
|
*/
|
|
if (slot->palm_state == PALM_NEW) {
|
|
if (slot->state != SLOT_STATE_BEGIN)
|
|
sent = fallback_flush_mt_cancel(dispatch,
|
|
device,
|
|
i,
|
|
time);
|
|
slot->palm_state = PALM_IS_PALM;
|
|
} else if (slot->palm_state == PALM_NONE) {
|
|
switch (slot->state) {
|
|
case SLOT_STATE_BEGIN:
|
|
if (!fallback_arbitrate_touch(dispatch,
|
|
slot)) {
|
|
sent = fallback_flush_mt_down(dispatch,
|
|
device,
|
|
i,
|
|
time);
|
|
}
|
|
break;
|
|
case SLOT_STATE_UPDATE:
|
|
sent = fallback_flush_mt_motion(dispatch,
|
|
device,
|
|
i,
|
|
time);
|
|
break;
|
|
case SLOT_STATE_END:
|
|
sent = fallback_flush_mt_up(dispatch,
|
|
device,
|
|
i,
|
|
time);
|
|
break;
|
|
case SLOT_STATE_NONE:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* State machine continues independent of the palm state */
|
|
switch (slot->state) {
|
|
case SLOT_STATE_BEGIN:
|
|
slot->state = SLOT_STATE_UPDATE;
|
|
break;
|
|
case SLOT_STATE_UPDATE:
|
|
break;
|
|
case SLOT_STATE_END:
|
|
slot->state = SLOT_STATE_NONE;
|
|
break;
|
|
case SLOT_STATE_NONE:
|
|
/* touch arbitration may swallow the begin,
|
|
* so we may get updates for a touch still
|
|
* in NONE state */
|
|
break;
|
|
}
|
|
}
|
|
|
|
return sent;
|
|
}
|
|
|
|
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;
|
|
} else 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)
|
|
need_touch_frame = fallback_flush_mt_events(dispatch,
|
|
device,
|
|
time);
|
|
|
|
if (need_touch_frame)
|
|
touch_notify_frame(&device->base, time);
|
|
|
|
fallback_wheel_handle_state(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->arbitration.in_arbitration)
|
|
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
|
|
cancel_touches(struct fallback_dispatch *dispatch,
|
|
struct evdev_device *device,
|
|
const struct device_coord_rect *rect,
|
|
uint64_t time)
|
|
{
|
|
unsigned int idx;
|
|
bool need_frame = false;
|
|
struct device_coords point;
|
|
|
|
point = dispatch->abs.point;
|
|
evdev_transform_absolute(device, &point);
|
|
if (!rect || point_in_rect(&point, rect))
|
|
need_frame = fallback_flush_st_cancel(dispatch,
|
|
device,
|
|
time);
|
|
|
|
for (idx = 0; idx < dispatch->mt.slots_len; idx++) {
|
|
struct mt_slot *slot = &dispatch->mt.slots[idx];
|
|
point = slot->point;
|
|
evdev_transform_absolute(device, &point);
|
|
|
|
if (slot->seat_slot == -1)
|
|
continue;
|
|
|
|
if ((!rect || point_in_rect(&point, rect)) &&
|
|
fallback_flush_mt_cancel(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_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;
|
|
|
|
cancel_touches(dispatch, device, NULL, 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;
|
|
|
|
libinput_timer_cancel(&dispatch->wheel.scroll_timer);
|
|
libinput_timer_cancel(&dispatch->debounce.timer);
|
|
libinput_timer_cancel(&dispatch->debounce.timer_short);
|
|
libinput_timer_cancel(&dispatch->arbitration.arbitration_timer);
|
|
|
|
libinput_device_remove_event_listener(&dispatch->tablet_mode.other.listener);
|
|
|
|
list_for_each_safe(kbd,
|
|
&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_update_rect(struct evdev_dispatch *evdev_dispatch,
|
|
struct evdev_device *device,
|
|
const struct phys_rect *phys_rect,
|
|
uint64_t time)
|
|
{
|
|
struct fallback_dispatch *dispatch = fallback_dispatch(evdev_dispatch);
|
|
struct device_coord_rect rect;
|
|
|
|
assert(phys_rect);
|
|
|
|
/* Existing touches do not change, we just update the rect and only
|
|
* new touches in these areas will be ignored. If you want to paint
|
|
* over your finger, be my guest. */
|
|
rect = evdev_phys_rect_to_units(device, phys_rect);
|
|
dispatch->arbitration.rect = rect;
|
|
}
|
|
|
|
static void
|
|
fallback_interface_toggle_touch(struct evdev_dispatch *evdev_dispatch,
|
|
struct evdev_device *device,
|
|
enum evdev_arbitration_state which,
|
|
const struct phys_rect *phys_rect,
|
|
uint64_t time)
|
|
{
|
|
struct fallback_dispatch *dispatch = fallback_dispatch(evdev_dispatch);
|
|
struct device_coord_rect rect = {0};
|
|
|
|
if (which == dispatch->arbitration.state)
|
|
return;
|
|
|
|
switch (which) {
|
|
case ARBITRATION_NOT_ACTIVE:
|
|
/* if in-kernel arbitration is in use and there is a touch
|
|
* and a pen in proximity, lifting the pen out of proximity
|
|
* causes a touch begin for the touch. On a hand-lift the
|
|
* proximity out precedes the touch up by a few ms, so we
|
|
* get what looks like a tap. Fix this by delaying
|
|
* arbitration by just a little bit so that any touch in
|
|
* event is caught as palm touch. */
|
|
libinput_timer_set(&dispatch->arbitration.arbitration_timer,
|
|
time + ms2us(90));
|
|
break;
|
|
case ARBITRATION_IGNORE_RECT:
|
|
assert(phys_rect);
|
|
rect = evdev_phys_rect_to_units(device, phys_rect);
|
|
cancel_touches(dispatch, device, &rect, time);
|
|
dispatch->arbitration.rect = rect;
|
|
break;
|
|
case ARBITRATION_IGNORE_ALL:
|
|
libinput_timer_cancel(&dispatch->arbitration.arbitration_timer);
|
|
fallback_return_to_neutral_state(dispatch, device);
|
|
dispatch->arbitration.in_arbitration = true;
|
|
break;
|
|
}
|
|
|
|
dispatch->arbitration.state = which;
|
|
}
|
|
|
|
static void
|
|
fallback_interface_destroy(struct evdev_dispatch *evdev_dispatch)
|
|
{
|
|
struct fallback_dispatch *dispatch = fallback_dispatch(evdev_dispatch);
|
|
|
|
libinput_timer_destroy(&dispatch->wheel.scroll_timer);
|
|
libinput_timer_destroy(&dispatch->arbitration.arbitration_timer);
|
|
libinput_timer_destroy(&dispatch->debounce.timer);
|
|
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_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_EXTERNAL_KEYBOARD))
|
|
return;
|
|
|
|
if ((keyboard->tags & EVDEV_TAG_TRACKPOINT)) {
|
|
if (keyboard->tags & EVDEV_TAG_EXTERNAL_MOUSE)
|
|
return;
|
|
/* This filters out all internal keyboard-like devices (Video
|
|
* Switch) */
|
|
} else if ((keyboard->tags & EVDEV_TAG_INTERNAL_KEYBOARD) == 0) {
|
|
return;
|
|
}
|
|
|
|
if (evdev_device_has_model_quirk(keyboard,
|
|
QUIRK_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: paired %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) {
|
|
evdev_log_debug(keyboard, "tablet-mode: suspending device\n");
|
|
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_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;
|
|
|
|
list_for_each_safe(kbd,
|
|
&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,
|
|
.touch_arbitration_toggle = fallback_interface_toggle_touch,
|
|
.touch_arbitration_update_rect = fallback_interface_update_rect,
|
|
.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 &&
|
|
device->scroll.want_lock_enabled == device->scroll.lock_enabled)
|
|
return;
|
|
|
|
if (fallback_any_button_down(dispatch, device))
|
|
return;
|
|
|
|
device->scroll.method = device->scroll.want_method;
|
|
device->scroll.button = device->scroll.want_button;
|
|
device->scroll.lock_enabled = device->scroll.want_lock_enabled;
|
|
evdev_set_button_scroll_lock_enabled(device, device->scroll.lock_enabled);
|
|
}
|
|
|
|
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->tags & EVDEV_TAG_TRACKPOINT)
|
|
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;
|
|
dispatch->mt.has_palm = libevdev_has_event_code(evdev,
|
|
EV_ABS,
|
|
ABS_MT_TOOL_TYPE);
|
|
|
|
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);
|
|
}
|
|
|
|
static void
|
|
fallback_arbitration_timeout(uint64_t now, void *data)
|
|
{
|
|
struct fallback_dispatch *dispatch = data;
|
|
|
|
if (dispatch->arbitration.in_arbitration)
|
|
dispatch->arbitration.in_arbitration = false;
|
|
}
|
|
|
|
static void
|
|
fallback_init_arbitration(struct fallback_dispatch *dispatch,
|
|
struct evdev_device *device)
|
|
{
|
|
char timer_name[64];
|
|
|
|
snprintf(timer_name,
|
|
sizeof(timer_name),
|
|
"%s arbitration",
|
|
evdev_device_get_sysname(device));
|
|
libinput_timer_init(&dispatch->arbitration.arbitration_timer,
|
|
evdev_libinput_context(device),
|
|
timer_name,
|
|
fallback_arbitration_timeout,
|
|
dispatch);
|
|
dispatch->arbitration.in_arbitration = false;
|
|
}
|
|
|
|
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_wheel(dispatch, device);
|
|
fallback_init_debounce(dispatch);
|
|
fallback_init_arbitration(dispatch, device);
|
|
|
|
return &dispatch->base;
|
|
}
|