diff --git a/doc/riverctl.1.scd b/doc/riverctl.1.scd index 1f0c4a2..68e1159 100644 --- a/doc/riverctl.1.scd +++ b/doc/riverctl.1.scd @@ -465,6 +465,8 @@ The _input_ command can be used to create a configuration rule for an input device identified by its _name_. The _name_ of an input device consists of its type, its numerical vendor id, its numerical product id and finally its self-advertised name, separated by -. +Simple globbing patterns are supported, see the rules section for further +information on globs. A list of all device properties that can be configured may be found below. However note that not every input device supports every property. diff --git a/river/InputConfig.zig b/river/InputConfig.zig index cf1e478..7381817 100644 --- a/river/InputConfig.zig +++ b/river/InputConfig.zig @@ -262,7 +262,7 @@ pub const ScrollButton = struct { } }; -identifier: []const u8, +glob: []const u8, // Note: Field names equal name of the setting in the 'input' command. events: ?EventState = null, @@ -281,15 +281,15 @@ tap: ?TapState = null, @"scroll-button": ?ScrollButton = null, pub fn deinit(self: *Self) void { - util.gpa.free(self.identifier); + util.gpa.free(self.glob); } -pub fn apply(self: *Self, device: *InputDevice) void { +pub fn apply(self: *const Self, device: *InputDevice) void { const libinput_device: *c.libinput_device = @ptrCast(device.wlr_device.getLibinputDevice() orelse return); - log.debug("applying input configuration to device: {s}", .{device.identifier}); + log.debug("applying input configuration '{s}' to device '{s}'.", .{ self.glob, device.identifier }); inline for (@typeInfo(Self).Struct.fields) |field| { - if (comptime mem.eql(u8, field.name, "identifier")) continue; + if (comptime mem.eql(u8, field.name, "glob")) continue; if (@field(self, field.name)) |setting| { log.debug("applying setting: {s}", .{field.name}); @@ -300,7 +300,7 @@ pub fn apply(self: *Self, device: *InputDevice) void { pub fn parse(self: *Self, setting: []const u8, value: []const u8) !void { inline for (@typeInfo(Self).Struct.fields) |field| { - if (comptime mem.eql(u8, field.name, "identifier")) continue; + if (comptime mem.eql(u8, field.name, "glob")) continue; if (mem.eql(u8, setting, field.name)) { // Special-case the settings which are not enums. @@ -329,10 +329,10 @@ pub fn parse(self: *Self, setting: []const u8, value: []const u8) !void { } pub fn write(self: *Self, writer: anytype) !void { - try writer.print("{s}\n", .{self.identifier}); + try writer.print("{s}\n", .{self.glob}); inline for (@typeInfo(Self).Struct.fields) |field| { - if (comptime mem.eql(u8, field.name, "identifier")) continue; + if (comptime mem.eql(u8, field.name, "glob")) continue; if (@field(self, field.name)) |setting| { // Special-case the settings which are not enums. if (comptime mem.eql(u8, field.name, "pointer-accel")) { diff --git a/river/InputDevice.zig b/river/InputDevice.zig index ae22d35..efaff27 100644 --- a/river/InputDevice.zig +++ b/river/InputDevice.zig @@ -22,6 +22,8 @@ const ascii = std.ascii; const wlr = @import("wlroots"); const wl = @import("wayland").server.wl; +const globber = @import("globber"); + const server = &@import("main.zig").server; const util = @import("util.zig"); @@ -82,7 +84,7 @@ pub fn init(device: *InputDevice, seat: *Seat, wlr_device: *wlr.InputDevice) !vo if (!isKeyboardGroup(wlr_device)) { // Apply any matching input device configuration. for (server.input_manager.configs.items) |*input_config| { - if (mem.eql(u8, input_config.identifier, identifier)) { + if (globber.match(identifier, input_config.glob)) { input_config.apply(device); } } diff --git a/river/InputManager.zig b/river/InputManager.zig index a440c5a..5ae93aa 100644 --- a/river/InputManager.zig +++ b/river/InputManager.zig @@ -49,7 +49,10 @@ pointer_constraints: *wlr.PointerConstraintsV1, input_method_manager: *wlr.InputMethodManagerV2, text_input_manager: *wlr.TextInputManagerV3, +/// List of input device configurations. Ordered by glob generality, with +/// the most general towards the start and the most specific towards the end. configs: std.ArrayList(InputConfig), + devices: wl.list.Head(InputDevice, .link), seats: std.TailQueue(Seat) = .{}, diff --git a/river/command/input.zig b/river/command/input.zig index 2a8d002..13353fe 100644 --- a/river/command/input.zig +++ b/river/command/input.zig @@ -16,6 +16,11 @@ const std = @import("std"); const mem = std.mem; +const meta = std.meta; +const math = std.math; +const sort = std.sort; + +const globber = @import("globber"); const server = &@import("../main.zig").server; const util = @import("../util.zig"); @@ -39,7 +44,7 @@ pub fn listInputs( var it = server.input_manager.devices.iterator(.forward); while (it.next()) |device| { const configured = for (server.input_manager.configs.items) |*input_config| { - if (mem.eql(u8, input_config.identifier, device.identifier)) { + if (globber.match(device.identifier, input_config.glob)) { break true; } } else false; @@ -82,36 +87,50 @@ pub fn input( if (args.len < 4) return Error.NotEnoughArguments; if (args.len > 4) return Error.TooManyArguments; - // Try to find an existing InputConfig with matching identifier, or create + try globber.validate(args[1]); + + // Try to find an existing InputConfig with matching glob pattern, or create // a new one if none was found. - const input_config = for (server.input_manager.configs.items) |*input_config| { - if (mem.eql(u8, input_config.identifier, args[1])) { + for (server.input_manager.configs.items) |*input_config| { + if (mem.eql(u8, input_config.glob, args[1])) { try input_config.parse(args[2], args[3]); - break input_config; } - } else blk: { - const identifier_owned = try util.gpa.dupe(u8, args[1]); - errdefer util.gpa.free(identifier_owned); + } else { + const glob_owned = try util.gpa.dupe(u8, args[1]); + errdefer util.gpa.free(glob_owned); try server.input_manager.configs.ensureUnusedCapacity(1); const input_config = server.input_manager.configs.addOneAssumeCapacity(); errdefer _ = server.input_manager.configs.pop(); input_config.* = .{ - .identifier = identifier_owned, + .glob = glob_owned, }; try input_config.parse(args[2], args[3]); + } - break :blk input_config; - }; + // Sort input configs by generality. + sort.insertion(InputConfig, server.input_manager.configs.items, {}, lessThan); - // Update matching existing input devices. + // We need to update all input device matching the glob. The user may + // add an input configuration at an arbitrary position in the generality + // ordered list, so the simplest way to ensure the device is configured + // correctly is to apply all input configurations again, in order. var it = server.input_manager.devices.iterator(.forward); while (it.next()) |device| { - if (mem.eql(u8, device.identifier, args[1])) { - input_config.apply(device); - // We don't break here because it is common to have multiple input - // devices with the same identifier. + // Device does not match the glob given in the command, so its + // configuration state after applying all configs again would be + // the same. + if (!globber.match(device.identifier, args[1])) continue; + + for (server.input_manager.configs.items) |ic| { + if (globber.match(device.identifier, ic.glob)) { + ic.apply(device); + } } } } + +fn lessThan(_: void, a: InputConfig, b: InputConfig) bool { + return globber.order(a.glob, b.glob) == .gt; +}