git: a79397d13228 - main - wsp: Handle horizontal scrolling and create tunable for swipe/scroll.

From: Vladimir Kondratyev <wulf_at_FreeBSD.org>
Date: Fri, 07 Mar 2025 17:55:22 UTC
The branch main has been updated by wulf:

URL: https://cgit.FreeBSD.org/src/commit/?id=a79397d13228b1e2cc00e4f18158966b3d8cac83

commit a79397d13228b1e2cc00e4f18158966b3d8cac83
Author:     Joshua Rogers <Joshua@Joshua.Hu>
AuthorDate: 2025-03-07 17:53:35 +0000
Commit:     Vladimir Kondratyev <wulf@FreeBSD.org>
CommitDate: 2025-03-07 17:53:35 +0000

    wsp: Handle horizontal scrolling and create tunable for swipe/scroll.
    
    Previously, a two-finger horizontal scroll would result in a forwards/backwards
    keyboard event being performed. This patch changes that functionality to be
    specified via two new sysctls: horizontal_swipe_finger_count and
    scroll_finger_count. The former specifies how many fingers are used to perform
    the aforementioned forwards/backwards keyboard event, while the latter specifies
    how many fingers are used to perform horizontal scrolling. 0 disables each of
    them.
    
    The threshold for scrolling has been coupled into a single tunable:
    scr_threshold. This tunable is used for both scrolling and the horizontal swipe.
    
    t_factor and t_invert tunables have been created in the same manner as their
    z-axis counterparts.
    
    Horizontal scrolling is disabled by default, as it requires the sysctl
    hw.usb.wsp.t_factor to 3 (wsp mode). Horizontal swiping is enabled by default
    with a three-finger tap.
    
    Also rewrite much of, and improve, documentation.
    
    Signed-off-by: Joshua Rogers <Joshua@Joshua.Hu>
---
 share/man/man4/wsp.4    | 158 ++++++++++++++++++++++++++++++++++++------------
 sys/dev/usb/input/wsp.c | 113 ++++++++++++++++++++++++----------
 2 files changed, 200 insertions(+), 71 deletions(-)

diff --git a/share/man/man4/wsp.4 b/share/man/man4/wsp.4
index b77d5ac99a7b..de2c121784d4 100644
--- a/share/man/man4/wsp.4
+++ b/share/man/man4/wsp.4
@@ -49,53 +49,131 @@ The
 driver provides support for the Apple Internal Trackpad
 device found in many Apple laptops.
 .Pp
-The driver simulates a three-button mouse using multi-finger tap
+The driver simulates a three-button mouse using multi-finger press/tap
 detection.
 A single-finger press generates a left button click.
-A two-finger tap maps to the right button; whereas a three-finger tap
-gets treated as a middle button click.
+A two-finger press maps to the right button; whereas a three-finger
+press gets treated as a middle button click.
 .Pp
+The trackpad functions with presses and taps. A press is a full-forced
+press which causes a physical lowering of the trackpad. A tap is a
+touch of the trackpad which does not depress the physical trackpad.
+.Pp
+The
 .Nm
-supports dynamic reconfiguration using
+driver supports receiving evdev input device data if enabled. This data
+is used for extended usage of the touchpad like multi-finger support,
+pressure detection, tap support, and gestures. At least the second bit
+of the
 .Xr sysctl 8
-through nodes under
-.Nm hw.usb.wsp .
-Pointer sensitivity can be controlled using the sysctl tunable
-.Nm hw.usb.wsp.scale_factor .
-Tap to left-click can be controlled using the sysctl tunable
-.Nm hw.usb.wsp.enable_single_tap_clicks
-with 0 disabling single tap clicks and 1 enabling them (default).
-Movement on the trackpad following a partially-released click can be
-controlled using the sysctl tunable
-.Nm hw.usb.wsp.enable_single_tap_movement ,
-with 0 to disable the movement on the trackpad until a full release
-or 1 to allow the continued movement (default).
-.Nm hw.usb.wsp.max_finger_area
-defines the maximum area on the trackpad which is registered as a
-finger (lower for greater palm detection).
-.Nm max_scroll_finger_distance
-defines the maximum distance between two fingers where z-axis
-movements are registered.
-.Nm hw.usb.wsp.max_double_tap_distance
-defines the maximum distance between two finger clicks or taps which may
-register as a double-click.
-.Nm hw.usb.wsp.scr_hor_threshold
-defines the minimum required horizontal movement to register as a forward
-/back button click.
-Z-Axis sensitivity can be controlled using the sysctl tunable
-.Nm hw.usb.wsp.z_factor .
-Z-Axis inversion can be controlled using the sysctl tunable
-.Nm hw.usb.wsp.z_invert ,
-set to 0 to disable (default) or 1 to enable inversion.
+tunable
+.Nm kern.evdev.rcpt_mask
+must be set. This can be enabled with
+.Nm kern.evdev.rcpt_mask=3 .
 .Pp
