tablet: handle a direct tool switch correctly
authorPeter Hutterer <peter.hutterer@who-t.net>
Wed, 30 Oct 2019 05:23:24 +0000 (15:23 +1000)
committerPeter Hutterer <peter.hutterer@who-t.net>
Thu, 31 Oct 2019 23:27:39 +0000 (23:27 +0000)
Fixes #259

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

index f2fbf847b0eb4b272ae7f503ae2eb42f4eb9a61d..67880d037454db773c8ded65ac76818d8e89866e 100644 (file)
@@ -1731,7 +1731,7 @@ tablet_proximity_out_quirk_set_timer(struct tablet_dispatch *tablet,
                                   time + FORCED_PROXOUT_TIMEOUT);
 }
 
-static void
+static bool
 tablet_update_tool_state(struct tablet_dispatch *tablet,
                         struct evdev_device *device,
                         uint64_t time)
@@ -1739,6 +1739,7 @@ tablet_update_tool_state(struct tablet_dispatch *tablet,
        enum libinput_tablet_tool_type type;
        uint32_t changed;
        int state;
+       uint32_t doubled_up_new_tool_bit = 0;
 
        /* We need to emulate a BTN_TOOL_PEN if we get an axis event (i.e.
         * stylus is def. in proximity) and:
@@ -1766,16 +1767,23 @@ tablet_update_tool_state(struct tablet_dispatch *tablet,
        }
 
        if (tablet->tool_state == tablet->prev_tool_state)
-               return;
+               return false;
 
        /* Kernel tools are supposed to be mutually exclusive, if we have
-        * two set discard the most recent one. */
+        * two, we force a proximity out for the older tool and handle the
+        * new tool as separate proximity in event.
+        */
        if (tablet->tool_state & (tablet->tool_state - 1)) {
-               evdev_log_bug_kernel(device,
-                                    "Multiple tools active simultaneously (%#x)\n",
-                                    tablet->tool_state);
-               tablet->tool_state = tablet->prev_tool_state;
-               goto out;
+               /* tool_state has 2 bits set. We set the current tool state
+                * to zero, thus setting everything up for a prox out on the
+                * tool. Once that is set up, we change the tool state to be
+                * the new one we just got so when we re-process this
+                * function we now get the new tool as prox in.
+                * Importantly, we basically rely on nothing else happening
+                * in the meantime.
+                */
+               doubled_up_new_tool_bit = tablet->tool_state ^ tablet->prev_tool_state;
+               tablet->tool_state = 0;
        }
 
        changed = tablet->tool_state ^ tablet->prev_tool_state;
@@ -1803,9 +1811,13 @@ tablet_update_tool_state(struct tablet_dispatch *tablet,
                }
        }
 
-out:
        tablet->prev_tool_state = tablet->tool_state;
 
+       if (doubled_up_new_tool_bit) {
+               tablet->tool_state = doubled_up_new_tool_bit;
+               return true; /* need to re-process */
+       }
+       return false;
 }
 
 static struct libinput_tablet_tool *
@@ -1826,8 +1838,10 @@ tablet_flush(struct tablet_dispatch *tablet,
             uint64_t time)
 {
        struct libinput_tablet_tool *tool;
+       bool process_tool_twice;
 
-       tablet_update_tool_state(tablet, device, time);
+reprocess:
+       process_tool_twice = tablet_update_tool_state(tablet, device, time);
 
        tool = tablet_get_current_tool(tablet);
        if (!tool)
@@ -1860,6 +1874,9 @@ tablet_flush(struct tablet_dispatch *tablet,
        }
 
        tablet_send_events(tablet, tool, device, time);
+
+       if (process_tool_twice)
+               goto reprocess;
 }
 
 static inline void
index 2ff3eae353c6e88bb2ec0651f1d06c502a20bc85..41452eeef32a8191dc6df7217f1424812433336e 100644 (file)
@@ -2902,12 +2902,28 @@ START_TEST(tool_direct_switch_skip_tool_update)
        libinput_tablet_tool_ref(tool);
        libinput_event_destroy(event);
 
-       /* Direct tool switch after proximity in is ignored */
-       litest_disable_log_handler(li);
        litest_event(dev, EV_KEY, BTN_TOOL_RUBBER, 1);
        litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       litest_assert_empty_queue(li);
-       litest_restore_log_handler(li);
+       libinput_dispatch(li);
+
+       event = libinput_get_event(li);
+       tev = litest_is_tablet_event(event,
+                                    LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY);
+       ck_assert_int_eq(libinput_event_tablet_tool_get_proximity_state(tev),
+                        LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT);
+       ck_assert_ptr_eq(libinput_event_tablet_tool_get_tool(tev), tool);
+       libinput_event_destroy(event);
+
+       event = libinput_get_event(li);
+       tev = litest_is_tablet_event(event,
+                                    LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY);
+       ck_assert_int_eq(libinput_event_tablet_tool_get_proximity_state(tev),
+                        LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN);
+       ck_assert_ptr_ne(libinput_event_tablet_tool_get_tool(tev), tool);
+       libinput_tablet_tool_unref(tool);
+       tool = libinput_event_tablet_tool_get_tool(tev);
+       libinput_tablet_tool_ref(tool);
+       libinput_event_destroy(event);
 
        litest_tablet_motion(dev, 20, 30, axes);
        libinput_dispatch(li);
@@ -2919,18 +2935,36 @@ START_TEST(tool_direct_switch_skip_tool_update)
                         tool);
        libinput_event_destroy(event);
 
-       /* Direct tool switch during sequence in is ignored */
-       litest_disable_log_handler(li);
        litest_event(dev, EV_KEY, BTN_TOOL_RUBBER, 0);
        litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       litest_assert_empty_queue(li);
+       libinput_dispatch(li);
+
+       event = libinput_get_event(li);
+       tev = litest_is_tablet_event(event,
+                                    LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY);
+       ck_assert_int_eq(libinput_event_tablet_tool_get_proximity_state(tev),
+                        LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT);
+       ck_assert_ptr_eq(libinput_event_tablet_tool_get_tool(tev),
+                        tool);
+       libinput_event_destroy(event);
 
        litest_push_event_frame(dev);
        litest_event(dev, EV_KEY, BTN_TOOL_RUBBER, 1);
        litest_tablet_motion(dev, 30, 40, axes);
        litest_pop_event_frame(dev);
        libinput_dispatch(li);
-       litest_restore_log_handler(li);
+
+       event = libinput_get_event(li);
+       tev = litest_is_tablet_event(event,
+                                    LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY);
+       ck_assert_int_eq(libinput_event_tablet_tool_get_proximity_state(tev),
+                        LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN);
+       ck_assert_ptr_eq(libinput_event_tablet_tool_get_tool(tev),
+                        tool);
+       libinput_event_destroy(event);
+
+       litest_tablet_motion(dev, 40, 30, axes);
+       libinput_dispatch(li);
 
        event = libinput_get_event(li);
        tev = litest_is_tablet_event(event,
@@ -2939,7 +2973,10 @@ START_TEST(tool_direct_switch_skip_tool_update)
                         tool);
        libinput_event_destroy(event);
 
+       litest_push_event_frame(dev);
+       litest_event(dev, EV_KEY, BTN_TOOL_RUBBER, 0);
        litest_tablet_proximity_out(dev);
+       litest_pop_event_frame(dev);
        libinput_dispatch(li);
        litest_timeout_tablet_proxout();
        libinput_dispatch(li);