command/input: support globs

This commit is contained in:
Leon Henrik Plickat 2024-01-08 00:49:16 +01:00 committed by Isaac Freund
parent 7f1f9152f2
commit 931b6268e7
No known key found for this signature in database
GPG Key ID: 86DED400DDFD7A11
5 changed files with 51 additions and 25 deletions

View File

@ -465,6 +465,8 @@ The _input_ command can be used to create a configuration rule for an input
device identified by its _name_. device identified by its _name_.
The _name_ of an input device consists of its type, its numerical vendor id, 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 -. 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. A list of all device properties that can be configured may be found below.
However note that not every input device supports every property. However note that not every input device supports every property.

View File

@ -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. // Note: Field names equal name of the setting in the 'input' command.
events: ?EventState = null, events: ?EventState = null,
@ -281,15 +281,15 @@ tap: ?TapState = null,
@"scroll-button": ?ScrollButton = null, @"scroll-button": ?ScrollButton = null,
pub fn deinit(self: *Self) void { 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); 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| { 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| { if (@field(self, field.name)) |setting| {
log.debug("applying setting: {s}", .{field.name}); 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 { pub fn parse(self: *Self, setting: []const u8, value: []const u8) !void {
inline for (@typeInfo(Self).Struct.fields) |field| { 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)) { if (mem.eql(u8, setting, field.name)) {
// Special-case the settings which are not enums. // 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 { 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| { 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| { if (@field(self, field.name)) |setting| {
// Special-case the settings which are not enums. // Special-case the settings which are not enums.
if (comptime mem.eql(u8, field.name, "pointer-accel")) { if (comptime mem.eql(u8, field.name, "pointer-accel")) {

View File

@ -22,6 +22,8 @@ const ascii = std.ascii;
const wlr = @import("wlroots"); const wlr = @import("wlroots");
const wl = @import("wayland").server.wl; const wl = @import("wayland").server.wl;
const globber = @import("globber");
const server = &@import("main.zig").server; const server = &@import("main.zig").server;
const util = @import("util.zig"); 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)) { if (!isKeyboardGroup(wlr_device)) {
// Apply any matching input device configuration. // Apply any matching input device configuration.
for (server.input_manager.configs.items) |*input_config| { 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); input_config.apply(device);
} }
} }

View File

@ -49,7 +49,10 @@ pointer_constraints: *wlr.PointerConstraintsV1,
input_method_manager: *wlr.InputMethodManagerV2, input_method_manager: *wlr.InputMethodManagerV2,
text_input_manager: *wlr.TextInputManagerV3, 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), configs: std.ArrayList(InputConfig),
devices: wl.list.Head(InputDevice, .link), devices: wl.list.Head(InputDevice, .link),
seats: std.TailQueue(Seat) = .{}, seats: std.TailQueue(Seat) = .{},

View File

@ -16,6 +16,11 @@
const std = @import("std"); const std = @import("std");
const mem = std.mem; 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 server = &@import("../main.zig").server;
const util = @import("../util.zig"); const util = @import("../util.zig");
@ -39,7 +44,7 @@ pub fn listInputs(
var it = server.input_manager.devices.iterator(.forward); var it = server.input_manager.devices.iterator(.forward);
while (it.next()) |device| { while (it.next()) |device| {
const configured = for (server.input_manager.configs.items) |*input_config| { 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; break true;
} }
} else false; } else false;
@ -82,36 +87,50 @@ pub fn input(
if (args.len < 4) return Error.NotEnoughArguments; if (args.len < 4) return Error.NotEnoughArguments;
if (args.len > 4) return Error.TooManyArguments; 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. // a new one if none was found.
const input_config = for (server.input_manager.configs.items) |*input_config| { for (server.input_manager.configs.items) |*input_config| {
if (mem.eql(u8, input_config.identifier, args[1])) { if (mem.eql(u8, input_config.glob, args[1])) {
try input_config.parse(args[2], args[3]); try input_config.parse(args[2], args[3]);
break input_config;
} }
} else blk: { } else {
const identifier_owned = try util.gpa.dupe(u8, args[1]); const glob_owned = try util.gpa.dupe(u8, args[1]);
errdefer util.gpa.free(identifier_owned); errdefer util.gpa.free(glob_owned);
try server.input_manager.configs.ensureUnusedCapacity(1); try server.input_manager.configs.ensureUnusedCapacity(1);
const input_config = server.input_manager.configs.addOneAssumeCapacity(); const input_config = server.input_manager.configs.addOneAssumeCapacity();
errdefer _ = server.input_manager.configs.pop(); errdefer _ = server.input_manager.configs.pop();
input_config.* = .{ input_config.* = .{
.identifier = identifier_owned, .glob = glob_owned,
}; };
try input_config.parse(args[2], args[3]); 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); var it = server.input_manager.devices.iterator(.forward);
while (it.next()) |device| { while (it.next()) |device| {
if (mem.eql(u8, device.identifier, args[1])) { // Device does not match the glob given in the command, so its
input_config.apply(device); // configuration state after applying all configs again would be
// We don't break here because it is common to have multiple input // the same.
// devices with the same identifier. 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;
}