command/input: add map-to-output
This commit is contained in:
		| @ -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 | ||||
|  | ||||
| @ -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' | ||||
|  | ||||
| @ -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
									
									
								
							
							
								
								
								
								
								
							
						
						
									
										2
									
								
								deps/zig-wlroots
									
									
									
									
										vendored
									
									
								
							 Submodule deps/zig-wlroots updated: 2149026047...e7e4995166
									
								
							| @ -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: | ||||
|  | ||||
| @ -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,9 +281,17 @@ 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}); | ||||
|             setting.apply(libinput_device); | ||||
|             if (comptime mem.eql(u8, field.name, "map-to-output")) { | ||||
|                 setting.apply(device.wlr_device); | ||||
|             } else { | ||||
|                 setting.apply(libinput_device); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -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) { | ||||
|  | ||||
| @ -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); | ||||
|  | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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 { | ||||
|  | ||||
		Reference in New Issue
	
	Block a user