Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions docs/user/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -438,13 +438,21 @@ 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,
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
Expand Down Expand Up @@ -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"
Expand All @@ -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
Expand Down
9 changes: 7 additions & 2 deletions examples/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 <step> <points...>"
# sensitivity = 0.5 # pointer speed, -1.0 to 1.0

[input.mouse]
# natural_scroll = false
Expand All @@ -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"
Expand Down
5 changes: 4 additions & 1 deletion src/config/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions src/config/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,8 @@ namespace umbriel {
struct Touchpad {
std::optional<bool> tap = true;
std::optional<bool> naturalScroll;
std::optional<AccelProfile> accelProfile;
std::optional<double> sensitivity;
bool operator==(const Touchpad&) const = default;
} touchpad;

Expand Down
56 changes: 35 additions & 21 deletions src/server/server_events.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,8 @@ namespace umbriel {

void applyMouseAcceleration(
libinput_device* libinputDevice, const wlr_input_device* device,
const std::optional<AccelProfile>& configuredProfile, double sensitivity
const std::optional<AccelProfile>& configuredProfile, const std::optional<double>& configuredSensitivity,
std::string_view accelSetting, std::string_view sensitivitySetting
) {
if (libinput_device_config_accel_is_available(libinputDevice) == 0) {
return;
Expand Down Expand Up @@ -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));
}
}
}

Expand Down Expand Up @@ -252,28 +259,35 @@ namespace umbriel {
override != nullptr && override->tap ? "input.device.tap" : "input.touchpad.tap", deviceName(device)
);
}

const std::optional<bool>& 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<bool>& naturalScroll =
override != nullptr && override->naturalScroll ? override->naturalScroll : input.mouse.naturalScroll;
const std::optional<bool>& 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> accelProfile = override != nullptr && override->accelProfile
? override->accelProfile
: isTouchpad ? input.touchpad.accelProfile
: std::optional{input.mouse.accelProfile};
const std::optional<double> 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>& 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) {
Expand Down
13 changes: 13 additions & 0 deletions tests/unit/config_change.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
20 changes: 20 additions & 0 deletions tests/unit/config_load.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand All @@ -564,6 +568,11 @@ sensitivity = -0.5
CHECK_EQ(input.mouse.accelProfile->step, 0.2);
CHECK_EQ(input.mouse.accelProfile->points, std::vector<double>({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<double>(0.1));
CHECK_EQ(input.devices.size(), size_t{3});

const auto* keyboard = input.findDevice("Acme Split Keyboard");
Expand All @@ -580,6 +589,11 @@ sensitivity = -0.5
if (touchpad != nullptr) {
CHECK(touchpad->tap == std::optional<bool>(false));
CHECK(touchpad->naturalScroll == std::optional<bool>(false));
CHECK(touchpad->accelProfile.has_value());
if (touchpad->accelProfile.has_value()) {
CHECK(touchpad->accelProfile->kind == umbriel::AccelProfile::Kind::Flat);
}
CHECK(touchpad->sensitivity == std::optional<double>(-0.5));
}

const auto* mouse = input.findDevice("Acme Gaming Mouse");
Expand All @@ -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<bool>(true));
Expand Down