-.Nm
-may use evdev data (if enabled during kernel compilation) for gesture support
-using the
+Vertical scrolling (z-axis) is enabled by default with a two-finger
+tap and the movement of a finger up and down.
+Horizontal scrolling (t-axis) is not natively supported by the sysmouse
+protocol, therefore must be enabled with evdev data. This can be enabled
+with the
+.Xr sysctl 8
+tunable
+.Nm kern.evdev.sysmouse_t_axis=3.
+Horizontal scrolling can be used with a two-finger tap and the movement
+of a finger from side to side. The
 .Xr sysctl 8
-value
-.Nm kern.evdev.rcpt_mask .
-On most MacBooks, setting this value to 3 enables gestures, while 12
-disables them.
+tunable
+.Nm hw.usb.wsp.t_factor
+must be greater than 0 for horizontal scrolling to be enabled, too.
+.Pp
+Horizontal swiping with a three-finger tap is registered as mouse buttons
+8 and 9, depending on the direction. These buttons default to backwards
+and forwards keyboard events.
+.Sh SYSCTL VARIABLES
+The following variables are available as
+.Xr sysctl 8
+tunables:
+.Bl -tag -width indent
+.It Va hw.usb.wsp.scale_factor
+Controls the pointer sensitivity. Default is 12.
+.El
+.Bl -tag -width indent
+.It Va hw.usb.wsp.enable_single_tap_clicks
+Enables single-tap to register as a left-click. Default is 1 (enabled).
+.El
+.Bl -tag -width indent
+.It Va hw.usb.wsp.enable_single_tap_movement
+Enables movement on the trackpad follow a partially-released left-click.
+Default is 1 (enabled).
+.El
+.Bl -tag -width indent
+.It Va hw.usb.wsp.max_finger_area
+Specifies the maximum area on the trackpad which is registered as a
+finger (a lower value is used for palm detection). Default is 1900.
+.El
+.Bl -tag -width indent
+.It Va max_scroll_finger_distance
+Specifies the maximum distance between two fingers where z-axis
+and t-axis movements are registered. Z-axis and T-axis movements
+are vertical and horizontal movements with more than one finger
+tapped (not clicked), respectively. Default is 8192.
+.El
+.Bl -tag -width indent
+.It Va hw.usb.wsp.max_double_tap_distance
+Specifies the maximum distance between two fingers that a two-finger
+click will be registered as a right-click. Default is 2500.
+.El
+.Bl -tag -width indent
+.It Va hw.usb.wsp.scr_threshold
+Specifies the minimum horizontal or vertical distance required to
+register as a scrolling gesture. Default is 20.
+.El
+.Bl -tag -width indent
+.It Va hw.usb.wsp.z_factor
+Z-axis sensitivity. Default is 5.
+.El
+.Bl -tag -width indent
+.It Va hw.usb.wsp.z_invert
+Z-axis inversion. Default is 0 (disabled).
+.El
+.Bl -tag -width indent
+.It Va hw.usb.wsp.t_factor
+T-axis sensitivity. Default is 0 (disabled).
+.El
+.Bl -tag -width indent
+.It Va hw.usb.wsp.t_invert
+T-axis inversion. Default is 0 (disabled).
+.El
+.Bl -tag -width indent
+.It Va hw.usb.wsp.scroll_finger_count
+Specifies the number of tapped fingers which registers as a scrolling
+movement. Default is 2.
+.El
+.Bl -tag -width indent
+.It Va hw.usb.wsp.horizontal_swipe_finger_count
+Speifies the number of tapped fingers which registers as a swipe
+gesture. Default is 3.
+.El
+.Bl -tag -width indent
+.It Va hw.usb.wsp.pressure_touch_threshold
+Specifies the threshold for a finger to be registered as a click.
+Default is 50.
+.El
+.Bl -tag -width indent
+.It Va hw.usb.wsp.pressure_untouch_threshold
+Specifies the threshold for a finger to be registered as an unclick.
+Default is 10.
+.El
+.Bl -tag -width indent
+.It Va hw.usb.wsp.pressure_tap_threshold
+Specifies the threadhold for a finger to be registered as a tap.
+Default is 120.
+.El
+.Bl -tag -width indent
+.It Va hw.usb.wsp.debug
+Specifies the
+.Nm
+driver debugging level (0-3). Default is 1.
 .Sh FILES
 .Nm
 creates a blocking pseudo-device file,
