touchpad: add sticky mode to drag-lock
authorsatrmb <10471-satrmb_true-email-is-private_contact-via-web@gitlab.freedesktop.org>
Mon, 2 Sep 2024 14:29:02 +0000 (16:29 +0200)
committerMarge Bot <emma+marge@anholt.net>
Thu, 5 Sep 2024 00:47:47 +0000 (00:47 +0000)
Sticky mode removes the timeout from drag-lock, only a tap ends a drag.
Timeout mode remains available without changes.

Sticky mode is exposed as a new value for the existing drag-lock setting.

Part-of: <https://gitlab.freedesktop.org/libinput/libinput/-/merge_requests/1037>

12 files changed:
doc/user/configuration.rst
doc/user/tapping.rst
src/evdev-mt-touchpad-tap.c
src/evdev-mt-touchpad.h
src/libinput.c
src/libinput.h
test/litest.h
test/test-touchpad-tap.c
tools/libinput-debug-events.man
tools/shared.c
tools/shared.h
tools/test_tool_option_parsing.py

index 8ce31f21d1198aaa1b4a93ffa529530d3d89798d..e9260a3b9598ba30b9707150ed8c6e71cd8dabef 100644 (file)
@@ -28,7 +28,7 @@ options exposed by libinput are:
 - how many tapping fingers are supported by this device
 - a toggle to enable/disable tapping
 - a toggle to enable/disable tap-and-drag, see :ref:`tapndrag`.
-- a toggle to enable/disable tap-and-drag drag lock see :ref:`tapndrag`
+- a toggle to enable/disable tap-and-drag drag lock, see :ref:`tapndrag`
 - The default order is 1, 2, 3 finger tap mapping to left, right, middle
   click, respectively. This order can be changed to left, middle, right click,
   respectively.
index 85b55f1fd124fc02edbb9e168d23a570a5272a20..5c02ca6eb1398d7b43d3b8cd0e4f69e1371f101e 100644 (file)
@@ -56,11 +56,13 @@ tap-and-drag enabled by default.
           single-finger drag.
 
 Also optional is a feature called "drag lock". With drag lock disabled, lifting
-the finger will stop any drag process. When enabled, libinput will ignore a
-finger up event during a drag process, provided the finger is set down again
-within a implementation-specific timeout. Drag lock can be enabled and
-disabled with **libinput_device_config_tap_set_drag_lock_enabled()**.
-Note that drag lock only applies if tap-and-drag is be enabled.
+the finger will stop any drag process. When enabled, the drag
+process continues even after lifting a finger but can be ended
+with an additional tap. If timeout-based drag-locks are enabled
+the drag process will also automatically end once the finger has
+been lifted for an implementation-specific timeout. Drag lock can be
+enabled and disabled with **libinput_device_config_tap_set_drag_lock_enabled()**.
+Note that drag lock only applies if tap-and-drag is enabled.
 
 .. figure:: tap-n-drag.svg
     :align: center
@@ -78,6 +80,9 @@ If drag lock is enabled, the release of the mouse buttons after the finger
 release (e) is triggered by a timeout. To release the button immediately,
 simply tap again (f).
 
+If drag lock is enabled in sticky mode there is no timeout after
+releasing a finger and an extra tap is required to release the button.
+
 If two fingers are supported by the hardware, a second finger can be used to
 drag while the first is held in-place.
 
index 299cd554c71786b27c835834241fd1dba2ceab06..3c76c3d65fab575f010147d70eff03c2c7873900 100644 (file)
@@ -815,7 +815,7 @@ tp_tap_dragging_handle_event(struct tp_dispatch *tp,
                break;
        }
        case TAP_EVENT_RELEASE:
