diff --git a/src/dbus/mod.rs b/src/dbus/mod.rs index 347b16fd0..f69dc1d94 100644 --- a/src/dbus/mod.rs +++ b/src/dbus/mod.rs @@ -50,7 +50,25 @@ impl DBusServers { } if is_session_instance || config.debug.dbus_interfaces_in_non_session_instances { - let display_config = DisplayConfig::new(backend.ipc_outputs()); + let (to_niri, from_display_config) = calloop::channel::channel(); + let display_config = DisplayConfig::new(to_niri, backend.ipc_outputs()); + niri.event_loop + .insert_source(from_display_config, move |event, _, state| match event { + calloop::channel::Event::Msg(new_conf) => { + for (name, conf) in new_conf { + state.modify_output_config(&name, move |output| { + if let Some(new_output) = conf { + *output = new_output; + } else { + output.off = true; + } + }); + } + state.reload_output_config(); + } + calloop::channel::Event::Closed => (), + }) + .unwrap(); dbus.conn_display_config = try_start(display_config); let screen_saver = ScreenSaver::new(niri.is_fdo_idle_inhibited.clone()); diff --git a/src/dbus/mutter_display_config.rs b/src/dbus/mutter_display_config.rs index 2be37b5eb..b8ab08f97 100644 --- a/src/dbus/mutter_display_config.rs +++ b/src/dbus/mutter_display_config.rs @@ -1,7 +1,8 @@ use std::collections::HashMap; +use std::str::FromStr; use std::sync::{Arc, Mutex}; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use smithay::utils::Size; use zbus::fdo::RequestNameFlags; use zbus::object_server::SignalEmitter; @@ -14,6 +15,7 @@ use crate::utils::is_laptop_panel; use crate::utils::scale::supported_scales; pub struct DisplayConfig { + to_niri: calloop::channel::Sender>>, ipc_outputs: Arc>, } @@ -46,6 +48,17 @@ pub struct LogicalMonitor { properties: HashMap, } +// ApplyMonitorsConfig +#[derive(Deserialize, Type)] +pub struct LogicalMonitorConfiguration { + x: i32, + y: i32, + scale: f64, + transform: u32, + _is_primary: bool, + monitors: Vec<(String, String, HashMap)>, +} + #[interface(name = "org.gnome.Mutter.DisplayConfig")] impl DisplayConfig { async fn get_current_state( @@ -158,6 +171,87 @@ impl DisplayConfig { Ok((0, monitors, logical_monitors, properties)) } + async fn apply_monitors_config( + &self, + _serial: u32, + method: u32, + logical_monitor_configs: Vec, + _properties: HashMap, + ) -> fdo::Result<()> { + let current_conf = self.ipc_outputs.lock().unwrap(); + let mut new_conf = HashMap::new(); + for requested_config in logical_monitor_configs { + if requested_config.monitors.len() > 1 { + return Err(zbus::fdo::Error::Failed( + "Mirroring is not yet supported".to_owned(), + )); + } + for (connector, mode, _props) in requested_config.monitors { + if !current_conf.values().any(|o| o.name == connector) { + return Err(zbus::fdo::Error::Failed(format!( + "Connector '{}' not found", + connector + ))); + } + new_conf.insert( + connector.clone(), + Some(niri_config::Output { + off: false, + name: connector, + scale: Some(niri_config::FloatOrInt(requested_config.scale)), + transform: match requested_config.transform { + 0 => niri_ipc::Transform::Normal, + 1 => niri_ipc::Transform::_90, + 2 => niri_ipc::Transform::_180, + 3 => niri_ipc::Transform::_270, + 4 => niri_ipc::Transform::Flipped, + 5 => niri_ipc::Transform::Flipped90, + 6 => niri_ipc::Transform::Flipped180, + 7 => niri_ipc::Transform::Flipped270, + x => { + return Err(zbus::fdo::Error::Failed(format!( + "Unknown transform {}", + x + ))) + } + }, + position: Some(niri_config::Position { + x: requested_config.x, + y: requested_config.y, + }), + mode: Some(niri_ipc::ConfiguredMode::from_str(&mode).map_err(|e| { + zbus::fdo::Error::Failed(format!( + "Could not parse mode '{}': {}", + mode, e + )) + })?), + // FIXME: VRR + ..Default::default() + }), + ); + } + } + if new_conf.is_empty() { + return Err(zbus::fdo::Error::Failed( + "At least one output must be enabled".to_owned(), + )); + } + for output in current_conf.values() { + if !new_conf.contains_key(&output.name) { + new_conf.insert(output.name.clone(), None); + } + } + if method == 0 { + // 0 means "verify", so don't actually apply here + return Ok(()); + } + if let Err(err) = self.to_niri.send(new_conf) { + warn!("error sending message to niri: {err:?}"); + return Err(fdo::Error::Failed("internal error".to_owned())); + } + Ok(()) + } + #[zbus(signal)] pub async fn monitors_changed(ctxt: &SignalEmitter<'_>) -> zbus::Result<()>; @@ -188,8 +282,14 @@ impl DisplayConfig { } impl DisplayConfig { - pub fn new(ipc_outputs: Arc>) -> Self { - Self { ipc_outputs } + pub fn new( + to_niri: calloop::channel::Sender>>, + ipc_outputs: Arc>, + ) -> Self { + Self { + to_niri, + ipc_outputs, + } } } diff --git a/src/niri.rs b/src/niri.rs index ef4e8e6c2..16758ab57 100644 --- a/src/niri.rs +++ b/src/niri.rs @@ -1343,82 +1343,85 @@ impl State { self.niri.output_management_state.on_config_changed(config); } - pub fn apply_transient_output_config(&mut self, name: &str, action: niri_ipc::OutputAction) { + pub fn modify_output_config(&mut self, name: &str, fun: F) + where + F: FnOnce(&mut niri_config::Output), + { + // Try hard to find the output config section corresponding to the output set by the + // user. Since if we add a new section and some existing section also matches the + // output, then our new section won't do anything. + let temp; + let match_name = if let Some(output) = self.niri.output_by_name_match(name) { + output.user_data().get::().unwrap() + } else if let Some(output_name) = self + .backend + .tty_checked() + .and_then(|tty| tty.disconnected_connector_name_by_name_match(name)) { - // Try hard to find the output config section corresponding to the output set by the - // user. Since if we add a new section and some existing section also matches the - // output, then our new section won't do anything. - let temp; - let match_name = if let Some(output) = self.niri.output_by_name_match(name) { - output.user_data().get::().unwrap() - } else if let Some(output_name) = self - .backend - .tty_checked() - .and_then(|tty| tty.disconnected_connector_name_by_name_match(name)) - { - temp = output_name; - &temp - } else { - // Even if name is "make model serial", matching will work fine this way. - temp = OutputName { - connector: name.to_owned(), - make: None, - model: None, - serial: None, - }; - &temp + temp = output_name; + &temp + } else { + // Even if name is "make model serial", matching will work fine this way. + temp = OutputName { + connector: name.to_owned(), + make: None, + model: None, + serial: None, }; + &temp + }; - let mut config = self.niri.config.borrow_mut(); - let config = if let Some(config) = config.outputs.find_mut(match_name) { - config - } else { - config.outputs.0.push(niri_config::Output { - // Save name as set by the user. - name: String::from(name), - ..Default::default() - }); - config.outputs.0.last_mut().unwrap() - }; + let mut config = self.niri.config.borrow_mut(); + let config = if let Some(config) = config.outputs.find_mut(match_name) { + config + } else { + config.outputs.0.push(niri_config::Output { + // Save name as set by the user. + name: String::from(name), + ..Default::default() + }); + config.outputs.0.last_mut().unwrap() + }; - match action { - niri_ipc::OutputAction::Off => config.off = true, - niri_ipc::OutputAction::On => config.off = false, - niri_ipc::OutputAction::Mode { mode } => { - config.mode = match mode { - niri_ipc::ModeToSet::Automatic => None, - niri_ipc::ModeToSet::Specific(mode) => Some(mode), - } + fun(config); + } + + pub fn apply_transient_output_config(&mut self, name: &str, action: niri_ipc::OutputAction) { + self.modify_output_config(name, move |config| match action { + niri_ipc::OutputAction::Off => config.off = true, + niri_ipc::OutputAction::On => config.off = false, + niri_ipc::OutputAction::Mode { mode } => { + config.mode = match mode { + niri_ipc::ModeToSet::Automatic => None, + niri_ipc::ModeToSet::Specific(mode) => Some(mode), } - niri_ipc::OutputAction::Scale { scale } => { - config.scale = match scale { - niri_ipc::ScaleToSet::Automatic => None, - niri_ipc::ScaleToSet::Specific(scale) => Some(FloatOrInt(scale)), - } + } + niri_ipc::OutputAction::Scale { scale } => { + config.scale = match scale { + niri_ipc::ScaleToSet::Automatic => None, + niri_ipc::ScaleToSet::Specific(scale) => Some(FloatOrInt(scale)), } - niri_ipc::OutputAction::Transform { transform } => config.transform = transform, - niri_ipc::OutputAction::Position { position } => { - config.position = match position { - niri_ipc::PositionToSet::Automatic => None, - niri_ipc::PositionToSet::Specific(position) => { - Some(niri_config::Position { - x: position.x, - y: position.y, - }) - } - } + } + niri_ipc::OutputAction::Transform { transform } => config.transform = transform, + niri_ipc::OutputAction::Position { position } => { + config.position = match position { + niri_ipc::PositionToSet::Automatic => None, + niri_ipc::PositionToSet::Specific(position) => Some(niri_config::Position { + x: position.x, + y: position.y, + }), } - niri_ipc::OutputAction::Vrr { vrr } => { - config.variable_refresh_rate = if vrr.vrr { - Some(niri_config::Vrr { - on_demand: vrr.on_demand, - }) - } else { - None - } + } + niri_ipc::OutputAction::Vrr { vrr } => { + config.variable_refresh_rate = if vrr.vrr { + Some(niri_config::Vrr { + on_demand: vrr.on_demand, + }) + } else { + None } } - } + }); self.reload_output_config(); }