command/input: add map-to-output

This commit is contained in:
LordMZTE 2024-02-20 21:54:06 +01:00 committed by Isaac Freund
parent 95da9b5875
commit 0cb7c49cc3
No known key found for this signature in database
GPG Key ID: 86DED400DDFD7A11
9 changed files with 92 additions and 18 deletions

View File

@ -95,7 +95,8 @@ function __riverctl_completion ()
tap \
tap-button-map \
scroll-method \
scroll-button"
scroll-button \
map-to-output"
COMPREPLY=($(compgen -W "${OPTS}" -- "${COMP_WORDS[3]}"))
elif [ "${COMP_WORDS[1]}" == "hide-cursor" ]
then

View File

@ -116,6 +116,7 @@ complete -c riverctl -n '__fish_seen_subcommand_from input; and __fish_riverctl_
complete -c riverctl -n '__fish_seen_subcommand_from input; and __fish_riverctl_complete_arg 3' -a 'tap-button-map' -d 'Configure the button mapping for tapping'
complete -c riverctl -n '__fish_seen_subcommand_from input; and __fish_riverctl_complete_arg 3' -a 'scroll-method' -d 'Set the scroll method'
complete -c riverctl -n '__fish_seen_subcommand_from input; and __fish_riverctl_complete_arg 3' -a 'scroll-button' -d 'Set the scroll button'
complete -c riverctl -n '__fish_seen_subcommand_from input; and __fish_riverctl_complete_arg 3' -a 'map-to-output' -d 'Map to a given output'
# Subcommands for the subcommands of 'input'
complete -c riverctl -n '__fish_seen_subcommand_from input; and __fish_riverctl_complete_arg 4; and __fish_seen_subcommand_from drag drag-lock disable-while-typing disable-while-trackpointing middle-emulation natural-scroll left-handed tap' -a 'enabled disabled'

View File

@ -123,6 +123,7 @@ _riverctl()
'tap-button-map:Configure the button mapping for tapping'
'scroll-method:Set the scroll method'
'scroll-button:Set the scroll button'
'map-to-output:Map to a given output'
)
_describe -t command 'command' input_subcommands

2
deps/zig-wlroots vendored

@ -1 +1 @@
Subproject commit 2149026047c95b3cad2eb2bf1e3e9f66447dae97
Subproject commit e7e49951662d346f0f522124f2138ecf9c44ded9

View File

@ -527,6 +527,11 @@ However note that not every input device supports every property.
Set the scroll button of an input device. _button_ is the name of a Linux
input event code.
*input* _name_ *map-to-output* _output_|*disabled*
Maps the input to a given output. This is valid even if the output isn't
currently active and will lead to the device being mapped once it is
connected.
# EXAMPLES
Bind Super+Return in normal mode to spawn a *foot*(1) terminal:

View File