diff --git a/sys/dev/usb/input/wsp.c b/sys/dev/usb/input/wsp.c
index 55b9aadfce86..55289aa40b17 100644
--- a/sys/dev/usb/input/wsp.c
+++ b/sys/dev/usb/input/wsp.c
@@ -86,17 +86,21 @@ enum wsp_log_level {
 static int wsp_debug = WSP_LLEVEL_ERROR;/* the default is to only log errors */
 
 SYSCTL_INT(_hw_usb_wsp, OID_AUTO, debug, CTLFLAG_RWTUN,
-    &wsp_debug, WSP_LLEVEL_ERROR, "WSP debug level");
+    &wsp_debug, WSP_LLEVEL_ERROR, "WSP debug level (0-3)");
 #endif					/* USB_DEBUG */
 
 static struct wsp_tuning {
 	int	scale_factor;
+	int	scroll_finger_count;
+	int	horizontal_swipe_finger_count;
 	int	z_factor;
 	int	z_invert;
+	int	t_factor;
+	int	t_invert;
 	int	pressure_touch_threshold;
 	int	pressure_untouch_threshold;
 	int	pressure_tap_threshold;
-	int	scr_hor_threshold;
+	int	scr_threshold;
 	int	max_finger_area;
 	int	max_scroll_finger_distance;
 	int	max_double_tap_distance;
@@ -106,12 +110,16 @@ static struct wsp_tuning {
 	wsp_tuning =
 {
 	.scale_factor = 12,
+	.scroll_finger_count = 2,
+	.horizontal_swipe_finger_count = 3,
 	.z_factor = 5,
 	.z_invert = 0,
+	.t_factor = 0,
+	.t_invert = 0,
 	.pressure_touch_threshold = 50,
 	.pressure_untouch_threshold = 10,
 	.pressure_tap_threshold = 120,
-	.scr_hor_threshold = 50,
+	.scr_threshold = 20,
 	.max_finger_area = 1900,
 	.max_scroll_finger_distance = MAX_FINGER_ORIENTATION/2,
 	.max_double_tap_distance = 2500,
@@ -123,25 +131,37 @@ static void
 wsp_running_rangecheck(struct wsp_tuning *ptun)
 {
 	WSP_CLAMP(ptun->scale_factor, 1, 63);
-	WSP_CLAMP(ptun->z_factor, 1, 63);
+	WSP_CLAMP(ptun->scroll_finger_count, 0, 3);
+	WSP_CLAMP(ptun->horizontal_swipe_finger_count, 0, 3);
+	WSP_CLAMP(ptun->z_factor, 0, 63);
 	WSP_CLAMP(ptun->z_invert, 0, 1);
+	WSP_CLAMP(ptun->t_factor, 0, 63);
+	WSP_CLAMP(ptun->t_invert, 0, 1);
 	WSP_CLAMP(ptun->pressure_touch_threshold, 1, 255);
 	WSP_CLAMP(ptun->pressure_untouch_threshold, 1, 255);
 	WSP_CLAMP(ptun->pressure_tap_threshold, 1, 255);
 	WSP_CLAMP(ptun->max_finger_area, 1, 2400);
 	WSP_CLAMP(ptun->max_scroll_finger_distance, 1, MAX_FINGER_ORIENTATION);
 	WSP_CLAMP(ptun->max_double_tap_distance, 1, MAX_FINGER_ORIENTATION);
-	WSP_CLAMP(ptun->scr_hor_threshold, 1, 255);
+	WSP_CLAMP(ptun->scr_threshold, 1, 255);
 	WSP_CLAMP(ptun->enable_single_tap_clicks, 0, 1);
 	WSP_CLAMP(ptun->enable_single_tap_movement, 0, 1);
 }
 
 SYSCTL_INT(_hw_usb_wsp, OID_AUTO, scale_factor, CTLFLAG_RWTUN,
     &wsp_tuning.scale_factor, 0, "movement scale factor");
+SYSCTL_INT(_hw_usb_wsp, OID_AUTO, scroll_finger_count, CTLFLAG_RWTUN,
+    &wsp_tuning.scroll_finger_count, 0, "amount of fingers to use scrolling gesture");
+SYSCTL_INT(_hw_usb_wsp, OID_AUTO, horizontal_swipe_finger_count, CTLFLAG_RWTUN,
+    &wsp_tuning.horizontal_swipe_finger_count, 0, "amount of fingers to use horizontal swipe gesture");
 SYSCTL_INT(_hw_usb_wsp, OID_AUTO, z_factor, CTLFLAG_RWTUN,
-    &wsp_tuning.z_factor, 0, "Z-axis scale factor");
+    &wsp_tuning.z_factor, 0, "Z-axis (vertical) scale factor");
 SYSCTL_INT(_hw_usb_wsp, OID_AUTO, z_invert, CTLFLAG_RWTUN,
-    &wsp_tuning.z_invert, 0, "enable Z-axis inversion");
+    &wsp_tuning.z_invert, 0, "enable (vertical) Z-axis inversion");
+SYSCTL_INT(_hw_usb_wsp, OID_AUTO, t_factor, CTLFLAG_RWTUN,
+    &wsp_tuning.t_factor, 0, "T-axis (horizontal) scale factor");
+SYSCTL_INT(_hw_usb_wsp, OID_AUTO, t_invert, CTLFLAG_RWTUN,
+    &wsp_tuning.t_invert, 0, "enable T-axis (horizontal) inversion");
 SYSCTL_INT(_hw_usb_wsp, OID_AUTO, pressure_touch_threshold, CTLFLAG_RWTUN,
     &wsp_tuning.pressure_touch_threshold, 0, "touch pressure threshold");
 SYSCTL_INT(_hw_usb_wsp, OID_AUTO, pressure_untouch_threshold, CTLFLAG_RWTUN,
@@ -154,8 +174,8 @@ SYSCTL_INT(_hw_usb_wsp, OID_AUTO, max_scroll_finger_distance, CTLFLAG_RWTUN,
     &wsp_tuning.max_scroll_finger_distance, 0, "maximum scroll finger distance");
 SYSCTL_INT(_hw_usb_wsp, OID_AUTO, max_double_tap_distance, CTLFLAG_RWTUN,
     &wsp_tuning.max_double_tap_distance, 0, "maximum double-finger click distance");
-SYSCTL_INT(_hw_usb_wsp, OID_AUTO, scr_hor_threshold, CTLFLAG_RWTUN,
-    &wsp_tuning.scr_hor_threshold, 0, "horizontal scrolling threshold");
+SYSCTL_INT(_hw_usb_wsp, OID_AUTO, scr_threshold, CTLFLAG_RWTUN,
+    &wsp_tuning.scr_threshold, 0, "scrolling threshold");
 SYSCTL_INT(_hw_usb_wsp, OID_AUTO, enable_single_tap_clicks, CTLFLAG_RWTUN,
     &wsp_tuning.enable_single_tap_clicks, 0, "enable single tap clicks");
 SYSCTL_INT(_hw_usb_wsp, OID_AUTO, enable_single_tap_movement, CTLFLAG_RWTUN,
@@ -1144,11 +1164,12 @@ wsp_intr_callback(struct usb_xfer *xfer, usb_error_t error)
 				}
 				wsp_add_to_queue(sc, 0, 0, 0, 0);	/* button release */
 			}
-			if ((sc->dt_sum / tun.scr_hor_threshold) != 0 &&
-			    sc->ntaps == 2 && sc->scr_mode == WSP_SCR_HOR) {
+
+			if (sc->scr_mode == WSP_SCR_HOR && sc->ntaps == tun.horizontal_swipe_finger_count
+			    && tun.horizontal_swipe_finger_count > 0 && (sc->dt_sum / tun.scr_threshold) != 0) {
 				/*
-				 * translate T-axis into button presses
-				 * until further
+				 * translate T-axis swipe into button
+				 * presses 3 and 4 (forward/back)
 				 */
 				if (sc->dt_sum > 0)
 					wsp_add_to_queue(sc, 0, 0, 0, 1UL << 3);
@@ -1234,11 +1255,18 @@ wsp_intr_callback(struct usb_xfer *xfer, usb_error_t error)
 					    dx, dy, sc->finger);
 				}
 				if (sc->dz_count--) {
-					rdz = (dy + sc->rdz) % tun.scale_factor;
-					sc->dz_sum -= (dy + sc->rdz) / tun.scale_factor;
+					if (sc->scr_mode == WSP_SCR_HOR) {
+						rdz = (dx + sc->rdz) % tun.scale_factor;
+						sc->dz_sum -= (dx + sc->rdz) / tun.scale_factor;
+					} else if (sc->scr_mode == WSP_SCR_VER) {
+						rdz = (dy + sc->rdz) % tun.scale_factor;
+						sc->dz_sum -= (dy + sc->rdz) / tun.scale_factor;
+					}
 					sc->rdz = rdz;
 				}
-				if ((sc->dz_sum / tun.z_factor) != 0)
+				if (sc->scr_mode == WSP_SCR_VER && (tun.z_factor == 0 || (sc->dz_sum / tun.z_factor) != 0))
+					sc->dz_count = 0;
+				else if (sc->scr_mode == WSP_SCR_HOR && (tun.t_factor == 0 || (sc->dz_sum / tun.t_factor) != 0))
 					sc->dz_count = 0;
 			}
 			rdx = (dx + sc->rdx) % tun.scale_factor;
