config: allow mapping input devices to outputs
This commit is contained in:
parent
efdca4de49
commit
86e283d255
18 changed files with 420 additions and 13 deletions
|
|
@ -88,7 +88,9 @@ pub(crate) struct Client {
|
|||
response: RefCell<Vec<Response>>,
|
||||
on_new_seat: RefCell<Option<Callback<Seat>>>,
|
||||
on_new_input_device: RefCell<Option<Callback<InputDevice>>>,
|
||||
on_input_device_removed: RefCell<Option<Callback<InputDevice>>>,
|
||||
on_connector_connected: RefCell<Option<Callback<Connector>>>,
|
||||
on_connector_disconnected: RefCell<Option<Callback<Connector>>>,
|
||||
on_graphics_initialized: Cell<Option<Box<dyn FnOnce()>>>,
|
||||
on_devices_enumerated: Cell<Option<Box<dyn FnOnce()>>>,
|
||||
on_new_connector: RefCell<Option<Callback<Connector>>>,
|
||||
|
|
@ -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<F: FnMut(InputDevice) + 'static>(&self, f: F) {
|
||||
*self.on_input_device_removed.borrow_mut() = Some(cb(f));
|
||||
}
|
||||
|
||||
pub fn on_switch_event<F: FnMut(SwitchEvent) + 'static>(
|
||||
&self,
|
||||
input_device: InputDevice,
|
||||
|
|
@ -818,6 +826,10 @@ impl Client {
|
|||
*self.on_connector_connected.borrow_mut() = Some(cb(f));
|
||||
}
|
||||
|
||||
pub fn on_connector_disconnected<F: FnMut(Connector) + 'static>(&self, f: F) {
|
||||
*self.on_connector_disconnected.borrow_mut() = Some(cb(f));
|
||||
}
|
||||
|
||||
pub fn on_graphics_initialized<F: FnOnce() + 'static>(&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 {
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -143,6 +143,20 @@ impl InputDevice {
|
|||
pub fn on_switch_event<F: FnMut(SwitchEvent) + 'static>(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: FnMut(InputDevice) + 'static>(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: FnMut(InputDevice) + 'static>(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.
|
||||
///
|
||||
|
|
|
|||
|
|
@ -275,6 +275,11 @@ pub fn on_connector_connected<F: FnMut(Connector) + 'static>(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: FnMut(Connector) + 'static>(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
|
||||
|
|
|
|||
|
|
@ -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<bool>,
|
||||
pub px_per_wheel_scroll: Option<f64>,
|
||||
pub transform_matrix: Option<[[f64; 2]; 2]>,
|
||||
pub output: Option<String>,
|
||||
}
|
||||
|
||||
#[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<Self>, 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
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Rc<DeviceHandlerData>, JayInputError> {
|
||||
|
|
@ -389,6 +398,32 @@ impl JayInputRequestHandler for JayInput {
|
|||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn map_to_output(&self, req: MapToOutput<'_>, _slf: &Rc<Self>) -> 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);
|
||||
|
|
|
|||
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<String>,
|
||||
pub keymap: CloneCell<Option<Rc<XkbKeymap>>>,
|
||||
pub xkb_state: CloneCell<Option<Rc<RefCell<XkbState>>>>,
|
||||
pub output: CloneCell<Option<Rc<OutputGlobalOpt>>>,
|
||||
}
|
||||
|
||||
pub struct ConnectorData {
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ pub fn handle(state: &Rc<State>, dev: Rc<dyn InputDevice>) {
|
|||
devnode: props.devnode,
|
||||
keymap: Default::default(),
|
||||
xkb_state: Default::default(),
|
||||
output: Default::default(),
|
||||
});
|
||||
let ae = Rc::new(AsyncEvent::default());
|
||||
let oh = DeviceHandler {
|
||||
|
|
|
|||
|
|
@ -246,6 +246,7 @@ pub struct Input {
|
|||
pub transform_matrix: Option<[[f64; 2]; 2]>,
|
||||
pub keymap: Option<ConfigKeymap>,
|
||||
pub switch_actions: AHashMap<SwitchEvent, Action>,
|
||||
pub output: Option<Option<OutputMatch>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
|
|||
|
|
@ -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 })
|
||||
|
|
|
|||
|
|
@ -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<Spanned<&Value>>, 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])
|
||||
|
|
|
|||
|
|
@ -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<String, InputMatch>,
|
||||
persistent: Rc<PersistentState>,
|
||||
keymaps: AHashMap<String, Keymap>,
|
||||
|
||||
io_maps: Vec<(InputMatch, OutputMatch)>,
|
||||
io_inputs: RefCell<AHashMap<InputDevice, Vec<bool>>>,
|
||||
io_outputs: RefCell<AHashMap<Connector, Vec<bool>>>,
|
||||
}
|
||||
|
||||
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<PersistentState>) {
|
|||
}
|
||||
}
|
||||
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<PersistentState>) {
|
|||
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<PersistentState>) {
|
|||
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<PersistentState>) {
|
|||
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<PersistentState>) {
|
|||
}
|
||||
}
|
||||
});
|
||||
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<PersistentState>) {
|
|||
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<PersistentState>) {
|
|||
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
|
||||
|
|
|
|||
|
|
@ -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": [
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
||||
<a name="types-InputMatch"></a>
|
||||
### `InputMatch`
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue