fallback: suspend internal keyboards and trackpoints on tablet-mode switch
authorPeter Hutterer <peter.hutterer@who-t.net>
Wed, 20 Sep 2017 04:40:59 +0000 (14:40 +1000)
committerPeter Hutterer <peter.hutterer@who-t.net>
Mon, 25 Sep 2017 05:34:45 +0000 (15:34 +1000)
Because on some devices the keyboard is where the fingers are holding the
device when in tablet mode.

https://bugs.freedesktop.org/show_bug.cgi?id=102729

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
src/evdev-fallback.c
test/test-switch.c

index adbb091b2c7bf1cd9953494b84db451551b33c4a..13e130acb77b7f026773b8e3c30c2abc4da243a6 100644 (file)
@@ -66,7 +66,16 @@ struct fallback_dispatch {
        struct device_coords rel;
 
        struct {
-               int state;
+               /* The struct for the tablet mode switch device itself */
+               struct {
+                       int state;
+               } sw;
+               /* The struct for other devices listening to the tablet mode
+                  switch */
+               struct {
+                       struct evdev_device *sw_device;
+                       struct libinput_event_listener listener;
+               } other;
        } tablet_mode;
 
        /* Bitmask of pressed keys used to ignore initial release events from
@@ -181,7 +190,7 @@ fallback_interface_get_switch_state(struct evdev_dispatch *evdev_dispatch,
                abort();
        }
 
-       return dispatch->tablet_mode.state ?
+       return dispatch->tablet_mode.sw.state ?
                        LIBINPUT_SWITCH_STATE_ON :
                        LIBINPUT_SWITCH_STATE_OFF;
 }
@@ -992,10 +1001,10 @@ fallback_process_switch(struct fallback_dispatch *dispatch,
                fallback_lid_notify_toggle(dispatch, device, time);
                break;
        case SW_TABLET_MODE:
-               if (dispatch->tablet_mode.state == e->value)
+               if (dispatch->tablet_mode.sw.state == e->value)
                        return;
 
-               dispatch->tablet_mode.state = e->value;
+               dispatch->tablet_mode.sw.state = e->value;
                if (e->value)
                        state = LIBINPUT_SWITCH_STATE_ON;
                else
@@ -1263,6 +1272,8 @@ fallback_interface_remove(struct evdev_dispatch *evdev_dispatch)
        struct fallback_dispatch *dispatch = fallback_dispatch(evdev_dispatch);
        struct paired_keyboard *kbd;
 
+       libinput_device_remove_event_listener(&dispatch->tablet_mode.other.listener);
+
        ARRAY_FOR_EACH(dispatch->lid.paired_keyboard, kbd) {
                if (!kbd->device)
                        continue;
@@ -1299,7 +1310,7 @@ fallback_interface_sync_initial_state(struct evdev_device *device,
                }
        }
 
-       if (dispatch->tablet_mode.state) {
+       if (dispatch->tablet_mode.sw.state) {
                switch_notify_toggle(&device->base,
                                     time,
                                     LIBINPUT_SWITCH_TABLET_MODE,
@@ -1377,11 +1388,94 @@ fallback_lid_pair_keyboard(struct evdev_device *lid_switch,
                                       "lid: too many internal keyboards\n");
 }
 
+static void
+fallback_resume(struct fallback_dispatch *dispatch,
+               struct evdev_device *device)
+{
+       if (dispatch->base.sendevents.current_mode ==
+           LIBINPUT_CONFIG_SEND_EVENTS_DISABLED)
+               return;
+
+       evdev_device_resume(device);
+}
+
+static void
+fallback_suspend(struct fallback_dispatch *dispatch,
+                struct evdev_device *device)
+{
+       evdev_device_suspend(device);
+}
+
+static void
+fallback_tablet_mode_switch_event(uint64_t time,
+                                 struct libinput_event *event,
+                                 void *data)
+{
+       struct fallback_dispatch *dispatch = data;
+       struct evdev_device *device = dispatch->device;
+       struct libinput_event_switch *swev;
+
+       if (libinput_event_get_type(event) != LIBINPUT_EVENT_SWITCH_TOGGLE)
+               return;
+
+       swev = libinput_event_get_switch_event(event);
+       if (libinput_event_switch_get_switch(swev) !=
+           LIBINPUT_SWITCH_TABLET_MODE)
+               return;
+
+
+       switch (libinput_event_switch_get_switch_state(swev)) {
+       case LIBINPUT_SWITCH_STATE_OFF:
+               fallback_resume(dispatch, device);
+               evdev_log_debug(device, "tablet-mode: resuming device\n");
+               break;
+       case LIBINPUT_SWITCH_STATE_ON:
+               fallback_suspend(dispatch, device);
+               evdev_log_debug(device, "tablet-mode: suspending device\n");
+               break;
+       }
+}
+
+static void
+fallback_keyboard_pair_tablet_mode(struct evdev_device *keyboard,
+                                  struct evdev_device *tablet_mode_switch)
+{
+       struct fallback_dispatch *dispatch =
+               fallback_dispatch(keyboard->dispatch);
+
+       if ((keyboard->tags &
+            (EVDEV_TAG_TRACKPOINT|EVDEV_TAG_INTERNAL_KEYBOARD)) == 0)
+               return;
+
+       if ((tablet_mode_switch->tags & EVDEV_TAG_TABLET_MODE_SWITCH) == 0)
+               return;
+
+       if (dispatch->tablet_mode.other.sw_device)
+               return;
+
+       evdev_log_debug(keyboard,
+                       "tablet_mode_switch: activated for %s<->%s\n",
+                       keyboard->devname,
+                       tablet_mode_switch->devname);
+
+       libinput_device_add_event_listener(&tablet_mode_switch->base,
+                               &dispatch->tablet_mode.other.listener,
+                               fallback_tablet_mode_switch_event,
+                               dispatch);
+       dispatch->tablet_mode.other.sw_device = tablet_mode_switch;
+
+       if (evdev_device_switch_get_state(tablet_mode_switch,
+                                         LIBINPUT_SWITCH_TABLET_MODE)
+                   == LIBINPUT_SWITCH_STATE_ON)
+               fallback_suspend(dispatch, keyboard);
+}
+
 static void
 fallback_interface_device_added(struct evdev_device *device,
                                struct evdev_device *added_device)
 {
        fallback_lid_pair_keyboard(device, added_device);
+       fallback_keyboard_pair_tablet_mode(device, added_device);
 }
 
 static void
@@ -1403,6 +1497,14 @@ fallback_interface_device_removed(struct evdev_device *device,
                libinput_device_init_event_listener(&kbd->listener);
                kbd->device = NULL;
        }
+
+       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 = {
@@ -1602,8 +1704,10 @@ fallback_dispatch_init_switch(struct fallback_dispatch *dispatch,
                val = libevdev_get_event_value(device->evdev,
                                               EV_SW,
                                               SW_TABLET_MODE);
-               dispatch->tablet_mode.state = val;
+               dispatch->tablet_mode.sw.state = val;
        }
+
+       libinput_device_init_event_listener(&dispatch->tablet_mode.other.listener);
 }
 
 struct evdev_dispatch *
index c306305b89822fe643b8e780f1b04cd57d81221b..3a499bf700c77696b196cc71f63a82134379c438 100644 (file)
@@ -864,6 +864,161 @@ START_TEST(tablet_mode_disable_touchpad_on_init)
 }
 END_TEST
 
+START_TEST(tablet_mode_disable_keyboard)
+{
+       struct litest_device *sw = litest_current_device();
+       struct litest_device *keyboard;
+       struct libinput *li = sw->libinput;
+
+       if (!switch_has_tablet_mode(sw))
+               return;
+
+       keyboard = litest_add_device(li, LITEST_KEYBOARD);
+       litest_drain_events(li);
+
+       litest_keyboard_key(keyboard, KEY_A, true);
+       litest_keyboard_key(keyboard, KEY_A, false);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
+
+       litest_switch_action(sw,
+                            LIBINPUT_SWITCH_TABLET_MODE,
+                            LIBINPUT_SWITCH_STATE_ON);
+       litest_drain_events(li);
+
+       litest_keyboard_key(keyboard, KEY_A, true);
+       litest_keyboard_key(keyboard, KEY_A, false);
+       litest_assert_empty_queue(li);
+
+       litest_switch_action(sw,
+                            LIBINPUT_SWITCH_TABLET_MODE,
+                            LIBINPUT_SWITCH_STATE_OFF);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_SWITCH_TOGGLE);
+
+       litest_keyboard_key(keyboard, KEY_A, true);
+       litest_keyboard_key(keyboard, KEY_A, false);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
+
+       litest_delete_device(keyboard);
+}
+END_TEST
+
+START_TEST(tablet_mode_disable_keyboard_on_init)
+{
+       struct litest_device *sw = litest_current_device();
+       struct litest_device *keyboard;
+       struct libinput *li = sw->libinput;
+
+       if (!switch_has_tablet_mode(sw))
+               return;
+
+       litest_switch_action(sw,
+                            LIBINPUT_SWITCH_TABLET_MODE,
+                            LIBINPUT_SWITCH_STATE_ON);
+       litest_drain_events(li);
+
+       /* keyboard comes with switch already on - no events */
+       keyboard = litest_add_device(li, LITEST_KEYBOARD);
+       litest_drain_events(li);
+
+       litest_keyboard_key(keyboard, KEY_A, true);
+       litest_keyboard_key(keyboard, KEY_A, false);
+       litest_assert_empty_queue(li);
+
+       litest_switch_action(sw,
+                            LIBINPUT_SWITCH_TABLET_MODE,
+                            LIBINPUT_SWITCH_STATE_OFF);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_SWITCH_TOGGLE);
+
+       litest_keyboard_key(keyboard, KEY_A, true);
+       litest_keyboard_key(keyboard, KEY_A, false);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
+
+       litest_delete_device(keyboard);
+}
+END_TEST
+
+START_TEST(tablet_mode_disable_trackpoint)
+{
+       struct litest_device *sw = litest_current_device();
+       struct litest_device *trackpoint;
+       struct libinput *li = sw->libinput;
+
+       if (!switch_has_tablet_mode(sw))
+               return;
+
+       trackpoint = litest_add_device(li, LITEST_TRACKPOINT);
+       litest_drain_events(li);
+
+       litest_event(trackpoint, EV_REL, REL_Y, -1);
+       litest_event(trackpoint, EV_SYN, SYN_REPORT, 0);
+       litest_event(trackpoint, EV_REL, REL_Y, -1);
+       litest_event(trackpoint, EV_SYN, SYN_REPORT, 0);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+
+       litest_switch_action(sw,
+                            LIBINPUT_SWITCH_TABLET_MODE,
+                            LIBINPUT_SWITCH_STATE_ON);
+       litest_drain_events(li);
+
+       litest_event(trackpoint, EV_REL, REL_Y, -1);
+       litest_event(trackpoint, EV_SYN, SYN_REPORT, 0);
+       litest_event(trackpoint, EV_REL, REL_Y, -1);
+       litest_event(trackpoint, EV_SYN, SYN_REPORT, 0);
+       litest_assert_empty_queue(li);
+
+       litest_switch_action(sw,
+                            LIBINPUT_SWITCH_TABLET_MODE,
+                            LIBINPUT_SWITCH_STATE_OFF);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_SWITCH_TOGGLE);
+
+       litest_event(trackpoint, EV_REL, REL_Y, -1);
+       litest_event(trackpoint, EV_SYN, SYN_REPORT, 0);
+       litest_event(trackpoint, EV_REL, REL_Y, -1);
+       litest_event(trackpoint, EV_SYN, SYN_REPORT, 0);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+
+       litest_delete_device(trackpoint);
+}
+END_TEST
+START_TEST(tablet_mode_disable_trackpoint_on_init)
+{
+       struct litest_device *sw = litest_current_device();
+       struct litest_device *trackpoint;
+       struct libinput *li = sw->libinput;
+
+       if (!switch_has_tablet_mode(sw))
+               return;
+
+       litest_switch_action(sw,
+                            LIBINPUT_SWITCH_TABLET_MODE,
+                            LIBINPUT_SWITCH_STATE_ON);
+       litest_drain_events(li);
+
+       /* trackpoint comes with switch already on - no events */
+       trackpoint = litest_add_device(li, LITEST_TRACKPOINT);
+       litest_drain_events(li);
+
+       litest_event(trackpoint, EV_REL, REL_Y, -1);
+       litest_event(trackpoint, EV_SYN, SYN_REPORT, 0);
+       litest_event(trackpoint, EV_REL, REL_Y, -1);
+       litest_event(trackpoint, EV_SYN, SYN_REPORT, 0);
+       litest_assert_empty_queue(li);
+
+       litest_switch_action(sw,
+                            LIBINPUT_SWITCH_TABLET_MODE,
+                            LIBINPUT_SWITCH_STATE_OFF);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_SWITCH_TOGGLE);
+
+       litest_event(trackpoint, EV_REL, REL_Y, -1);
+       litest_event(trackpoint, EV_SYN, SYN_REPORT, 0);
+       litest_event(trackpoint, EV_REL, REL_Y, -1);
+       litest_event(trackpoint, EV_SYN, SYN_REPORT, 0);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+
+       litest_delete_device(trackpoint);
+}
+END_TEST
+
 void
 litest_setup_tests_lid(void)
 {
@@ -896,4 +1051,8 @@ litest_setup_tests_lid(void)
        litest_add_for_device("lid:keypress", lid_key_press, LITEST_GPIO_KEYS);
 
        litest_add("tablet-mode:touchpad", tablet_mode_disable_touchpad_on_init, LITEST_SWITCH, LITEST_ANY);
+       litest_add("tablet-mode:keyboard", tablet_mode_disable_keyboard, LITEST_SWITCH, LITEST_ANY);
+       litest_add("tablet-mode:keyboard", tablet_mode_disable_keyboard_on_init, LITEST_SWITCH, LITEST_ANY);
+       litest_add("tablet-mode:trackpoint", tablet_mode_disable_trackpoint, LITEST_SWITCH, LITEST_ANY);
+       litest_add("tablet-mode:trackpoint", tablet_mode_disable_trackpoint_on_init, LITEST_SWITCH, LITEST_ANY);
 }