git.lucas.co / cce-compositor
Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git

commit4f157ea1d6c1b3b0a953c84add723dd20f49ed15
parentf9b55cbf15
authorIsaac Freund <[email protected]>
date2024-04-16 13:25
input: minor fixes and cleanups for scroll-factor

 doc/riverctl.1.scd    |  4 +++-
 river/Cursor.zig      | 12 ++++++++++--
 river/InputConfig.zig | 15 +++++++++------
 river/InputDevice.zig |  6 ++++--
 river/command.zig     |  3 ++-
 5 files changed, 28 insertions(+), 12 deletions(-)

diff --git a/doc/riverctl.1.scd b/doc/riverctl.1.scd
index 0fb809b..01b05a1 100644
--- a/doc/riverctl.1.scd
+++ b/doc/riverctl.1.scd
@@ -506,7 +506,9 @@ However note that not every input device supports every property.
 	active, the scroll direction is inverted.
 
 *input* _name_ *scroll-factor* _factor_
-	Set the scroll factor of the input device. Needs a non-negative float.
+	Set the scroll factor of the input device. Accepts a postive value
+	greater than 0. For example, a _factor_ of 0.5 will make scrolling twice
+	as slow while a _factor_ of 3 will make scrolling 3 times as fast.
 
 *input* _name_ *left-handed* *enabled*|*disabled*
 	Enable or disable the left handed mode of the input device.
diff --git a/river/Cursor.zig b/river/Cursor.zig
index 54e17ae..3b4e0a3 100644
--- a/river/Cursor.zig
+++ b/river/Cursor.zig
@@ -313,8 +313,16 @@ fn handleAxis(listener: *wl.Listener(*wlr.Pointer.event.Axis), event: *wlr.Point
     cursor.seat.wlr_seat.pointerNotifyAxis(
         event.time_msec,
         event.orientation,
-        event.delta * device.scroll_factor,
-        @intFromFloat(@as(f32, @floatFromInt(event.delta_discrete)) * device.scroll_factor),
+        event.delta * device.config.scroll_factor,
+        @intFromFloat(math.clamp(
+            @round(@as(f32, @floatFromInt(event.delta_discrete)) * device.config.scroll_factor),
+            // It seems that clamping to exactly the bounds of an i32 is insufficient to make the
+            // @intFromFloat() call safe due to the max/min i32 not being exactly representable
+            // by an f32. Dividing by 2 is a low effort way to ensure the value is in bounds and
+            // allow users to set their scroll-factor to inf without crashing river.
+            math.minInt(i32) / 2,
+            math.maxInt(i32) / 2,
+        )),
         event.source,
     );
 }
diff --git a/river/InputConfig.zig b/river/InputConfig.zig
index 4e01f21..09250d7 100644
--- a/river/InputConfig.zig
+++ b/river/InputConfig.zig
@@ -253,10 +253,10 @@ pub const MapToOutput = struct {
 };
 
 pub const ScrollFactor = struct {
-    value: ?f32,
+    value: ?f32 = null,
 
     fn apply(scroll_factor: ScrollFactor, device: *InputDevice) void {
-        device.scroll_factor = scroll_factor.value orelse 1.0;
+        device.config.scroll_factor = scroll_factor.value orelse 1.0;
     }
 };
 
@@ -272,7 +272,7 @@ drag: ?DragState = null,
 @"disable-while-trackpointing": ?DwtpState = null,
 @"middle-emulation": ?MiddleEmulation = null,
 @"natural-scroll": ?NaturalScroll = null,
-@"scroll-factor": ScrollFactor = .{ .value = 1.0 },
+@"scroll-factor": ScrollFactor = .{},
 @"left-handed": ?LeftHanded = null,
 tap: ?TapState = null,
 @"tap-button-map": ?TapButtonMap = null,
@@ -317,9 +317,12 @@ pub fn parse(config: *InputConfig, setting: []const u8, value: []const u8) !void
                     .value = math.clamp(try std.fmt.parseFloat(f32, value), -1.0, 1.0),
                 };
             } else if (comptime mem.eql(u8, field.name, "scroll-factor")) {
-                config.@"scroll-factor" = ScrollFactor{
-                    .value = @max(try std.fmt.parseFloat(f32, value), 0.0),
-                };
+                const unvalidated = try std.fmt.parseFloat(f32, value);
+                if (unvalidated > 0) {
+                    config.@"scroll-factor" = ScrollFactor{ .value = unvalidated };
+                } else {
+                    return error.OutOfBounds;
+                }
             } else if (comptime mem.eql(u8, field.name, "scroll-button")) {
                 const ret = c.libevdev_event_code_from_name(c.EV_KEY, value.ptr);
                 if (ret < 1) return error.InvalidButton;
diff --git a/river/InputDevice.zig b/river/InputDevice.zig
index 90e7bc2..685a671 100644
--- a/river/InputDevice.zig
+++ b/river/InputDevice.zig
@@ -34,7 +34,6 @@ const Tablet = @import("Tablet.zig");
 
 const log = std.log.scoped(.input_manager);
 
-scroll_factor: f32,
 seat: *Seat,
 wlr_device: *wlr.InputDevice,
 
@@ -45,6 +44,10 @@ destroy: wl.Listener(*wlr.InputDevice) = wl.Listener(*wlr.InputDevice).init(hand
 /// and name. However identifiers of InputConfigs are unique.
 identifier: []const u8,
 
+config: struct {
+    scroll_factor: f32 = 1.0,
+} = .{},
+
 /// InputManager.devices
 link: wl.list.Link,
 
@@ -74,7 +77,6 @@ pub fn init(device: *InputDevice, seat: *Seat, wlr_device: *wlr.InputDevice) !vo
     }
 
     device.* = .{
-        .scroll_factor = 1.0,
         .seat = seat,
         .wlr_device = wlr_device,
         .identifier = identifier,
diff --git a/river/command.zig b/river/command.zig
index ad8d21f..d32efce 100644
--- a/river/command.zig
+++ b/river/command.zig
@@ -105,6 +105,7 @@ pub const Error = error{
     UnknownCommand,
     NotEnoughArguments,
     TooManyArguments,
+    OutOfBounds,
     Overflow,
     InvalidButton,
     InvalidCharacter,
@@ -150,7 +151,7 @@ pub fn errToMsg(err: Error) [:0]const u8 {
         Error.ConflictingOptions => "options conflict",
         Error.NotEnoughArguments => "not enough arguments",
         Error.TooManyArguments => "too many arguments",
-        Error.Overflow => "value out of bounds",
+        Error.OutOfBounds, Error.Overflow => "value out of bounds",
         Error.InvalidButton => "invalid button",
         Error.InvalidCharacter => "invalid character in argument",
         Error.InvalidDirection => "invalid direction. Must be 'next' or 'previous'",