-               if (tp->tap.drag_lock_enabled) {
+               if (tp->tap.drag_lock != LIBINPUT_CONFIG_DRAG_LOCK_DISABLED) {
                        enum tp_tap_state dest[3] = {
                                TAP_STATE_1FGTAP_DRAGGING_WAIT,
                                TAP_STATE_2FGTAP_DRAGGING_WAIT,
@@ -823,7 +823,8 @@ tp_tap_dragging_handle_event(struct tp_dispatch *tp,
                        };
                        assert(nfingers_tapped >= 1 && nfingers_tapped <= 3);
                        tp->tap.state = dest[nfingers_tapped - 1];
-                       tp_tap_set_draglock_timer(tp, time);
+                       if (tp->tap.drag_lock == LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_TIMEOUT)
+                               tp_tap_set_draglock_timer(tp, time);
                } else {
                        tp_tap_notify(tp,
                                      time,
@@ -1517,7 +1518,7 @@ tp_tap_config_set_draglock_enabled(struct libinput_device *device,
        struct evdev_dispatch *dispatch = evdev_device(device)->dispatch;
        struct tp_dispatch *tp = tp_dispatch(dispatch);
 
-       tp->tap.drag_lock_enabled = enabled;
+       tp->tap.drag_lock = enabled;
 
        return LIBINPUT_CONFIG_STATUS_SUCCESS;
 }
@@ -1528,7 +1529,7 @@ tp_tap_config_get_draglock_enabled(struct libinput_device *device)
        struct evdev_dispatch *dispatch = evdev_device(device)->dispatch;
        struct tp_dispatch *tp = tp_dispatch(dispatch);
 
-       return tp->tap.drag_lock_enabled;
+       return tp->tap.drag_lock;
 }
 
 static inline enum libinput_config_drag_lock_state
@@ -1570,7 +1571,7 @@ tp_init_tap(struct tp_dispatch *tp)
        tp->tap.map = LIBINPUT_CONFIG_TAP_MAP_LRM;
        tp->tap.want_map = tp->tap.map;
        tp->tap.drag_enabled = tp_drag_default(tp->device);
-       tp->tap.drag_lock_enabled = tp_drag_lock_default(tp->device);
+       tp->tap.drag_lock = tp_drag_lock_default(tp->device);
 
        snprintf(timer_name,
                 sizeof(timer_name),
index 26588dbb27a2ef0e6b0e8b8a5920580bb3969e5b..55547014390e29c092b9e4e6e87669e61d7f096e 100644 (file)
@@ -435,7 +435,7 @@ struct tp_dispatch {
                enum libinput_config_tap_button_map want_map;
 
                bool drag_enabled;
-               bool drag_lock_enabled;
+               enum libinput_config_drag_lock_state drag_lock;
 
                unsigned int nfingers_down;     /* number of fingers down for tapping (excl. thumb/palm) */
        } tap;
index 9ad2e808f7610549b07d270479f2cbccf91ca323..42a58d391c5bef39430543ac5c57603c41860849 100644 (file)
@@ -4067,7 +4067,8 @@ LIBINPUT_EXPORT enum libinput_config_status
 libinput_device_config_tap_set_drag_lock_enabled(struct libinput_device *device,
                                                 enum libinput_config_drag_lock_state enable)
 {
-       if (enable != LIBINPUT_CONFIG_DRAG_LOCK_ENABLED &&
+       if (enable != LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_STICKY &&
+           enable != LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_TIMEOUT &&
            enable != LIBINPUT_CONFIG_DRAG_LOCK_DISABLED)
                return LIBINPUT_CONFIG_STATUS_INVALID;
 
index 1a9d4f290d2555a96f7001422e054faecba3ad80..5365ff808a72eb888f44052970a3d9f254decf98 100644 (file)
@@ -4929,24 +4929,33 @@ libinput_device_config_tap_get_default_drag_enabled(struct libinput_device *devi
 enum libinput_config_drag_lock_state {
        /** Drag lock is to be disabled, or is currently disabled */
        LIBINPUT_CONFIG_DRAG_LOCK_DISABLED,
-       /** Drag lock is to be enabled, or is currently disabled */
-       LIBINPUT_CONFIG_DRAG_LOCK_ENABLED,
+       /** Drag lock is to be enabled in timeout mode,
+        *  or is currently enabled in timeout mode */
+       LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_TIMEOUT,
+       /** legacy spelling for LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_TIMEOUT */
+       LIBINPUT_CONFIG_DRAG_LOCK_ENABLED = LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_TIMEOUT,
+       /** Drag lock is to be enabled in sticky mode,
+        *  or is currently enabled in sticky mode */
+       LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_STICKY,
 };
 
 /**
  * @ingroup config
  *
  * Enable or disable drag-lock during tapping on this device. When enabled,
- * a finger may be lifted and put back on the touchpad within a timeout and
- * the drag process continues. When disabled, lifting the finger during a
- * tap-and-drag will immediately stop the drag. See the libinput
- * documentation for more details.
+ * a finger may be lifted and put back on the touchpad and the drag process
+ * continues. A timeout for lifting the finger is optional. When disabled,
+ * lifting the finger during a tap-and-drag will immediately stop the drag.
+ * See the libinput documentation for more details.
  *
- * Enabling drag lock on a device that has tapping disabled is permitted,
- * but has no effect until tapping is enabled.
+ * Enabling drag lock on a device that has tapping or tap-and-drag disabled is
+ * permitted, but has no effect until tapping and tap-and-drag are enabled.
  *
  * @param device The device to configure
- * @param enable @ref LIBINPUT_CONFIG_DRAG_LOCK_ENABLED to enable drag lock
+ * @param enable @ref LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_STICKY to enable drag
+ * lock in sticky mode,
+ * @ref LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_TIMEOUT to enable drag lock in timeout
+ * mode,
  * or @ref LIBINPUT_CONFIG_DRAG_LOCK_DISABLED to disable drag lock
  *
  * @return A config status code. Disabling drag lock on a device that does not
@@ -4966,11 +4975,14 @@ libinput_device_config_tap_set_drag_lock_enabled(struct libinput_device *device,
  * device does not support tapping, this function always returns
  * @ref LIBINPUT_CONFIG_DRAG_LOCK_DISABLED.
  *
- * Drag lock may be enabled even when tapping is disabled.
+ * Drag lock may be enabled even when tapping or tap-and-drag is disabled.
  *
  * @param device The device to configure
  *
- * @retval LIBINPUT_CONFIG_DRAG_LOCK_ENABLED If drag lock is currently enabled
+ * @retval LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_STICKY If drag lock is currently
+ * enabled in sticky mode
+ * @retval LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_TIMEOUT If drag lock is currently
+ * enabled in timeout mode
  * @retval LIBINPUT_CONFIG_DRAG_LOCK_DISABLED If drag lock is currently disabled
  *
  * @see libinput_device_config_tap_set_drag_lock_enabled
@@ -4986,13 +4998,15 @@ libinput_device_config_tap_get_drag_lock_enabled(struct libinput_device *device)
  * If the device does not support tapping, this function always returns
  * @ref LIBINPUT_CONFIG_DRAG_LOCK_DISABLED.
  *
- * Drag lock may be enabled by default even when tapping is disabled by
- * default.
+ * Drag lock may be enabled by default even when tapping or tap-and-drag is
+ * disabled by default.
  *
  * @param device The device to configure
  *
- * @retval LIBINPUT_CONFIG_DRAG_LOCK_ENABLED If drag lock is enabled by
- * default
+ * @retval LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_STICKY If drag lock is enabled in
+ * sticky mode by default
+ * @retval LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_TIMEOUT If drag lock is enabled in
+ * timeout mode by default
  * @retval LIBINPUT_CONFIG_DRAG_LOCK_DISABLED If drag lock is disabled by
  * default
  *
index 6108be50a4c190318b3847e6926dae0f4f1aeef7..f411c0acf189991e2ca456912c5b2142ea31b208 100644 (file)
@@ -1179,6 +1179,18 @@ litest_enable_buttonareas(struct litest_device *dev)
        litest_assert_int_eq(status, expected);
 }
 
+static inline void
+litest_enable_drag_lock_sticky(struct libinput_device *device)
+{
+       enum libinput_config_status status, expected;
+
+       expected = LIBINPUT_CONFIG_STATUS_SUCCESS;
+       status = libinput_device_config_tap_set_drag_lock_enabled(device,
+                                                                 LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_STICKY);
+
+       litest_assert_int_eq(status, expected);
+}
+
 static inline void
 litest_enable_drag_lock(struct libinput_device *device)
 {
@@ -1186,7 +1198,7 @@ litest_enable_drag_lock(struct libinput_device *device)
 
        expected = LIBINPUT_CONFIG_STATUS_SUCCESS;
        status = libinput_device_config_tap_set_drag_lock_enabled(device,
-                                                                 LIBINPUT_CONFIG_DRAG_LOCK_ENABLED);
+                                                                 LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_TIMEOUT);
 
        litest_assert_int_eq(status, expected);
 }
index f8651562088b74b627d160da1122cc525e2e432d..8cf539f947c1e03f4fe6650ce7ed90292c5b9e55 100644 (file)
@@ -1579,6 +1579,81 @@ START_TEST(touchpad_tap_n_drag_draglock_timeout)
 }
 END_TEST
 
+START_TEST(touchpad_tap_n_drag_draglock_sticky)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       int nfingers = _i; /* ranged test */
+       unsigned int button = 0;
+
+       if (nfingers > litest_slot_count(dev))
+               return;
+
+       litest_enable_tap(dev->libinput_device);
+       litest_enable_drag_lock_sticky(dev->libinput_device);
+       litest_disable_hold_gestures(dev->libinput_device);
+
+       switch (nfingers) {
+       case 1:
+               button = BTN_LEFT;
+               break;
+       case 2:
+               button = BTN_RIGHT;
+               break;
+       case 3:
+               button = BTN_MIDDLE;
+               break;
+       default:
+               abort();
+       }
+
+       litest_drain_events(li);
+
+       switch (nfingers) {
+       case 3:
+               litest_touch_down(dev, 2, 60, 30);
+               _fallthrough_;
+       case 2:
+               litest_touch_down(dev, 1, 50, 30);
+               _fallthrough_;
+       case 1:
+               litest_touch_down(dev, 0, 40, 30);
+               break;
+       }
+       switch (nfingers) {
+       case 3:
+               litest_touch_up(dev, 2);
+               _fallthrough_;
+       case 2:
+               litest_touch_up(dev, 1);
+               _fallthrough_;
+       case 1:
+               litest_touch_up(dev, 0);
+               break;
+       }
+       litest_touch_down(dev, 0, 50, 50);
+       libinput_dispatch(li);
+       litest_timeout_tap();
+
+       litest_assert_button_event(li, button,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+
+       litest_assert_empty_queue(li);
+       litest_touch_up(dev, 0);
+       libinput_dispatch(li);
+
+       litest_timeout_tapndrag();
+       litest_assert_empty_queue(li);
+
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_up(dev, 0);
+       litest_assert_button_event(li, button,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
 START_TEST(touchpad_tap_n_drag_2fg)
 {
        /* Test: tap with 1-3 fingers (multiple times), then a 1fg move
@@ -4133,14 +4208,27 @@ START_TEST(touchpad_drag_lock_default_disabled)
        status = libinput_device_config_tap_set_drag_lock_enabled(device,
                                                                  LIBINPUT_CONFIG_DRAG_LOCK_ENABLED);
        ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+       /* ENABLED is a legacy spelling for ENABLED_TIMEOUT */
+       ck_assert_int_eq(libinput_device_config_tap_get_drag_lock_enabled(device),
+                        LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_TIMEOUT);
 
        status = libinput_device_config_tap_set_drag_lock_enabled(device,
                                                                  LIBINPUT_CONFIG_DRAG_LOCK_DISABLED);
        ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+       ck_assert_int_eq(libinput_device_config_tap_get_drag_lock_enabled(device),
+                        LIBINPUT_CONFIG_DRAG_LOCK_DISABLED);
 
        status = libinput_device_config_tap_set_drag_lock_enabled(device,
-                                                                 LIBINPUT_CONFIG_DRAG_LOCK_ENABLED);
+                                                                 LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_TIMEOUT);
        ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+       ck_assert_int_eq(libinput_device_config_tap_get_drag_lock_enabled(device),
+                        LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_TIMEOUT);
+
+       status = libinput_device_config_tap_set_drag_lock_enabled(device,
+                                                                 LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_STICKY);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+       ck_assert_int_eq(libinput_device_config_tap_get_drag_lock_enabled(device),
+                        LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_STICKY);
 
        status = libinput_device_config_tap_set_drag_lock_enabled(device,
                                                                  3);
@@ -4163,6 +4251,14 @@ START_TEST(touchpad_drag_lock_default_unavailable)
                                                                  LIBINPUT_CONFIG_DRAG_LOCK_ENABLED);
        ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_UNSUPPORTED);
 
+       status = libinput_device_config_tap_set_drag_lock_enabled(device,
+                                                                 LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_TIMEOUT);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_UNSUPPORTED);
+
+       status = libinput_device_config_tap_set_drag_lock_enabled(device,
+                                                                 LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_STICKY);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_UNSUPPORTED);
+
        status = libinput_device_config_tap_set_drag_lock_enabled(device,
                                                                  LIBINPUT_CONFIG_DRAG_LOCK_DISABLED);
        ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
@@ -5658,6 +5754,7 @@ TEST_COLLECTION(touchpad_tap)
        litest_add_ranged(touchpad_tap_n_drag_draglock, LITEST_TOUCHPAD, LITEST_ANY, &range_multifinger_tap);
        litest_add_ranged(touchpad_tap_n_drag_draglock_tap, LITEST_TOUCHPAD, LITEST_ANY, &range_multifinger_doubletap);
        litest_add_ranged(touchpad_tap_n_drag_draglock_timeout, LITEST_TOUCHPAD, LITEST_ANY, &range_multifinger_tap);
+       litest_add_ranged(touchpad_tap_n_drag_draglock_sticky, LITEST_TOUCHPAD, LITEST_ANY, &range_multifinger_tap);
 
        /* Real buttons don't interfere with tapping, so don't run those for
           pads with buttons */
index e602edec1517974367f94fd38f1dfa4b4f679013..b8f0727f6adbe2f310f5f1a0168bb1edfa199a22 100644 (file)
@@ -64,8 +64,10 @@ Enable or disable tap-to-click
 .B \-\-enable-drag|\-\-disable\-drag
 Enable or disable tap-and-drag
 .TP 8
-.B \-\-enable\-drag-lock|\-\-disable\-drag\-lock
-Enable or disable drag-lock
+.B \-\-enable\-drag\-lock|\-\-disable\-drag\-lock
+Enable (in timeout mode) or disable drag-lock
+.B \-\-enable\-drag\-lock=[sticky|timeout]
+Enable drag-lock in sticky or timeout mode
 .TP 8
 .B \-\-enable\-natural\-scrolling|\-\-disable\-natural\-scrolling
 Enable or disable natural scrolling
index 25f9c43f815449845d960c09aab132b3f8d3a249..0fc2c454a5b3f661b56b952743d733bcc8a4b920 100644 (file)
@@ -158,10 +158,20 @@ tools_parse_option(int option,
                options->drag = 0;
                break;
        case OPT_DRAG_LOCK_ENABLE:
-               options->drag_lock = 1;
+               if (optarg) {
+                       if (streq(optarg, "sticky")) {
+                               options->drag_lock = LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_STICKY;
+                       } else if (streq(optarg, "timeout")) {
+                               options->drag_lock = LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_TIMEOUT;
+                       } else {
+                               return 1;
+                       }
+               } else {
+                       options->drag_lock = LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_TIMEOUT;
+               }
                break;
        case OPT_DRAG_LOCK_DISABLE:
-               options->drag_lock = 0;
+               options->drag_lock = LIBINPUT_CONFIG_DRAG_LOCK_DISABLED;
                break;
        case OPT_NATURAL_SCROLL_ENABLE:
                options->natural_scroll = 1;
index cedb3159942b9e8b81297032788827091935c371..8e874b52df2bdbec4f32885bc6002e13b2dcbb7f 100644 (file)
@@ -74,7 +74,7 @@ enum configuration_options {
        { "disable-tap",               no_argument,       0, OPT_TAP_DISABLE }, \
        { "enable-drag",               no_argument,       0, OPT_DRAG_ENABLE }, \
        { "disable-drag",              no_argument,       0, OPT_DRAG_DISABLE }, \
-       { "enable-drag-lock",          no_argument,       0, OPT_DRAG_LOCK_ENABLE }, \
+       { "enable-drag-lock",          optional_argument, 0, OPT_DRAG_LOCK_ENABLE }, \
        { "disable-drag-lock",         no_argument,       0, OPT_DRAG_LOCK_DISABLE }, \
        { "enable-natural-scrolling",  no_argument,       0, OPT_NATURAL_SCROLL_ENABLE }, \
        { "disable-natural-scrolling", no_argument,       0, OPT_NATURAL_SCROLL_DISABLE }, \
index 9c0f62875699b54e5b82345dc62c9b71a327bf9a..8f5a006ed993d5e20e73aee21961134da4302f65 100755 (executable)
@@ -219,6 +219,7 @@ options = {
         "set-profile": ["adaptive", "flat"],
         "set-tap-map": ["lrm", "lmr"],
         "set-clickfinger-map": ["lrm", "lmr"],
+        "enable-drag-lock": ["sticky", "timeout"],
     },
     # options with a range (and increment)
     "ranges": {