diff --git a/docs/user/configuration.md b/docs/user/configuration.md index ff991112..246b973d 100644 --- a/docs/user/configuration.md +++ b/docs/user/configuration.md @@ -438,6 +438,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, @@ -445,6 +447,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 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 ```toml @@ -492,6 +500,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" @@ -502,8 +512,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 c7856776..5e3b477f 100644 --- a/examples/config.toml +++ b/examples/config.toml @@ -148,6 +148,8 @@ numlock_toggle = true # true enables NumLock when a keyboard connects; false lea [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 @@ -156,12 +158,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 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 # 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 db1d4f5c..c0307cae 100644 --- a/src/config/config.cpp +++ b/src/config/config.cpp @@ -762,7 +762,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 e7f4af48..2b827b5c 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -405,6 +405,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 2a83d192..fce58b4f 100644 --- a/src/server/server_events.cpp +++ b/src/server/server_events.cpp @@ -165,7 +165,8 @@ namespace umbriel { void applyMouseAcceleration( libinput_device* libinputDevice, const wlr_input_device* device, - const std::optional& configuredProfile, double sensitivity + 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) { return; @@ -212,19 +213,25 @@ namespace umbriel { : 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)); + kLog.warn("input: failed to apply {} to '{}'", accelSetting, deviceName(device)); } 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)); + kLog.warn("input: failed to apply {} to '{}'", accelSetting, deviceName(device)); return; } } + 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) { - kLog.warn("input: failed to apply input.mouse.sensitivity to '{}'", deviceName(device)); + 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)); + } } } @@ -252,28 +259,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 std::optional& 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 ac49af05..871e7a69 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 24ac9aef..8f4d117e 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" @@ -564,6 +568,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"); @@ -580,6 +589,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"); @@ -600,6 +614,12 @@ UMBRIEL_TEST(mouseAccelerationPreservesDeviceProfileByDefault) { 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));