diff --git a/doc/Makefile.am b/doc/Makefile.am index f66b47f1..6e6f72d3 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -1,4 +1,7 @@ -EXTRA_DIST = touchpad-tap-state-machine.svg touchpad-softbutton-state-machine.svg +EXTRA_DIST = \ + middle-button-emulation.svg \ + touchpad-tap-state-machine.svg \ + touchpad-softbutton-state-machine.svg if BUILD_DOCS diff --git a/doc/middle-button-emulation.svg b/doc/middle-button-emulation.svg new file mode 100644 index 00000000..338af386 --- /dev/null +++ b/doc/middle-button-emulation.svg @@ -0,0 +1,1315 @@ + + + + + + + + + +
+
+ IDLE
+
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ left button
+ press
+
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ right button
+ press
+
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ other button
+ press
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + + + +
+
+ LEFT_DOWN
+
+
+ + [Not supported by viewer] +
+
+ + + + + + +
+
+ RIGHT_DOWN
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ PASSTHROUGH
+
+
+ + [Not supported by viewer] +
+
+ + + + + + +
+
+ left button
+ release
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + +
+
+ right button
+ press
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + +
+
+ timeout
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ BTN_LEFT
+ PRESS
+
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ BTN_LEFT
+ RELEASE
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ BTN_LEFT
+ PRESS
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + +
+
+ MIDDLE
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ BTN_MIDDLE
+ PRESS
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ left button
+ release
+
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ right button
+ release
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ BTN_MIDDLE
+ RELEASE
+
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ BTN_MIDDLE
+ RELEASE
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ LUP_PENDING
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ RUP_PENDING
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ left button
+ release
+
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ right button
+ release
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ IDLE
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ right button
+ press
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ left button
+ press
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + + + +
+
+ other button
+ press
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + +
+
+ left button
+ press
+
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ right button
+ release
+
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ timeout
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ other button
+ press
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + +
+
+ BTN_RIGHT
+ PRESS
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + + + + + +
+
+ BTN_RIGHT
+ PRESS
+
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ BTN_RIGHT
+ RELEASE
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + + + + + + + +
+
+ other button
+ press
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + +
+
+ BTN_MIDDLE
+ RELEASE
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ IGNORE_LR
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ left button
+ release
+
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ right button
+ release
+
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ other button
+ event
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + + + +
+
+ PASSTHROUGH
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ IGNORE_R
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ IGNORE_L
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ left button
+ press
+
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ BTN_LEFT
+ PRESS
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + + + +
+
+ left button
+ release
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + +
+
+ BTN_LEFT
+ RELEASE
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ right button
+ release
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ right button
+ release
+
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ right button
+ press
+
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ BTN_RIGHT
+ PRESS
+
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ BTN_RIGHT
+ RELEASE
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + + + + + + + + + +
+
+ left button
+ release
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ other button
+ press
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + +
+
+ other button
+ press
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + +
+
+ BTN*
+ event
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ BTN*
+ event
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ BTN*
+ event
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ BTN*
+ event
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ BTN*
+ event
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ BTN_LEFT
+ PRESS
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ BTN_RIGHT
+ PRESS
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ any button
+ event
+
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ BTN*
+ event
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ all buttons
+ up?
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ no
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ yes
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ other button
+ event
+
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ BTN*
+ event
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + + + +
+
+ other button
+ event
+
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ BTN*
+ event
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + + + +
+
+ any button
+ event
+
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ BTN*
+ event
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ all buttons
+ up?
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ no
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ IDLE
+
+
+ + [Not supported by viewer] +
+
+ + + + + +
+
+ yes
+
+
+ + [Not supported by viewer] +
+
+
+
diff --git a/src/Makefile.am b/src/Makefile.am index 0f180cc4..90bdc9a8 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -11,6 +11,7 @@ libinput_la_SOURCES = \ libinput-private.h \ evdev.c \ evdev.h \ + evdev-middle-button.c \ evdev-mt-touchpad.c \ evdev-mt-touchpad.h \ evdev-mt-touchpad-tap.c \ diff --git a/src/evdev-middle-button.c b/src/evdev-middle-button.c new file mode 100644 index 00000000..328cf6c3 --- /dev/null +++ b/src/evdev-middle-button.c @@ -0,0 +1,716 @@ +/* + * Copyright © 2014 Red Hat, Inc. + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include "evdev.h" + +#define MIDDLEBUTTON_TIMEOUT 50 + +/***************************************** + * BEFORE YOU EDIT THIS FILE, look at the state diagram in + * doc/middle-button-emulation-state-machine.svg, or online at + * https://drive.google.com/file/d/0B1NwWmji69nodUJncXRMc1FvY1k/view?usp=sharing + * (it's a http://draw.io diagram) + * + * Any changes in this file must be represented in the diagram. + * + * Note in regards to the state machine: it only handles left, right and + * emulated middle button clicks, all other button events are passed + * through. When in the PASSTHROUGH state, all events are passed through + * as-is. + */ + +#define CASE_RETURN_STRING(a) case a: return #a; + +static inline const char* +middlebutton_state_to_str(enum evdev_middlebutton_state state) +{ + switch (state) { + CASE_RETURN_STRING(MIDDLEBUTTON_IDLE); + CASE_RETURN_STRING(MIDDLEBUTTON_LEFT_DOWN); + CASE_RETURN_STRING(MIDDLEBUTTON_RIGHT_DOWN); + CASE_RETURN_STRING(MIDDLEBUTTON_MIDDLE); + CASE_RETURN_STRING(MIDDLEBUTTON_LEFT_UP_PENDING); + CASE_RETURN_STRING(MIDDLEBUTTON_RIGHT_UP_PENDING); + CASE_RETURN_STRING(MIDDLEBUTTON_PASSTHROUGH); + CASE_RETURN_STRING(MIDDLEBUTTON_IGNORE_LR); + CASE_RETURN_STRING(MIDDLEBUTTON_IGNORE_L); + CASE_RETURN_STRING(MIDDLEBUTTON_IGNORE_R); + } + + return NULL; +} + +static inline const char* +middlebutton_event_to_str(enum evdev_middlebutton_event event) +{ + switch (event) { + CASE_RETURN_STRING(MIDDLEBUTTON_EVENT_L_DOWN); + CASE_RETURN_STRING(MIDDLEBUTTON_EVENT_R_DOWN); + CASE_RETURN_STRING(MIDDLEBUTTON_EVENT_OTHER); + CASE_RETURN_STRING(MIDDLEBUTTON_EVENT_L_UP); + CASE_RETURN_STRING(MIDDLEBUTTON_EVENT_R_UP); + CASE_RETURN_STRING(MIDDLEBUTTON_EVENT_TIMEOUT); + CASE_RETURN_STRING(MIDDLEBUTTON_EVENT_ALL_UP); + } + + return NULL; +} + +static void +middlebutton_state_error(struct evdev_device *device, + enum evdev_middlebutton_event event) +{ + log_bug_libinput(device->base.seat->libinput, + "Invalid event %s in middle btn state %s\n", + middlebutton_event_to_str(event), + middlebutton_state_to_str(device->middlebutton.state)); +} + +static void +middlebutton_timer_set(struct evdev_device *device, uint64_t now) +{ + libinput_timer_set(&device->middlebutton.timer, + now + MIDDLEBUTTON_TIMEOUT); +} + +static void +middlebutton_timer_cancel(struct evdev_device *device) +{ + libinput_timer_cancel(&device->middlebutton.timer); +} + +static inline void +middlebutton_set_state(struct evdev_device *device, + enum evdev_middlebutton_state state, + uint64_t now) +{ + switch (state) { + case MIDDLEBUTTON_LEFT_DOWN: + case MIDDLEBUTTON_RIGHT_DOWN: + middlebutton_timer_set(device, now); + device->middlebutton.first_event_time = now; + break; + case MIDDLEBUTTON_IDLE: + case MIDDLEBUTTON_MIDDLE: + case MIDDLEBUTTON_LEFT_UP_PENDING: + case MIDDLEBUTTON_RIGHT_UP_PENDING: + case MIDDLEBUTTON_PASSTHROUGH: + case MIDDLEBUTTON_IGNORE_LR: + case MIDDLEBUTTON_IGNORE_L: + case MIDDLEBUTTON_IGNORE_R: + middlebutton_timer_cancel(device); + break; + } + + device->middlebutton.state = state; +} + +static void +middlebutton_post_event(struct evdev_device *device, + uint64_t now, + int button, + enum libinput_button_state state) +{ + evdev_pointer_notify_button(device, + now, + button, + state); +} + +static int +evdev_middlebutton_idle_handle_event(struct evdev_device *device, + uint64_t time, + enum evdev_middlebutton_event event) +{ + switch (event) { + case MIDDLEBUTTON_EVENT_L_DOWN: + middlebutton_set_state(device, MIDDLEBUTTON_LEFT_DOWN, time); + break; + case MIDDLEBUTTON_EVENT_R_DOWN: + middlebutton_set_state(device, MIDDLEBUTTON_RIGHT_DOWN, time); + break; + case MIDDLEBUTTON_EVENT_OTHER: + return 0; + case MIDDLEBUTTON_EVENT_R_UP: + case MIDDLEBUTTON_EVENT_L_UP: + case MIDDLEBUTTON_EVENT_TIMEOUT: + middlebutton_state_error(device, event); + break; + case MIDDLEBUTTON_EVENT_ALL_UP: + break; + } + + return 1; +} + +static int +evdev_middlebutton_ldown_handle_event(struct evdev_device *device, + uint64_t time, + enum evdev_middlebutton_event event) +{ + switch (event) { + case MIDDLEBUTTON_EVENT_L_DOWN: + middlebutton_state_error(device, event); + break; + case MIDDLEBUTTON_EVENT_R_DOWN: + middlebutton_post_event(device, time, + BTN_MIDDLE, + LIBINPUT_BUTTON_STATE_PRESSED); + middlebutton_set_state(device, MIDDLEBUTTON_MIDDLE, time); + break; + case MIDDLEBUTTON_EVENT_OTHER: + middlebutton_post_event(device, time, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_PRESSED); + middlebutton_set_state(device, + MIDDLEBUTTON_PASSTHROUGH, + time); + return 0; + case MIDDLEBUTTON_EVENT_R_UP: + middlebutton_state_error(device, event); + break; + case MIDDLEBUTTON_EVENT_L_UP: + middlebutton_post_event(device, + device->middlebutton.first_event_time, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_PRESSED); + middlebutton_post_event(device, time, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_RELEASED); + middlebutton_set_state(device, MIDDLEBUTTON_IDLE, time); + break; + case MIDDLEBUTTON_EVENT_TIMEOUT: + middlebutton_post_event(device, + device->middlebutton.first_event_time, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_PRESSED); + middlebutton_set_state(device, + MIDDLEBUTTON_PASSTHROUGH, + time); + break; + case MIDDLEBUTTON_EVENT_ALL_UP: + middlebutton_state_error(device, event); + break; + } + + return 1; +} + +static int +evdev_middlebutton_rdown_handle_event(struct evdev_device *device, + uint64_t time, + enum evdev_middlebutton_event event) +{ + switch (event) { + case MIDDLEBUTTON_EVENT_L_DOWN: + middlebutton_post_event(device, time, + BTN_MIDDLE, + LIBINPUT_BUTTON_STATE_PRESSED); + middlebutton_set_state(device, MIDDLEBUTTON_MIDDLE, time); + break; + case MIDDLEBUTTON_EVENT_R_DOWN: + middlebutton_state_error(device, event); + break; + case MIDDLEBUTTON_EVENT_OTHER: + middlebutton_post_event(device, + device->middlebutton.first_event_time, + BTN_RIGHT, + LIBINPUT_BUTTON_STATE_PRESSED); + middlebutton_set_state(device, + MIDDLEBUTTON_PASSTHROUGH, + time); + return 0; + case MIDDLEBUTTON_EVENT_R_UP: + middlebutton_post_event(device, + device->middlebutton.first_event_time, + BTN_RIGHT, + LIBINPUT_BUTTON_STATE_PRESSED); + middlebutton_post_event(device, time, + BTN_RIGHT, + LIBINPUT_BUTTON_STATE_RELEASED); + middlebutton_set_state(device, MIDDLEBUTTON_IDLE, time); + break; + case MIDDLEBUTTON_EVENT_L_UP: + middlebutton_state_error(device, event); + break; + case MIDDLEBUTTON_EVENT_TIMEOUT: + middlebutton_post_event(device, + device->middlebutton.first_event_time, + BTN_RIGHT, + LIBINPUT_BUTTON_STATE_PRESSED); + middlebutton_set_state(device, + MIDDLEBUTTON_PASSTHROUGH, + time); + break; + case MIDDLEBUTTON_EVENT_ALL_UP: + middlebutton_state_error(device, event); + break; + } + + return 1; +} + +static int +evdev_middlebutton_middle_handle_event(struct evdev_device *device, + uint64_t time, + enum evdev_middlebutton_event event) +{ + switch (event) { + case MIDDLEBUTTON_EVENT_L_DOWN: + case MIDDLEBUTTON_EVENT_R_DOWN: + middlebutton_state_error(device, event); + break; + case MIDDLEBUTTON_EVENT_OTHER: + middlebutton_post_event(device, time, + BTN_MIDDLE, + LIBINPUT_BUTTON_STATE_RELEASED); + middlebutton_set_state(device, MIDDLEBUTTON_IGNORE_LR, time); + return 0; + case MIDDLEBUTTON_EVENT_R_UP: + middlebutton_post_event(device, time, + BTN_MIDDLE, + LIBINPUT_BUTTON_STATE_RELEASED); + middlebutton_set_state(device, + MIDDLEBUTTON_LEFT_UP_PENDING, + time); + break; + case MIDDLEBUTTON_EVENT_L_UP: + middlebutton_post_event(device, time, + BTN_MIDDLE, + LIBINPUT_BUTTON_STATE_RELEASED); + middlebutton_set_state(device, + MIDDLEBUTTON_RIGHT_UP_PENDING, + time); + break; + case MIDDLEBUTTON_EVENT_TIMEOUT: + middlebutton_state_error(device, event); + break; + case MIDDLEBUTTON_EVENT_ALL_UP: + middlebutton_state_error(device, event); + break; + } + + return 1; +} + +static int +evdev_middlebutton_lup_pending_handle_event(struct evdev_device *device, + uint64_t time, + enum evdev_middlebutton_event event) +{ + switch (event) { + case MIDDLEBUTTON_EVENT_L_DOWN: + middlebutton_state_error(device, event); + break; + case MIDDLEBUTTON_EVENT_R_DOWN: + middlebutton_post_event(device, time, + BTN_MIDDLE, + LIBINPUT_BUTTON_STATE_PRESSED); + middlebutton_set_state(device, MIDDLEBUTTON_MIDDLE, time); + break; + case MIDDLEBUTTON_EVENT_OTHER: + middlebutton_set_state(device, MIDDLEBUTTON_IGNORE_L, time); + return 0; + case MIDDLEBUTTON_EVENT_R_UP: + middlebutton_state_error(device, event); + break; + case MIDDLEBUTTON_EVENT_L_UP: + middlebutton_set_state(device, MIDDLEBUTTON_IDLE, time); + break; + case MIDDLEBUTTON_EVENT_TIMEOUT: + middlebutton_state_error(device, event); + break; + case MIDDLEBUTTON_EVENT_ALL_UP: + middlebutton_state_error(device, event); + break; + } + + return 1; +} + +static int +evdev_middlebutton_rup_pending_handle_event(struct evdev_device *device, + uint64_t time, + enum evdev_middlebutton_event event) +{ + switch (event) { + case MIDDLEBUTTON_EVENT_L_DOWN: + middlebutton_post_event(device, time, + BTN_MIDDLE, + LIBINPUT_BUTTON_STATE_PRESSED); + middlebutton_set_state(device, MIDDLEBUTTON_MIDDLE, time); + break; + case MIDDLEBUTTON_EVENT_R_DOWN: + middlebutton_state_error(device, event); + break; + case MIDDLEBUTTON_EVENT_OTHER: + middlebutton_set_state(device, MIDDLEBUTTON_IGNORE_R, time); + return 0; + case MIDDLEBUTTON_EVENT_R_UP: + middlebutton_set_state(device, MIDDLEBUTTON_IDLE, time); + break; + case MIDDLEBUTTON_EVENT_L_UP: + middlebutton_state_error(device, event); + break; + case MIDDLEBUTTON_EVENT_TIMEOUT: + middlebutton_state_error(device, event); + break; + case MIDDLEBUTTON_EVENT_ALL_UP: + middlebutton_state_error(device, event); + break; + } + + return 1; +} + +static int +evdev_middlebutton_passthrough_handle_event(struct evdev_device *device, + uint64_t time, + enum evdev_middlebutton_event event) +{ + switch (event) { + case MIDDLEBUTTON_EVENT_L_DOWN: + case MIDDLEBUTTON_EVENT_R_DOWN: + case MIDDLEBUTTON_EVENT_OTHER: + case MIDDLEBUTTON_EVENT_R_UP: + case MIDDLEBUTTON_EVENT_L_UP: + return 0; + case MIDDLEBUTTON_EVENT_TIMEOUT: + middlebutton_state_error(device, event); + break; + case MIDDLEBUTTON_EVENT_ALL_UP: + middlebutton_set_state(device, MIDDLEBUTTON_IDLE, time); + break; + } + + return 1; +} + +static int +evdev_middlebutton_ignore_lr_handle_event(struct evdev_device *device, + uint64_t time, + enum evdev_middlebutton_event event) +{ + switch (event) { + case MIDDLEBUTTON_EVENT_L_DOWN: + case MIDDLEBUTTON_EVENT_R_DOWN: + middlebutton_state_error(device, event); + break; + case MIDDLEBUTTON_EVENT_OTHER: + return 0; + case MIDDLEBUTTON_EVENT_R_UP: + middlebutton_set_state(device, MIDDLEBUTTON_IGNORE_L, time); + break; + case MIDDLEBUTTON_EVENT_L_UP: + middlebutton_set_state(device, MIDDLEBUTTON_IGNORE_R, time); + break; + case MIDDLEBUTTON_EVENT_TIMEOUT: + middlebutton_state_error(device, event); + break; + case MIDDLEBUTTON_EVENT_ALL_UP: + middlebutton_state_error(device, event); + break; + } + + return 1; +} + +static int +evdev_middlebutton_ignore_l_handle_event(struct evdev_device *device, + uint64_t time, + enum evdev_middlebutton_event event) +{ + switch (event) { + case MIDDLEBUTTON_EVENT_L_DOWN: + middlebutton_state_error(device, event); + break; + case MIDDLEBUTTON_EVENT_R_DOWN: + return 0; + case MIDDLEBUTTON_EVENT_OTHER: + case MIDDLEBUTTON_EVENT_R_UP: + return 0; + case MIDDLEBUTTON_EVENT_L_UP: + middlebutton_set_state(device, + MIDDLEBUTTON_PASSTHROUGH, + time); + break; + case MIDDLEBUTTON_EVENT_TIMEOUT: + case MIDDLEBUTTON_EVENT_ALL_UP: + middlebutton_state_error(device, event); + break; + } + + return 1; +} +static int +evdev_middlebutton_ignore_r_handle_event(struct evdev_device *device, + uint64_t time, + enum evdev_middlebutton_event event) +{ + switch (event) { + case MIDDLEBUTTON_EVENT_L_DOWN: + return 0; + case MIDDLEBUTTON_EVENT_R_DOWN: + middlebutton_state_error(device, event); + break; + case MIDDLEBUTTON_EVENT_OTHER: + return 0; + case MIDDLEBUTTON_EVENT_R_UP: + middlebutton_set_state(device, + MIDDLEBUTTON_PASSTHROUGH, + time); + break; + case MIDDLEBUTTON_EVENT_L_UP: + return 0; + case MIDDLEBUTTON_EVENT_TIMEOUT: + case MIDDLEBUTTON_EVENT_ALL_UP: + break; + } + + return 1; +} + +static int +evdev_middlebutton_handle_event(struct evdev_device *device, + uint64_t time, + enum evdev_middlebutton_event event) +{ + int rc; + enum evdev_middlebutton_state current; + + current = device->middlebutton.state; + + switch (current) { + case MIDDLEBUTTON_IDLE: + rc = evdev_middlebutton_idle_handle_event(device, time, event); + break; + case MIDDLEBUTTON_LEFT_DOWN: + rc = evdev_middlebutton_ldown_handle_event(device, time, event); + break; + case MIDDLEBUTTON_RIGHT_DOWN: + rc = evdev_middlebutton_rdown_handle_event(device, time, event); + break; + case MIDDLEBUTTON_MIDDLE: + rc = evdev_middlebutton_middle_handle_event(device, time, event); + break; + case MIDDLEBUTTON_LEFT_UP_PENDING: + rc = evdev_middlebutton_lup_pending_handle_event(device, + time, + event); + break; + case MIDDLEBUTTON_RIGHT_UP_PENDING: + rc = evdev_middlebutton_rup_pending_handle_event(device, + time, + event); + break; + case MIDDLEBUTTON_PASSTHROUGH: + rc = evdev_middlebutton_passthrough_handle_event(device, + time, + event); + break; + case MIDDLEBUTTON_IGNORE_LR: + rc = evdev_middlebutton_ignore_lr_handle_event(device, + time, + event); + break; + case MIDDLEBUTTON_IGNORE_L: + rc = evdev_middlebutton_ignore_l_handle_event(device, + time, + event); + break; + case MIDDLEBUTTON_IGNORE_R: + rc = evdev_middlebutton_ignore_r_handle_event(device, + time, + event); + break; + } + + log_debug(device->base.seat->libinput, + "middlebuttonstate: %s → %s → %s, rc %d\n", + middlebutton_state_to_str(current), + middlebutton_event_to_str(event), + middlebutton_state_to_str(device->middlebutton.state), + rc); + + return rc; +} + +static inline void +evdev_middlebutton_apply_config(struct evdev_device *device) +{ + if (device->middlebutton.want_enabled == + device->middlebutton.enabled) + return; + + if (device->middlebutton.button_mask != 0) + return; + + device->middlebutton.enabled = device->middlebutton.want_enabled; +} + +bool +evdev_middlebutton_filter_button(struct evdev_device *device, + uint64_t time, + int button, + enum libinput_button_state state) +{ + enum evdev_middlebutton_event event; + bool is_press = state == LIBINPUT_BUTTON_STATE_PRESSED; + int rc; + unsigned int bit = (button - BTN_LEFT); + uint32_t old_mask = 0; + + if (!device->middlebutton.enabled) + return false; + + switch (button) { + case BTN_LEFT: + if (is_press) + event = MIDDLEBUTTON_EVENT_L_DOWN; + else + event = MIDDLEBUTTON_EVENT_L_UP; + break; + case BTN_RIGHT: + if (is_press) + event = MIDDLEBUTTON_EVENT_R_DOWN; + else + event = MIDDLEBUTTON_EVENT_R_UP; + break; + + /* BTN_MIDDLE counts as "other" and resets middle button + * emulation */ + case BTN_MIDDLE: + default: + event = MIDDLEBUTTON_EVENT_OTHER; + break; + } + + if (button < BTN_LEFT || + bit >= sizeof(device->middlebutton.button_mask) * 8) { + log_bug_libinput(device->base.seat->libinput, + "Button mask too small for %d\n", + libevdev_event_code_get_name(EV_KEY, + button)); + return true; + } + + rc = evdev_middlebutton_handle_event(device, time, event); + + old_mask = device->middlebutton.button_mask; + if (is_press) + device->middlebutton.button_mask |= 1 << bit; + else + device->middlebutton.button_mask &= ~(1 << bit); + + if (old_mask != device->middlebutton.button_mask && + device->middlebutton.button_mask == 0) { + evdev_middlebutton_handle_event(device, + time, + MIDDLEBUTTON_EVENT_ALL_UP); + evdev_middlebutton_apply_config(device); + } + + return rc; +} + +static void +evdev_middlebutton_handle_timeout(uint64_t now, void *data) +{ + struct evdev_device *device = (struct evdev_device*)data; + + evdev_middlebutton_handle_event(device, + libinput_now(device->base.seat->libinput), + MIDDLEBUTTON_EVENT_TIMEOUT); +} + +static int +evdev_middlebutton_is_available(struct libinput_device *device) +{ + return 1; +} + +static enum libinput_config_status +evdev_middlebutton_set(struct libinput_device *device, + enum libinput_config_middle_emulation_state enable) +{ + struct evdev_device *evdev = (struct evdev_device*)device; + + switch (enable) { + case LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED: + evdev->middlebutton.want_enabled = true; + break; + case LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED: + evdev->middlebutton.want_enabled = false; + break; + default: + return LIBINPUT_CONFIG_STATUS_INVALID; + } + + evdev_middlebutton_apply_config(evdev); + + return LIBINPUT_CONFIG_STATUS_SUCCESS; +} + +static enum libinput_config_middle_emulation_state +evdev_middlebutton_get(struct libinput_device *device) +{ + struct evdev_device *evdev = (struct evdev_device*)device; + + return evdev->middlebutton.enabled ? + LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED : + LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED; +} + +static enum libinput_config_middle_emulation_state +evdev_middlebutton_get_default(struct libinput_device *device) +{ + struct evdev_device *evdev = (struct evdev_device*)device; + + return evdev->middlebutton.enabled_default ? + LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED : + LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED; +} + +void +evdev_init_middlebutton(struct evdev_device *device, + bool enable, + bool want_config) +{ + libinput_timer_init(&device->middlebutton.timer, + device->base.seat->libinput, + evdev_middlebutton_handle_timeout, + device); + device->middlebutton.enabled_default = enable; + device->middlebutton.want_enabled = enable; + device->middlebutton.enabled = enable; + + if (!want_config) + return; + + device->middlebutton.config.available = evdev_middlebutton_is_available; + device->middlebutton.config.set = evdev_middlebutton_set; + device->middlebutton.config.get = evdev_middlebutton_get; + device->middlebutton.config.get_default = evdev_middlebutton_get_default; + device->base.config.middle_emulation = &device->middlebutton.config; +} diff --git a/src/evdev.c b/src/evdev.c index 6f87484d..6ca874a1 100644 --- a/src/evdev.c +++ b/src/evdev.c @@ -143,6 +143,12 @@ evdev_pointer_notify_physical_button(struct evdev_device *device, int button, enum libinput_button_state state) { + if (evdev_middlebutton_filter_button(device, + time, + button, + state)) + return; + evdev_pointer_notify_button(device, time, button, state); } diff --git a/src/evdev.h b/src/evdev.h index f2ce9bcd..9b1dcc36 100644 --- a/src/evdev.h +++ b/src/evdev.h @@ -63,6 +63,29 @@ enum evdev_device_tags { EVDEV_TAG_TOUCHPAD_TRACKPOINT = (1 << 3), }; +enum evdev_middlebutton_state { + MIDDLEBUTTON_IDLE, + MIDDLEBUTTON_LEFT_DOWN, + MIDDLEBUTTON_RIGHT_DOWN, + MIDDLEBUTTON_MIDDLE, + MIDDLEBUTTON_LEFT_UP_PENDING, + MIDDLEBUTTON_RIGHT_UP_PENDING, + MIDDLEBUTTON_IGNORE_LR, + MIDDLEBUTTON_IGNORE_L, + MIDDLEBUTTON_IGNORE_R, + MIDDLEBUTTON_PASSTHROUGH, +}; + +enum evdev_middlebutton_event { + MIDDLEBUTTON_EVENT_L_DOWN, + MIDDLEBUTTON_EVENT_R_DOWN, + MIDDLEBUTTON_EVENT_OTHER, + MIDDLEBUTTON_EVENT_L_UP, + MIDDLEBUTTON_EVENT_R_UP, + MIDDLEBUTTON_EVENT_TIMEOUT, + MIDDLEBUTTON_EVENT_ALL_UP, +}; + struct mt_slot { int32_t seat_slot; struct device_coords point; @@ -158,6 +181,18 @@ struct evdev_device { void (*change_to_enabled)(struct evdev_device *device); } left_handed; + struct { + struct libinput_device_config_middle_emulation config; + /* middle-button emulation enabled */ + bool enabled; + bool enabled_default; + bool want_enabled; + enum evdev_middlebutton_state state; + struct libinput_timer timer; + uint32_t button_mask; + uint64_t first_event_time; + } middlebutton; + int dpi; /* HW resolution */ struct ratelimit syn_drop_limit; /* ratelimit for SYN_DROPPED logging */ }; @@ -332,6 +367,17 @@ evdev_device_remove(struct evdev_device *device); void evdev_device_destroy(struct evdev_device *device); +bool +evdev_middlebutton_filter_button(struct evdev_device *device, + uint64_t time, + int button, + enum libinput_button_state state); + +void +evdev_init_middlebutton(struct evdev_device *device, + bool enabled, + bool want_config); + static inline double evdev_convert_to_mm(const struct input_absinfo *absinfo, double v) { diff --git a/test/litest.c b/test/litest.c index 5c4f84dd..12851071 100644 --- a/test/litest.c +++ b/test/litest.c @@ -1436,6 +1436,12 @@ litest_timeout_edgescroll(void) msleep(300); } +void +litest_timeout_middlebutton(void) +{ + msleep(70); +} + void litest_push_event_frame(struct litest_device *dev) { diff --git a/test/litest.h b/test/litest.h index 84567be3..8cb11214 100644 --- a/test/litest.h +++ b/test/litest.h @@ -194,6 +194,7 @@ void litest_timeout_softbuttons(void); void litest_timeout_buttonscroll(void); void litest_timeout_edgescroll(void); void litest_timeout_finger_switch(void); +void litest_timeout_middlebutton(void); void litest_push_event_frame(struct litest_device *dev); void litest_pop_event_frame(struct litest_device *dev); diff --git a/test/touchpad.c b/test/touchpad.c index 4e425f58..ac8ffb92 100644 --- a/test/touchpad.c +++ b/test/touchpad.c @@ -2895,6 +2895,9 @@ START_TEST(touchpad_left_handed_delayed) /* left-handed takes effect now */ litest_button_click(dev, BTN_RIGHT, 1); + libinput_dispatch(li); + litest_timeout_middlebutton(); + libinput_dispatch(li); litest_button_click(dev, BTN_LEFT, 1); libinput_dispatch(li);