@@ -1252,26 +1280,49 @@ wsp_intr_callback(struct usb_xfer *xfer, usb_error_t error)
 			sc->dx_sum += dx;
 			sc->dy_sum += dy;
 
-			if (ntouch == 2 && sc->sc_status.button == 0) {
-				if (sc->scr_mode == WSP_SCR_NONE &&
-				    abs(sc->dx_sum) + abs(sc->dy_sum) > tun.scr_hor_threshold)
-					sc->scr_mode = abs(sc->dx_sum) >
-					    abs(sc->dy_sum) * 2 ? WSP_SCR_HOR : WSP_SCR_VER;
-				DPRINTFN(WSP_LLEVEL_INFO, "scr_mode=%5d, count=%d, dx_sum=%d, dy_sum=%d\n",
-				    sc->scr_mode, sc->intr_count, sc->dx_sum, sc->dy_sum);
-				if (sc->scr_mode == WSP_SCR_HOR)
-					sc->dt_sum += dx;
-				else
-					sc->dt_sum = 0;
+			if (sc->sc_status.button == 0 && ntouch > 0) {
+				if (ntouch == tun.scroll_finger_count || ntouch == tun.horizontal_swipe_finger_count) {
+					if (sc->scr_mode == WSP_SCR_NONE && abs(sc->dx_sum) + abs(sc->dy_sum) > tun.scr_threshold)
+						sc->scr_mode = abs(sc->dx_sum) > abs(sc->dy_sum) * 2 ? WSP_SCR_HOR : WSP_SCR_VER;
 
-				dx = dy = 0;
-				if (sc->dz_count == 0)
-					dz = (sc->dz_sum / tun.z_factor) * (tun.z_invert ? -1 : 1);
-				if (sc->scr_mode == WSP_SCR_HOR || sc->distance > tun.max_scroll_finger_distance)
+					DPRINTFN(WSP_LLEVEL_INFO, "scr_mode=%5d, count=%d, dx_sum=%d, dy_sum=%d\n", sc->scr_mode, sc->intr_count, sc->dx_sum, sc->dy_sum);
+				}
+
+				if (ntouch == tun.scroll_finger_count) { /* preference scrolling over swipe if tun.scroll_finger_count == tun.horizontal_swipe_finger_count */
+					if (sc->scr_mode == WSP_SCR_HOR) {
+						sc->sc_status.button = 1 << 5;
+					}
+					dx = dy = dz = 0;
 					dz = 0;
+					sc->dt_sum = 0;
+					if (sc->distance <= tun.max_scroll_finger_distance && sc->dz_count == 0) {
+						if (sc->scr_mode == WSP_SCR_VER) {
+							if (tun.z_factor > 0)
+								dz = (sc->dz_sum / tun.z_factor) * (tun.z_invert ? -1 : 1);
+						} else if (sc->scr_mode == WSP_SCR_HOR) {
+							if (tun.t_factor > 0)
+								dz = (sc->dz_sum / tun.t_factor) * (tun.t_invert ? -1 : 1);
+						}
+					}
+				} else if (ntouch == tun.horizontal_swipe_finger_count) {
+					if (sc->scr_mode == WSP_SCR_HOR) {
+						sc->dt_sum += dx * (tun.t_invert ? -1 : 1);
+					} else {
+						sc->dt_sum = 0;
+					}
+					dx = dy = dz = 0;
+				}
 			}
+
 			if (ntouch == 3)
 				dx = dy = dz = 0;
+
+			if (ntouch != tun.horizontal_swipe_finger_count)
+				sc->dt_sum = 0;
+
+			if (ntouch == 0)
+				sc->scr_mode = WSP_SCR_NONE;
+
 			if (sc->intr_count < WSP_TAP_MAX_COUNT &&
 			    abs(dx) < 3 && abs(dy) < 3 && abs(dz) < 3)
 				dx = dy = dz = 0;