diff --git a/jay-config/src/_private/client.rs b/jay-config/src/_private/client.rs index d00a274c..b34dc2c7 100644 --- a/jay-config/src/_private/client.rs +++ b/jay-config/src/_private/client.rs @@ -88,7 +88,9 @@ pub(crate) struct Client { response: RefCell>, on_new_seat: RefCell>>, on_new_input_device: RefCell>>, + on_input_device_removed: RefCell>>, on_connector_connected: RefCell>>, + on_connector_disconnected: RefCell>>, on_graphics_initialized: Cell>>, on_devices_enumerated: Cell>>, on_new_connector: RefCell>>, @@ -216,7 +218,9 @@ pub unsafe extern "C" fn init( response: Default::default(), on_new_seat: Default::default(), on_new_input_device: Default::default(), + on_input_device_removed: Default::default(), on_connector_connected: Default::default(), + on_connector_disconnected: Default::default(), on_graphics_initialized: Default::default(), on_devices_enumerated: Default::default(), on_new_connector: Default::default(), @@ -605,6 +609,10 @@ impl Client { *self.on_new_input_device.borrow_mut() = Some(cb(f)); } + pub fn on_input_device_removed(&self, f: F) { + *self.on_input_device_removed.borrow_mut() = Some(cb(f)); + } + pub fn on_switch_event( &self, input_device: InputDevice, @@ -818,6 +826,10 @@ impl Client { *self.on_connector_connected.borrow_mut() = Some(cb(f)); } + pub fn on_connector_disconnected(&self, f: F) { + *self.on_connector_disconnected.borrow_mut() = Some(cb(f)); + } + pub fn on_graphics_initialized(&self, f: F) { self.on_graphics_initialized.set(Some(Box::new(f))); } @@ -943,6 +955,17 @@ impl Client { self.send(&ClientMessage::SetFocusFollowsMouseMode { seat, mode }) } + pub fn set_input_device_connector(&self, input_device: InputDevice, connector: Connector) { + self.send(&ClientMessage::SetInputDeviceConnector { + input_device, + connector, + }) + } + + pub fn remove_input_mapping(&self, input_device: InputDevice) { + self.send(&ClientMessage::RemoveInputMapping { input_device }) + } + pub fn parse_keymap(&self, keymap: &str) -> Keymap { let res = self.send_with_response(&ClientMessage::ParseKeymap { keymap }); get_response!(res, Keymap(0), ParseKeymap { keymap }); @@ -1272,6 +1295,10 @@ impl Client { } ServerMessage::DelInputDevice { device } => { self.on_switch_event.borrow_mut().remove(&device); + let handler = self.on_input_device_removed.borrow_mut().clone(); + if let Some(handler) = handler { + run_cb("input device removed", &handler, device); + } } ServerMessage::ConnectorConnect { device } => { let handler = self.on_connector_connected.borrow_mut().clone(); @@ -1279,7 +1306,12 @@ impl Client { run_cb("connector connected", &handler, device); } } - ServerMessage::ConnectorDisconnect { .. } => {} + ServerMessage::ConnectorDisconnect { device } => { + let handler = self.on_connector_disconnected.borrow_mut().clone(); + if let Some(handler) = handler { + run_cb("connector disconnected", &handler, device); + } + } ServerMessage::NewConnector { device } => { let handler = self.on_new_connector.borrow_mut().clone(); if let Some(handler) = handler { diff --git a/jay-config/src/_private/ipc.rs b/jay-config/src/_private/ipc.rs index 16e57be1..65fa8db6 100644 --- a/jay-config/src/_private/ipc.rs +++ b/jay-config/src/_private/ipc.rs @@ -476,6 +476,13 @@ pub enum ClientMessage<'a> { seat: Seat, mode: FocusFollowsMouseMode, }, + SetInputDeviceConnector { + input_device: InputDevice, + connector: Connector, + }, + RemoveInputMapping { + input_device: InputDevice, + }, } #[derive(Serialize, Deserialize, Debug)] diff --git a/jay-config/src/input.rs b/jay-config/src/input.rs index 30edfb73..ae64897f 100644 --- a/jay-config/src/input.rs +++ b/jay-config/src/input.rs @@ -143,6 +143,20 @@ impl InputDevice { pub fn on_switch_event(self, f: F) { get!().on_switch_event(self, f) } + + /// Maps this input device to a connector. + /// + /// The connector should be connected. + /// + /// This should be used for touch screens and graphics tablets. + pub fn set_connector(self, connector: Connector) { + get!().set_input_device_connector(self, connector); + } + + /// Removes the mapping of this device to a connector. + pub fn remove_mapping(self) { + get!().remove_input_mapping(self); + } } /// A seat. @@ -449,6 +463,11 @@ pub fn on_new_input_device(f: F) { get!().on_new_input_device(f) } +/// Sets a closure to run when an input device has been removed. +pub fn on_input_device_removed(f: F) { + get!().on_input_device_removed(f) +} + /// Sets the maximum time between two clicks to be registered as a double click by the /// compositor. /// diff --git a/jay-config/src/video.rs b/jay-config/src/video.rs index acbd2601..00fa2b8c 100644 --- a/jay-config/src/video.rs +++ b/jay-config/src/video.rs @@ -275,6 +275,11 @@ pub fn on_connector_connected(f: F) { get!().on_connector_connected(f) } +/// Sets the callback to be called when a connector is disconnected from an output device. +pub fn on_connector_disconnected(f: F) { + get!().on_connector_disconnected(f) +} + /// Sets the callback to be called when the graphics of the compositor have been initialized. /// /// This callback is only invoked once during the lifetime of the compositor. This is a good place diff --git a/src/cli/input.rs b/src/cli/input.rs index a67dde78..280ee485 100644 --- a/src/cli/input.rs +++ b/src/cli/input.rs @@ -127,6 +127,10 @@ pub enum DeviceCommand { Attach(AttachArgs), /// Detach the device from its seat. Detach, + /// Maps this device to an output. + MapToOutput(MapToOutputArgs), + /// Removes the mapping from this device to an output. + RemoveMapping, } #[derive(ValueEnum, Debug, Clone)] @@ -196,6 +200,12 @@ pub struct SetTransformMatrixArgs { pub m22: f64, } +#[derive(Args, Debug, Clone)] +pub struct MapToOutputArgs { + /// The output to map to. + pub output: String, +} + #[derive(Args, Debug, Clone)] pub struct AttachArgs { /// The seat to attach to. @@ -261,6 +271,7 @@ struct InputDevice { pub natural_scrolling_enabled: Option, pub px_per_wheel_scroll: Option, pub transform_matrix: Option<[[f64; 2]; 2]>, + pub output: Option, } #[derive(Clone, Debug, Default)] @@ -564,6 +575,26 @@ impl Input { let map = self.handle_keymap(input).await; stdout().write_all(&map).unwrap(); } + DeviceCommand::MapToOutput(a) => { + self.handle_error(input, |e| { + eprintln!("Could not map the device to an output: {}", e); + }); + tc.send(jay_input::MapToOutput { + self_id: input, + id: args.device, + output: Some(&a.output), + }); + } + DeviceCommand::RemoveMapping => { + self.handle_error(input, |e| { + eprintln!("Could not remove the output mapping: {}", e); + }); + tc.send(jay_input::MapToOutput { + self_id: input, + id: args.device, + output: None, + }); + } } tc.round_trip().await; } @@ -694,6 +725,9 @@ impl Input { if let Some(v) = &device.transform_matrix { println!("{prefix} transform matrix: {:?}", v); } + if let Some(v) = &device.output { + println!("{prefix} mapped to output: {}", v); + } } async fn get(self: &Rc, input: JayInputId) -> Data { @@ -757,8 +791,15 @@ impl Input { .then_some(msg.natural_scrolling_enabled != 0), px_per_wheel_scroll: is_pointer.then_some(msg.px_per_wheel_scroll), transform_matrix: uapi::pod_read(msg.transform_matrix).ok(), + output: None, }); }); + jay_input::InputDeviceOutput::handle(tc, input, data.clone(), |data, msg| { + let mut data = data.borrow_mut(); + if let Some(last) = data.input_device.last_mut() { + last.output = Some(msg.output.to_string()); + } + }); tc.round_trip().await; let x = data.borrow_mut().clone(); x diff --git a/src/config/handler.rs b/src/config/handler.rs index 2b97c782..f6829226 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -338,6 +338,23 @@ impl ConfigProxyHandler { Ok(()) } + fn handle_set_input_device_connector( + &self, + input_device: InputDevice, + connector: Connector, + ) -> Result<(), CphError> { + let dev = self.get_device_handler_data(input_device)?; + let output = self.get_output_node(connector)?; + dev.set_output(Some(&output.global)); + Ok(()) + } + + fn handle_remove_input_mapping(&self, input_device: InputDevice) -> Result<(), CphError> { + let dev = self.get_device_handler_data(input_device)?; + dev.set_output(None); + Ok(()) + } + fn handle_set_status(&self, status: &str) { self.state.set_status(status); } @@ -1790,6 +1807,15 @@ impl ConfigProxyHandler { ClientMessage::SetFocusFollowsMouseMode { seat, mode } => self .handle_set_focus_follows_mouse_mode(seat, mode) .wrn("set_focus_follows_mouse_mode")?, + ClientMessage::SetInputDeviceConnector { + input_device, + connector, + } => self + .handle_set_input_device_connector(input_device, connector) + .wrn("set_input_device_connector")?, + ClientMessage::RemoveInputMapping { input_device } => self + .handle_remove_input_mapping(input_device) + .wrn("remove_input_mapping")?, } Ok(()) } diff --git a/src/ifs/jay_input.rs b/src/ifs/jay_input.rs index 13284139..605e26a8 100644 --- a/src/ifs/jay_input.rs +++ b/src/ifs/jay_input.rs @@ -129,6 +129,15 @@ impl JayInput { .map(uapi::as_bytes) .unwrap_or_default(), }); + if let Some(output) = data.data.output.get() { + if let Some(output) = output.get() { + self.client.event(InputDeviceOutput { + self_id: self.id, + id: data.id.raw(), + output: &output.connector.name, + }); + } + } } fn device(&self, id: u32) -> Result, JayInputError> { @@ -389,6 +398,32 @@ impl JayInputRequestHandler for JayInput { Ok(()) }) } + + fn map_to_output(&self, req: MapToOutput<'_>, _slf: &Rc) -> Result<(), Self::Error> { + self.or_error(|| { + let dev = self.device(req.id)?; + match req.output { + Some(output) => { + let namelc = output.to_ascii_lowercase(); + let c = self + .client + .state + .root + .outputs + .lock() + .values() + .find(|c| c.global.connector.name.to_ascii_lowercase() == namelc) + .cloned(); + match c { + Some(c) => dev.set_output(Some(&c.global)), + _ => return Err(JayInputError::OutputNotConnected), + } + } + _ => dev.set_output(None), + } + Ok(()) + }) + } } object_base! { @@ -418,5 +453,7 @@ pub enum JayInputError { ClientMemError(#[from] ClientMemError), #[error("Could not parse keymap")] XkbCommonError(#[from] XkbCommonError), + #[error("Output is not connected")] + OutputNotConnected, } efrom!(JayInputError, ClientError); diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index 440d6ae0..b79b039f 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -41,6 +41,7 @@ use { zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1, DynDataSource, IpcError, }, + wl_output::WlOutputGlobal, wl_seat::{ gesture_owner::GestureOwnerHolder, kb_owner::KbOwnerHolder, @@ -1163,4 +1164,17 @@ impl DeviceHandlerData { seat.handle_xkb_state_change(&old.borrow(), &new.borrow()); } } + + pub fn set_output(&self, output: Option<&WlOutputGlobal>) { + match output { + None => { + log::info!("Removing output mapping of {}", self.device.name()); + self.output.take(); + } + Some(o) => { + log::info!("Mapping {} to {}", self.device.name(), o.connector.name); + self.output.set(Some(o.opt.clone())); + } + } + } } diff --git a/src/state.rs b/src/state.rs index 77d6cab2..097e11b2 100644 --- a/src/state.rs +++ b/src/state.rs @@ -33,7 +33,7 @@ use { jay_seat_events::JaySeatEvents, jay_workspace_watcher::JayWorkspaceWatcher, wl_drm::WlDrmGlobal, - wl_output::{OutputId, PersistentOutputState}, + wl_output::{OutputGlobalOpt, OutputId, PersistentOutputState}, wl_seat::{SeatIds, WlSeatGlobal}, wl_surface::{ wl_subsurface::SubsurfaceIds, @@ -262,6 +262,7 @@ pub struct DeviceHandlerData { pub devnode: Option, pub keymap: CloneCell>>, pub xkb_state: CloneCell>>>, + pub output: CloneCell>>, } pub struct ConnectorData { diff --git a/src/tasks/input_device.rs b/src/tasks/input_device.rs index 08857ad7..ee8d7250 100644 --- a/src/tasks/input_device.rs +++ b/src/tasks/input_device.rs @@ -23,6 +23,7 @@ pub fn handle(state: &Rc, dev: Rc) { devnode: props.devnode, keymap: Default::default(), xkb_state: Default::default(), + output: Default::default(), }); let ae = Rc::new(AsyncEvent::default()); let oh = DeviceHandler { diff --git a/toml-config/src/config.rs b/toml-config/src/config.rs index 44e7d9cb..f2e3370c 100644 --- a/toml-config/src/config.rs +++ b/toml-config/src/config.rs @@ -246,6 +246,7 @@ pub struct Input { pub transform_matrix: Option<[[f64; 2]; 2]>, pub keymap: Option, pub switch_actions: AHashMap, + pub output: Option>, } #[derive(Debug, Clone)] diff --git a/toml-config/src/config/parsers/action.rs b/toml-config/src/config/parsers/action.rs index 42b19df4..a0133a4b 100644 --- a/toml-config/src/config/parsers/action.rs +++ b/toml-config/src/config/parsers/action.rs @@ -162,7 +162,7 @@ impl ActionParser<'_> { .extract(val("input"))? .parse_map(&mut InputParser { cx: self.0, - tag_ok: false, + is_inputs_array: false, }) .map_spanned_err(ActionParserError::ConfigureInput)?; Ok(Action::ConfigureInput { input }) diff --git a/toml-config/src/config/parsers/input.rs b/toml-config/src/config/parsers/input.rs index 83ee3bed..3b5ff6b8 100644 --- a/toml-config/src/config/parsers/input.rs +++ b/toml-config/src/config/parsers/input.rs @@ -8,6 +8,7 @@ use { action::ActionParser, input_match::{InputMatchParser, InputMatchParserError}, keymap::KeymapParser, + output_match::OutputMatchParser, }, Input, }, @@ -43,7 +44,7 @@ pub enum InputParserError { pub struct InputParser<'a> { pub cx: &'a Context<'a>, - pub tag_ok: bool, + pub is_inputs_array: bool, } impl<'a> Parser for InputParser<'a> { @@ -77,6 +78,8 @@ impl<'a> Parser for InputParser<'a> { on_lid_closed_val, on_converted_to_laptop_val, on_converted_to_tablet_val, + output_val, + remove_mapping, ), ) = ext.extract(( ( @@ -98,6 +101,8 @@ impl<'a> Parser for InputParser<'a> { opt(val("on-lid-closed")), opt(val("on-converted-to-laptop")), opt(val("on-converted-to-tablet")), + opt(val("output")), + recover(opt(bol("remove-mapping"))), ), ))?; let accel_profile = match accel_profile { @@ -122,7 +127,7 @@ impl<'a> Parser for InputParser<'a> { }, }; if let Some(tag) = tag { - if self.tag_ok { + if self.is_inputs_array { self.cx.used.borrow_mut().defined_inputs.insert(tag.into()); } else { log::warn!( @@ -147,7 +152,7 @@ impl<'a> Parser for InputParser<'a> { let mut switch_actions = AHashMap::new(); let mut parse_action = |val: Option>, name, event| { if let Some(val) = val { - if !self.tag_ok { + if !self.is_inputs_array { log::warn!( "{name} has no effect in this position: {}", self.cx.error3(val.span) @@ -176,6 +181,39 @@ impl<'a> Parser for InputParser<'a> { "on-converted-to-tablet", SwitchEvent::ConvertedToTablet, ); + let mut output = None; + if let Some(val) = output_val { + match val.parse(&mut OutputMatchParser(self.cx)) { + Ok(v) => output = Some(Some(v)), + Err(e) => { + log::warn!("Could not parse output: {}", self.cx.error(e)); + } + } + } + if let Some(val) = remove_mapping { + if self.is_inputs_array { + log::warn!( + "`remove-mapping` has no effect in this position: {}", + self.cx.error3(val.span) + ); + } else if !val.value { + log::warn!( + "`remove-mapping = false` has no effect: {}", + self.cx.error3(val.span) + ); + } else if let Some(output) = output_val { + log::warn!( + "Ignoring `remove-mapping = true` due to conflicting `output` field: {}", + self.cx.error3(val.span) + ); + log::info!( + "`output` field defined here: {}", + self.cx.error3(output.span) + ); + } else { + output = Some(None); + } + } Ok(Input { tag: tag.despan_into(), match_: match_val.parse_map(&mut InputMatchParser(self.cx))?, @@ -190,6 +228,7 @@ impl<'a> Parser for InputParser<'a> { transform_matrix, keymap, switch_actions, + output, }) } } @@ -206,7 +245,7 @@ impl<'a> Parser for InputsParser<'a> { for el in array { match el.parse(&mut InputParser { cx: self.0, - tag_ok: true, + is_inputs_array: true, }) { Ok(o) => res.push(o), Err(e) => { @@ -228,7 +267,7 @@ impl<'a> Parser for InputsParser<'a> { ); InputParser { cx: self.0, - tag_ok: true, + is_inputs_array: true, } .parse_table(span, table) .map(|v| vec![v]) diff --git a/toml-config/src/lib.rs b/toml-config/src/lib.rs index 73ce2329..7e8b8ba9 100644 --- a/toml-config/src/lib.rs +++ b/toml-config/src/lib.rs @@ -16,8 +16,8 @@ use { exec::{set_env, unset_env, Command}, get_workspace, input::{ - capability::CAP_SWITCH, get_seat, input_devices, on_new_input_device, - FocusFollowsMouseMode, InputDevice, Seat, SwitchEvent, + capability::CAP_SWITCH, get_seat, input_devices, on_input_device_removed, + on_new_input_device, FocusFollowsMouseMode, InputDevice, Seat, SwitchEvent, }, is_reload, keyboard::{Keymap, ModifiedKeySym}, @@ -28,9 +28,9 @@ use { switch_to_vt, theme::{reset_colors, reset_font, reset_sizes, set_font}, video::{ - connectors, drm_devices, on_connector_connected, on_graphics_initialized, - on_new_connector, on_new_drm_device, set_direct_scanout_enabled, set_gfx_api, - Connector, DrmDevice, + connectors, drm_devices, on_connector_connected, on_connector_disconnected, + on_graphics_initialized, on_new_connector, on_new_drm_device, + set_direct_scanout_enabled, set_gfx_api, Connector, DrmDevice, }, }, std::{cell::RefCell, io::ErrorKind, path::PathBuf, rc::Rc}, @@ -422,6 +422,17 @@ impl Input { c.set_keymap(km); } } + if let Some(output) = &self.output { + if let Some(output) = output { + for connector in connectors() { + if output.matches(connector, state) { + c.set_connector(connector); + } + } + } else { + c.remove_mapping(); + } + } } } @@ -550,6 +561,10 @@ struct State { input_devices: AHashMap, persistent: Rc, keymaps: AHashMap, + + io_maps: Vec<(InputMatch, OutputMatch)>, + io_inputs: RefCell>>, + io_outputs: RefCell>>, } impl Drop for State { @@ -701,6 +716,60 @@ impl State { } }); } + + fn add_io_output(&self, c: Connector) { + let mappings: Vec<_> = self + .io_maps + .iter() + .map(|(_, output)| output.matches(c, self)) + .collect(); + if mappings.len() > 0 { + self.io_outputs.borrow_mut().insert(c, mappings); + } + } + + fn add_io_input(&self, d: InputDevice) { + let mappings: Vec<_> = self + .io_maps + .iter() + .map(|(input, _)| input.matches(d, self)) + .collect(); + if mappings.len() > 0 { + self.io_inputs.borrow_mut().insert(d, mappings); + } + } + + fn map_input_to_output(&self, d: InputDevice) { + let input_mappings = &*self.io_inputs.borrow(); + let Some(input_matches) = input_mappings.get(&d) else { + return; + }; + for (idx, &input_is_match) in input_matches.iter().enumerate() { + if input_is_match { + for (&c, output_maps) in &*self.io_outputs.borrow() { + if output_maps.get(idx) == Some(&true) { + d.set_connector(c); + } + } + } + } + } + + fn map_output_to_input(&self, c: Connector) { + let output_mappings = &*self.io_outputs.borrow(); + let Some(output_matches) = output_mappings.get(&c) else { + return; + }; + for (idx, &output_is_match) in output_matches.iter().enumerate() { + if output_is_match { + for (&d, input_matches) in &*self.io_inputs.borrow() { + if input_matches.get(idx) == Some(&true) { + d.set_connector(c); + } + } + } + } + } } #[derive(Eq, PartialEq, Hash)] @@ -763,6 +832,7 @@ fn load_config(initial_load: bool, persistent: &Rc) { } } let mut input_devices = AHashMap::new(); + let mut io_maps = vec![]; for input in &config.inputs { if let Some(tag) = &input.tag { let prev = input_devices.insert(tag.clone(), input.match_.clone()); @@ -770,6 +840,9 @@ fn load_config(initial_load: bool, persistent: &Rc) { log::warn!("Duplicate input tag {tag}"); } } + if let Some(Some(output)) = &input.output { + io_maps.push((input.match_.clone(), output.clone())); + } } let mut named_drm_device = AHashMap::new(); for drm_device in &config.drm_devices { @@ -786,6 +859,9 @@ fn load_config(initial_load: bool, persistent: &Rc) { input_devices, persistent: persistent.clone(), keymaps, + io_maps, + io_inputs: Default::default(), + io_outputs: Default::default(), }); state.set_status(&config.status); let mut switch_actions = vec![]; @@ -827,6 +903,8 @@ fn load_config(initial_load: bool, persistent: &Rc) { on_connector_connected({ let state = state.clone(); move |c| { + state.add_io_output(c); + state.map_output_to_input(c); let id = OutputId { manufacturer: c.manufacturer(), model: c.model(), @@ -841,6 +919,12 @@ fn load_config(initial_load: bool, persistent: &Rc) { } } }); + on_connector_disconnected({ + let state = state.clone(); + move |c| { + state.io_outputs.borrow_mut().remove(&c); + } + }); set_default_workspace_capture(config.workspace_capture); for (k, v) in config.env { set_env(&k, &v); @@ -896,6 +980,7 @@ fn load_config(initial_load: bool, persistent: &Rc) { let state = state.clone(); let switch_actions = switch_actions.clone(); move |c| { + state.add_io_input(c); for input in &config.inputs { if input.match_.matches(c, &state) { input.apply(c, &state); @@ -904,7 +989,18 @@ fn load_config(initial_load: bool, persistent: &Rc) { state.handle_switch_device(c, &switch_actions); } }); + on_input_device_removed({ + let state = state.clone(); + move |c| { + state.io_inputs.borrow_mut().remove(&c); + } + }); + for c in connectors() { + state.add_io_output(c); + } for c in jay_config::input::input_devices() { + state.add_io_input(c); + state.map_input_to_output(c); state.handle_switch_device(c, &switch_actions); } persistent diff --git a/toml-spec/spec/spec.generated.json b/toml-spec/spec/spec.generated.json index 86e68430..56d908c2 100644 --- a/toml-spec/spec/spec.generated.json +++ b/toml-spec/spec/spec.generated.json @@ -841,6 +841,14 @@ "on-converted-to-tablet": { "description": "An action to execute when the convertible device is converted to a tablet.\n\nThis should only be used in the top-level inputs array.\n", "$ref": "#/$defs/Action" + }, + "output": { + "description": "Maps this input device to an output.\n\nThis is used to map touch screen and graphics tablets to outputs.\n\n- Example:\n\n ```toml\n [[inputs]]\n match.name = \"Wacom Bamboo Comic 2FG Pen\"\n output.connector = \"DP-1\"\n ```\n", + "$ref": "#/$defs/OutputMatch" + }, + "remove-mapping": { + "type": "boolean", + "description": "Removes the mapping of from this device to an output.\n\nThis should only be used within `configure-input` actions.\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-x = { type = \"configure-input\", input = { match.tag = \"wacom\", remove-mapping = true } }\n\n [[inputs]]\n tag = \"wacom\"\n match.name = \"Wacom Bamboo Comic 2FG Pen\"\n output.connector = \"DP-1\"\n ```\n" } }, "required": [ diff --git a/toml-spec/spec/spec.generated.md b/toml-spec/spec/spec.generated.md index d50e2c1b..0c3c48ce 100644 --- a/toml-spec/spec/spec.generated.md +++ b/toml-spec/spec/spec.generated.md @@ -1685,6 +1685,42 @@ The table has the following fields: The value of this field should be a [Action](#types-Action). +- `output` (optional): + + Maps this input device to an output. + + This is used to map touch screen and graphics tablets to outputs. + + - Example: + + ```toml + [[inputs]] + match.name = "Wacom Bamboo Comic 2FG Pen" + output.connector = "DP-1" + ``` + + The value of this field should be a [OutputMatch](#types-OutputMatch). + +- `remove-mapping` (optional): + + Removes the mapping of from this device to an output. + + This should only be used within `configure-input` actions. + + - Example: + + ```toml + [shortcuts] + alt-x = { type = "configure-input", input = { match.tag = "wacom", remove-mapping = true } } + + [[inputs]] + tag = "wacom" + match.name = "Wacom Bamboo Comic 2FG Pen" + output.connector = "DP-1" + ``` + + The value of this field should be a boolean. + ### `InputMatch` diff --git a/toml-spec/spec/spec.yaml b/toml-spec/spec/spec.yaml index 58456a23..06da5295 100644 --- a/toml-spec/spec/spec.yaml +++ b/toml-spec/spec/spec.yaml @@ -1285,6 +1285,40 @@ Input: An action to execute when the convertible device is converted to a tablet. This should only be used in the top-level inputs array. + output: + ref: OutputMatch + required: false + description: | + Maps this input device to an output. + + This is used to map touch screen and graphics tablets to outputs. + + - Example: + + ```toml + [[inputs]] + match.name = "Wacom Bamboo Comic 2FG Pen" + output.connector = "DP-1" + ``` + remove-mapping: + kind: boolean + required: false + description: | + Removes the mapping of from this device to an output. + + This should only be used within `configure-input` actions. + + - Example: + + ```toml + [shortcuts] + alt-x = { type = "configure-input", input = { match.tag = "wacom", remove-mapping = true } } + + [[inputs]] + tag = "wacom" + match.name = "Wacom Bamboo Comic 2FG Pen" + output.connector = "DP-1" + ``` AccelProfile: diff --git a/wire/jay_input.txt b/wire/jay_input.txt index 3539389e..91a7875d 100644 --- a/wire/jay_input.txt +++ b/wire/jay_input.txt @@ -109,6 +109,11 @@ request get_device_keymap { id: u32, } +request map_to_output { + id: u32, + output: optstr, +} + # events event seat { @@ -148,3 +153,8 @@ event keymap { keymap: fd, keymap_len: u32, } + +event input_device_output { + id: u32, + output: str, +}