From: Peter Hutterer Date: Mon, 13 Apr 2015 06:38:44 +0000 (+1000) Subject: evdev: add support for middle button emulation X-Git-Tag: 0.14.0~17 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=75f15917a396136fbc0f1c56f5e654a89a320a44;p=platform%2Fupstream%2Flibinput.git evdev: add support for middle button emulation This is just the required framework, it's not hooked up to anything just yet. Hooking it up comes as separate commit to better detail why/when a device supports emulation. Signed-off-by: Peter Hutterer Reviewed-by: Hans de Goede --- 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);