From e0ba7982672b47f27b380c926ae11dbd2c77d5e7 Mon Sep 17 00:00:00 2001 From: AdityaKr015 Date: Mon, 24 Aug 2026 11:07:38 +0530 Subject: [PATCH 1/2] feat(input): add touchpad acceleration and sensitivity --- docs/user/configuration.md | 15 +++- examples/config.toml | 9 ++- src/config/config.cpp | 5 +- src/config/config.h | 2 + src/server/server_events.cpp | 131 +++++++++++++++++++---------------- tests/unit/config_change.cpp | 13 ++++ tests/unit/config_load.cpp | 20 ++++++ 7 files changed, 132 insertions(+), 63 deletions(-) diff --git a/docs/user/configuration.md b/docs/user/configuration.md index 68a41545..fca18797 100644 --- a/docs/user/configuration.md +++ b/docs/user/configuration.md @@ -437,6 +437,8 @@ log and the whole keyboard block falls back to the system default. [input.touchpad] tap = true natural_scroll = true +# accel_profile = "adaptive" # "flat", "adaptive", or a custom curve +# sensitivity = 0.5 # -1.0 to 1.0 ``` Tap-to-click is enabled by default. Set `tap = false` to disable it globally, @@ -444,6 +446,12 @@ or use a per-device override below. `natural_scroll` remains unset by default, which preserves each device's libinput setting. Options are applied only when supported by the device. +`accel_profile` and `sensitivity` work like their `[input.mouse]` counterparts, +including custom curves. Both remain unset by default, which leaves libinput's +own acceleration behavior for touchpads untouched; setting either one takes +control from the device defaults. `sensitivity` alone adjusts pointer speed +under the device's current profile. + ### Mouse ```toml @@ -489,6 +497,8 @@ repeat_delay = 250 name = "Acme Precision Touchpad" tap = true natural_scroll = false +accel_profile = "flat" +sensitivity = 0.0 [[input.device]] name = "Acme Gaming Mouse" @@ -499,8 +509,9 @@ sensitivity = 0.0 Each rule inherits the matching class settings and overrides only the keys it contains. `layout`, `variant`, `options`, `repeat_rate`, and `repeat_delay` apply to keyboards. `tap` applies to touchpads. `natural_scroll` applies to -touchpads and mice. `accel_profile` and `sensitivity` apply to mice. -Unsupported libinput settings are reported in the log. +touchpads and mice. `accel_profile` and `sensitivity` apply to mice and +touchpads; for a touchpad the rule overrides `[input.touchpad]` rather than +`[input.mouse]`. Unsupported libinput settings are reported in the log. Rules match every attached device with the exact name. Device overrides also apply when a device is connected after startup and when the configuration is diff --git a/examples/config.toml b/examples/config.toml index 96c2d290..b751e06e 100644 --- a/examples/config.toml +++ b/examples/config.toml @@ -147,6 +147,8 @@ repeat_delay = 600 # 0-10000 ms [input.touchpad] tap = true # enabled by default; false disables tap-to-click # natural_scroll = true +# accel_profile = "adaptive" # "flat", "adaptive", or "custom " +# sensitivity = 0.5 # pointer speed, -1.0 to 1.0 [input.mouse] # natural_scroll = false @@ -155,12 +157,15 @@ sensitivity = 0.0 # pointer speed, -1.0 to 1.0 scroll_wheel_step = 60 # 1-1000 # Override only the listed settings for an exact, case-sensitive device name. # Applies per device kind: layout, variant, options, repeat_rate, and -# repeat_delay to keyboards; tap to touchpads; natural_scroll to touchpads -# and mice; acceleration settings to mice. Find names with `libinput list-devices`. +# repeat_delay to keyboards: tap, acceleration, and sensitivity to touchpads. +# natural_scroll to touchpads and mice, acceleration settings to mice. Find +# names with `libinput list-devices`. # [[input.device]] # name = "Acme Precision Touchpad" # tap = true # natural_scroll = false +# accel_profile = "flat" +# sensitivity = 0.0 # [[input.device]] # name = "Gaming Mouse" # accel_profile = "flat" diff --git a/src/config/config.cpp b/src/config/config.cpp index 65159cd6..3f8cb347 100644 --- a/src/config/config.cpp +++ b/src/config/config.cpp @@ -761,7 +761,10 @@ namespace umbriel { in.keyboard.options.clear(); } s.sub("touchpad", [&](Section& t) { - t.boolean("tap", in.touchpad.tap).boolean("natural_scroll", in.touchpad.naturalScroll); + t.boolean("tap", in.touchpad.tap) + .boolean("natural_scroll", in.touchpad.naturalScroll) + .real("sensitivity", -1.0, 1.0, in.touchpad.sensitivity); + in.touchpad.accelProfile = readAccelProfile(t, "accel_profile", "input.touchpad"); }); s.sub("mouse", [&](Section& m) { m.boolean("natural_scroll", in.mouse.naturalScroll) diff --git a/src/config/config.h b/src/config/config.h index 876f25cc..ea47c40d 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -404,6 +404,8 @@ namespace umbriel { struct Touchpad { std::optional tap = true; std::optional naturalScroll; + std::optional accelProfile; + std::optional sensitivity; bool operator==(const Touchpad&) const = default; } touchpad; diff --git a/src/server/server_events.cpp b/src/server/server_events.cpp index df5cb0ba..eb978eb6 100644 --- a/src/server/server_events.cpp +++ b/src/server/server_events.cpp @@ -164,59 +164,67 @@ namespace umbriel { } void applyMouseAcceleration( - libinput_device* libinputDevice, const wlr_input_device* device, const AccelProfile& configuredProfile, - double sensitivity + libinput_device* libinputDevice, const wlr_input_device* device, + const std::optional& configuredProfile, const std::optional& configuredSensitivity, + std::string_view accelSetting, std::string_view sensitivitySetting ) { - if (libinput_device_config_accel_is_available(libinputDevice) == 0) { + if (!configuredProfile && !configuredSensitivity) { return; } - - enum libinput_config_accel_profile profile = LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT; - const char* profileName = "flat"; - switch (configuredProfile.kind) { - case AccelProfile::Kind::Flat: - break; - case AccelProfile::Kind::Adaptive: - profile = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE; - profileName = "adaptive"; - break; - case AccelProfile::Kind::Custom: - profile = LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM; - profileName = "custom"; - break; - } - if ((libinput_device_config_accel_get_profiles(libinputDevice) & profile) == 0) { - kLog.warn("input: '{}' does not support the {} acceleration profile", deviceName(device), profileName); + if (libinput_device_config_accel_is_available(libinputDevice) == 0) { return; } - if (configuredProfile.kind == AccelProfile::Kind::Custom) { - libinput_config_accel* acceleration = libinput_config_accel_create(profile); - if (acceleration == nullptr) { - kLog.warn("input: failed to create custom acceleration profile for '{}'", deviceName(device)); + if (configuredProfile) { + enum libinput_config_accel_profile profile = LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT; + const char* profileName = "flat"; + switch (configuredProfile->kind) { + case AccelProfile::Kind::Flat: + break; + case AccelProfile::Kind::Adaptive: + profile = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE; + profileName = "adaptive"; + break; + case AccelProfile::Kind::Custom: + profile = LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM; + profileName = "custom"; + break; + } + if ((libinput_device_config_accel_get_profiles(libinputDevice) & profile) == 0) { + kLog.warn("input: '{}' does not support the {} acceleration profile", deviceName(device), profileName); return; } - const auto pointsStatus = libinput_config_accel_set_points( - acceleration, LIBINPUT_ACCEL_TYPE_MOTION, configuredProfile.step, configuredProfile.points.size(), - configuredProfile.points.data() - ); - const auto applyStatus = pointsStatus == LIBINPUT_CONFIG_STATUS_SUCCESS - ? libinput_device_config_accel_apply(libinputDevice, acceleration) - : pointsStatus; - libinput_config_accel_destroy(acceleration); - if (applyStatus != LIBINPUT_CONFIG_STATUS_SUCCESS) { - kLog.warn("input: failed to apply input.mouse.accel_profile to '{}'", deviceName(device)); + + if (configuredProfile->kind == AccelProfile::Kind::Custom) { + libinput_config_accel* acceleration = libinput_config_accel_create(profile); + if (acceleration == nullptr) { + kLog.warn("input: failed to create custom acceleration profile for '{}'", deviceName(device)); + return; + } + const auto pointsStatus = libinput_config_accel_set_points( + acceleration, LIBINPUT_ACCEL_TYPE_MOTION, configuredProfile->step, configuredProfile->points.size(), + configuredProfile->points.data() + ); + const auto applyStatus = pointsStatus == LIBINPUT_CONFIG_STATUS_SUCCESS + ? libinput_device_config_accel_apply(libinputDevice, acceleration) + : pointsStatus; + libinput_config_accel_destroy(acceleration); + if (applyStatus != LIBINPUT_CONFIG_STATUS_SUCCESS) { + kLog.warn("input: failed to apply {} to '{}'", accelSetting, deviceName(device)); + } + return; } - return; - } - if (libinput_device_config_accel_set_profile(libinputDevice, profile) != LIBINPUT_CONFIG_STATUS_SUCCESS) { - kLog.warn("input: failed to apply input.mouse.accel_profile to '{}'", deviceName(device)); - return; + if (libinput_device_config_accel_set_profile(libinputDevice, profile) != LIBINPUT_CONFIG_STATUS_SUCCESS) { + kLog.warn("input: failed to apply {} to '{}'", accelSetting, deviceName(device)); + return; + } } - if (libinput_device_config_accel_set_speed(libinputDevice, sensitivity) != LIBINPUT_CONFIG_STATUS_SUCCESS) { - kLog.warn("input: failed to apply input.mouse.sensitivity to '{}'", deviceName(device)); + if (configuredSensitivity + && libinput_device_config_accel_set_speed(libinputDevice, *configuredSensitivity) + != LIBINPUT_CONFIG_STATUS_SUCCESS) { + kLog.warn("input: failed to apply {} to '{}'", sensitivitySetting, deviceName(device)); } } @@ -244,28 +252,35 @@ namespace umbriel { override != nullptr && override->tap ? "input.device.tap" : "input.touchpad.tap", deviceName(device) ); } - - const std::optional& naturalScroll = - override != nullptr && override->naturalScroll ? override->naturalScroll : input.touchpad.naturalScroll; - applyNaturalScroll( - libinputDevice, device, naturalScroll, - override != nullptr && override->naturalScroll ? "input.device.natural_scroll" - : "input.touchpad.natural_scroll" - ); - return; } - const std::optional& naturalScroll = - override != nullptr && override->naturalScroll ? override->naturalScroll : input.mouse.naturalScroll; + const std::optional& naturalScroll = override != nullptr && override->naturalScroll + ? override->naturalScroll + : isTouchpad ? input.touchpad.naturalScroll + : input.mouse.naturalScroll; applyNaturalScroll( libinputDevice, device, naturalScroll, - override != nullptr && override->naturalScroll ? "input.device.natural_scroll" : "input.mouse.natural_scroll" + override != nullptr && override->naturalScroll ? "input.device.natural_scroll" + : isTouchpad ? "input.touchpad.natural_scroll" + : "input.mouse.natural_scroll" + ); + + const std::optional accelProfile = override != nullptr && override->accelProfile + ? override->accelProfile + : isTouchpad ? input.touchpad.accelProfile + : std::optional{input.mouse.accelProfile}; + const std::optional sensitivity = override != nullptr && override->sensitivity ? override->sensitivity + : isTouchpad ? input.touchpad.sensitivity + : std::optional{input.mouse.sensitivity}; + applyMouseAcceleration( + libinputDevice, device, accelProfile, sensitivity, + override != nullptr && override->accelProfile ? "input.device.accel_profile" + : isTouchpad ? "input.touchpad.accel_profile" + : "input.mouse.accel_profile", + override != nullptr && override->sensitivity ? "input.device.sensitivity" + : isTouchpad ? "input.touchpad.sensitivity" + : "input.mouse.sensitivity" ); - const AccelProfile& accelProfile = - override != nullptr && override->accelProfile ? *override->accelProfile : input.mouse.accelProfile; - const double sensitivity = - override != nullptr && override->sensitivity ? *override->sensitivity : input.mouse.sensitivity; - applyMouseAcceleration(libinputDevice, device, accelProfile, sensitivity); } } // namespace void Server::applyConfig(const ConfigEffects& effects) { diff --git a/tests/unit/config_change.cpp b/tests/unit/config_change.cpp index 0eef24ca..dfe795be 100644 --- a/tests/unit/config_change.cpp +++ b/tests/unit/config_change.cpp @@ -64,6 +64,19 @@ UMBRIEL_TEST(eachSectionIsReportedOnItsOwn) { CHECK(change.input); CHECK(!change.appearance); } + { + Config after; + after.input.touchpad.accelProfile.emplace(); + after.input.touchpad.accelProfile->kind = AccelProfile::Kind::Flat; + const ConfigChange change = ConfigChange::between(before, after); + CHECK(change.input); + CHECK(ConfigEffects::between(before, after).input); + } + { + Config after; + after.input.touchpad.sensitivity = 0.5; + CHECK(ConfigChange::between(before, after).input); + } { Config after; after.input.middleClickPaste = !after.input.middleClickPaste; diff --git a/tests/unit/config_load.cpp b/tests/unit/config_load.cpp index 29b60f92..851b50f9 100644 --- a/tests/unit/config_load.cpp +++ b/tests/unit/config_load.cpp @@ -530,6 +530,8 @@ repeat_rate = 25 [input.touchpad] tap = true natural_scroll = true +accel_profile = "adaptive" +sensitivity = 0.1 [input.mouse] accel_profile = "custom 0.2 0.0 0.5 1.0 2.0" @@ -546,6 +548,8 @@ repeat_delay = 250 name = "Acme Precision Touchpad" tap = false natural_scroll = false +accel_profile = "flat" +sensitivity = -0.5 [[input.device]] name = "Acme Gaming Mouse" @@ -563,6 +567,11 @@ sensitivity = -0.5 CHECK_EQ(input.mouse.accelProfile.step, 0.2); CHECK_EQ(input.mouse.accelProfile.points, std::vector({0.0, 0.5, 1.0, 2.0})); CHECK_EQ(input.mouse.sensitivity, 0.25); + CHECK(input.touchpad.accelProfile.has_value()); + if (input.touchpad.accelProfile.has_value()) { + CHECK(input.touchpad.accelProfile->kind == umbriel::AccelProfile::Kind::Adaptive); + } + CHECK(input.touchpad.sensitivity == std::optional(0.1)); CHECK_EQ(input.devices.size(), size_t{3}); const auto* keyboard = input.findDevice("Acme Split Keyboard"); @@ -579,6 +588,11 @@ sensitivity = -0.5 if (touchpad != nullptr) { CHECK(touchpad->tap == std::optional(false)); CHECK(touchpad->naturalScroll == std::optional(false)); + CHECK(touchpad->accelProfile.has_value()); + if (touchpad->accelProfile.has_value()) { + CHECK(touchpad->accelProfile->kind == umbriel::AccelProfile::Kind::Flat); + } + CHECK(touchpad->sensitivity == std::optional(-0.5)); } const auto* mouse = input.findDevice("Acme Gaming Mouse"); @@ -599,6 +613,12 @@ UMBRIEL_TEST(mouseAccelerationDefaultsToFlat) { CHECK_EQ(defaults.input.mouse.sensitivity, 0.0); } +UMBRIEL_TEST(touchpadAccelerationDefaultsToUnset) { + const umbriel::Config defaults; + CHECK(!defaults.input.touchpad.accelProfile.has_value()); + CHECK(!defaults.input.touchpad.sensitivity.has_value()); +} + UMBRIEL_TEST(touchpadTapDefaultsToEnabled) { const umbriel::Config defaults; CHECK(defaults.input.touchpad.tap == std::optional(true)); From bf98def7fc95bac096a10f6728043d7030a6be51 Mon Sep 17 00:00:00 2001 From: Lemmy Date: Tue, 25 Aug 2026 23:42:28 -0400 Subject: [PATCH 2/2] fix(input): restore touchpad acceleration defaults --- docs/user/configuration.md | 8 ++++---- examples/config.toml | 6 +++--- src/server/server_events.cpp | 15 ++++++++------- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/docs/user/configuration.md b/docs/user/configuration.md index b51eafb6..246b973d 100644 --- a/docs/user/configuration.md +++ b/docs/user/configuration.md @@ -448,10 +448,10 @@ which preserves each device's libinput setting. Options are applied only when supported by the device. `accel_profile` and `sensitivity` work like their `[input.mouse]` counterparts, -including custom curves. Both remain unset by default, which leaves libinput's -own acceleration behavior for touchpads untouched; setting either one takes -control from the device defaults. `sensitivity` alone adjusts pointer speed -under the device's current profile. +including custom curves. Both remain unset by default, which uses each +touchpad's libinput default profile and speed. Removing either setting on reload +restores the corresponding default. `sensitivity` alone adjusts pointer speed +under the device's default profile. ### Mouse diff --git a/examples/config.toml b/examples/config.toml index d49c4a0c..5e3b477f 100644 --- a/examples/config.toml +++ b/examples/config.toml @@ -158,9 +158,9 @@ sensitivity = 0.0 # pointer speed, -1.0 to 1.0 scroll_wheel_step = 60 # 1-1000 # Override only the listed settings for an exact, case-sensitive device name. # Applies per device kind: layout, variant, options, repeat_rate, and -# repeat_delay to keyboards: tap, acceleration, and sensitivity to touchpads. -# natural_scroll to touchpads and mice, acceleration settings to mice. Find -# names with `libinput list-devices`. +# repeat_delay to keyboards; tap to touchpads; natural_scroll, accel_profile, +# and sensitivity to touchpads and mice. Find names with +# `libinput list-devices`. # [[input.device]] # name = "Acme Precision Touchpad" # tap = true diff --git a/src/server/server_events.cpp b/src/server/server_events.cpp index a981f772..fce58b4f 100644 --- a/src/server/server_events.cpp +++ b/src/server/server_events.cpp @@ -168,9 +168,6 @@ namespace umbriel { const std::optional& configuredProfile, const std::optional& configuredSensitivity, std::string_view accelSetting, std::string_view sensitivitySetting ) { - if (!configuredProfile && !configuredSensitivity) { - return; - } if (libinput_device_config_accel_is_available(libinputDevice) == 0) { return; } @@ -227,10 +224,14 @@ namespace umbriel { } } - if (configuredSensitivity - && libinput_device_config_accel_set_speed(libinputDevice, *configuredSensitivity) - != LIBINPUT_CONFIG_STATUS_SUCCESS) { - kLog.warn("input: failed to apply {} to '{}'", sensitivitySetting, deviceName(device)); + const double sensitivity = + configuredSensitivity.value_or(libinput_device_config_accel_get_default_speed(libinputDevice)); + if (libinput_device_config_accel_set_speed(libinputDevice, sensitivity) != LIBINPUT_CONFIG_STATUS_SUCCESS) { + if (configuredSensitivity) { + kLog.warn("input: failed to apply {} to '{}'", sensitivitySetting, deviceName(device)); + } else { + kLog.warn("input: failed to restore the default acceleration speed for '{}'", deviceName(device)); + } } }