@ -214,6 +214,40 @@ pub const ScrollButton = struct {
}
};
pub const MapToOutput = struct {
output_name: ?[]const u8,
fn apply(map_to_output: MapToOutput, device: *wlr.InputDevice) void {
const output = out: {
if (map_to_output.output_name) |name| {
var it = server.root.active_outputs.iterator(.forward);
while (it.next()) |outp| {
if (mem.eql(u8, mem.span(outp.wlr_output.name), name)) {
break :out outp.wlr_output;
}
}
}
break :out null;
};
switch (device.type) {
.pointer, .touch, .tablet_tool => {
log.debug("mapping input '{s}' -> '{s}'", .{
device.name,
if (output) |o| o.name else "<no output>",
});
// TODO: support multiple seats
server.input_manager.defaultSeat().cursor.wlr_cursor.mapInputToOutput(device, output);
},
// These devices do not support being mapped to outputs.
.keyboard, .tablet_pad, .switch_device => {},
}
}
};
glob: []const u8,
// Note: Field names equal name of the setting in the 'input' command.
@ -232,9 +266,12 @@ tap: ?TapState = null,
@"pointer-accel": ?PointerAccel = null,
@"scroll-method": ?ScrollMethod = null,
@"scroll-button": ?ScrollButton = null,
@"map-to-output": MapToOutput = .{ .output_name = null },
pub fn deinit(self: *Self) void {
util.gpa.free(self.glob);
if (self.@"map-to-output".output_name) |name|
util.gpa.free(name);
}
pub fn apply(self: *const Self, device: *InputDevice) void {
@ -244,12 +281,20 @@ pub fn apply(self: *const Self, device: *InputDevice) void {
inline for (@typeInfo(Self).Struct.fields) |field| {
if (comptime mem.eql(u8, field.name, "glob")) continue;
if (@field(self, field.name)) |setting| {
if (@as(if (@typeInfo(field.type) == .Optional)
field.type
else
?field.type, @field(self, field.name))) |setting|
{
log.debug("applying setting: {s}", .{field.name});
if (comptime mem.eql(u8, field.name, "map-to-output")) {
setting.apply(device.wlr_device);
} else {
setting.apply(libinput_device);
}
}
}
}
pub fn parse(self: *Self, setting: []const u8, value: []const u8) !void {
inline for (@typeInfo(Self).Struct.fields) |field| {
@ -265,6 +310,13 @@ pub fn parse(self: *Self, setting: []const u8, value: []const u8) !void {
const ret = c.libevdev_event_code_from_name(c.EV_KEY, value.ptr);
if (ret < 1) return error.InvalidButton;
self.@"scroll-button" = ScrollButton{ .button = @intCast(ret) };
} else if (comptime mem.eql(u8, field.name, "map-to-output")) {
if (self.@"map-to-output".output_name) |name|
util.gpa.free(name);
self.@"map-to-output" = MapToOutput{
.output_name = if (std.mem.eql(u8, value, "disabled")) null else try util.gpa.dupe(u8, value),
};
} else {
const T = @typeInfo(field.type).Optional.child;
if (@typeInfo(T) != .Enum) {
@ -286,7 +338,11 @@ pub fn write(self: *Self, writer: anytype) !void {
inline for (@typeInfo(Self).Struct.fields) |field| {
if (comptime mem.eql(u8, field.name, "glob")) continue;
if (@field(self, field.name)) |setting| {
if (@as(if (@typeInfo(field.type) == .Optional)
field.type
else
?field.type, @field(self, field.name))) |setting|
{
// Special-case the settings which are not enums.
if (comptime mem.eql(u8, field.name, "pointer-accel")) {
try writer.print("\tpointer-accel: {d}\n", .{setting.value});
@ -294,6 +350,9 @@ pub fn write(self: *Self, writer: anytype) !void {
try writer.print("\tscroll-button: {s}\n", .{
mem.sliceTo(c.libevdev_event_code_get_name(c.EV_KEY, setting.button), 0),
});
} else if (comptime mem.eql(u8, field.name, "map-to-output")) {
if (setting.output_name) |outp|
try writer.print("\tmap-to-output: {s}\n", .{outp});
} else {
const T = @typeInfo(field.type).Optional.child;
if (@typeInfo(T) != .Enum) {

View File

@ -138,6 +138,19 @@ pub fn inputAllowed(self: Self, wlr_surface: *wlr.Surface) bool {
true;
}
/// Reconfigures all devices' libinput configuration as well as their output mapping.
/// This is called on outputs being added or removed and on the input configuration being changed.
pub fn reconfigureDevices(self: *Self) void {
var it = self.devices.iterator(.forward);
while (it.next()) |device| {
for (self.configs.items) |config| {
if (@import("globber").match(device.identifier, config.glob)) {
config.apply(device);
}
}
}
}
fn handleNewInput(listener: *wl.Listener(*wlr.InputDevice), wlr_device: *wlr.InputDevice) void {
const self = @fieldParentPtr(Self, "new_input", listener);

View File

@ -246,6 +246,8 @@ fn handleNewOutput(_: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) void {
}
wlr_output.destroy();
};
server.input_manager.reconfigureDevices();
}
/// Remove the output from root.active_outputs and the output layout.
@ -334,6 +336,10 @@ pub fn deactivateOutput(root: *Self, output: *Output) void {
root.notifyLayoutDemandDone();
}
while (output.layouts.first) |node| node.data.destroy();
// We must call reconfigureDevices here to unmap devices that might be mapped to this output
// in order to prevent a segfault in wlroots.
server.input_manager.reconfigureDevices();
}
/// Add the output to root.active_outputs and the output layout if it has not

View File

@ -112,19 +112,7 @@ pub fn input(
// 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| {
// 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) |config| {
if (globber.match(device.identifier, config.glob)) {
config.apply(device);
}
}
}
server.input_manager.reconfigureDevices();
}
fn lessThan(_: void, a: InputConfig, b: InputConfig) bool {