dbus: DisplayConfig: implement apply_monitors_config

This enables gnome-control-center to apply display configuration
changes. Only temporarily, persistence is ignored currently.
This commit is contained in:
Val Packett
2025-01-01 03:07:47 -03:00
committed by Ivan Molodetskikh
parent b853d5b124
commit 890bbff007
3 changed files with 195 additions and 74 deletions
+19 -1
View File
@@ -50,7 +50,25 @@ impl DBusServers {
} }
if is_session_instance || config.debug.dbus_interfaces_in_non_session_instances { 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); dbus.conn_display_config = try_start(display_config);
let screen_saver = ScreenSaver::new(niri.is_fdo_idle_inhibited.clone()); let screen_saver = ScreenSaver::new(niri.is_fdo_idle_inhibited.clone());
+103 -3
View File
@@ -1,7 +1,8 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::str::FromStr;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use serde::Serialize; use serde::{Deserialize, Serialize};
use smithay::utils::Size; use smithay::utils::Size;
use zbus::fdo::RequestNameFlags; use zbus::fdo::RequestNameFlags;
use zbus::object_server::SignalEmitter; use zbus::object_server::SignalEmitter;
@@ -14,6 +15,7 @@ use crate::utils::is_laptop_panel;
use crate::utils::scale::supported_scales; use crate::utils::scale::supported_scales;
pub struct DisplayConfig { pub struct DisplayConfig {
to_niri: calloop::channel::Sender<HashMap<String, Option<niri_config::Output>>>,
ipc_outputs: Arc<Mutex<IpcOutputMap>>, ipc_outputs: Arc<Mutex<IpcOutputMap>>,
} }
@@ -46,6 +48,17 @@ pub struct LogicalMonitor {
properties: HashMap<String, OwnedValue>, properties: HashMap<String, OwnedValue>,
} }
// ApplyMonitorsConfig
#[derive(Deserialize, Type)]
pub struct LogicalMonitorConfiguration {
x: i32,
y: i32,
scale: f64,
transform: u32,
_is_primary: bool,
monitors: Vec<(String, String, HashMap<String, OwnedValue>)>,
}
#[interface(name = "org.gnome.Mutter.DisplayConfig")] #[interface(name = "org.gnome.Mutter.DisplayConfig")]
impl DisplayConfig { impl DisplayConfig {
async fn get_current_state( async fn get_current_state(
@@ -158,6 +171,87 @@ impl DisplayConfig {
Ok((0, monitors, logical_monitors, properties)) Ok((0, monitors, logical_monitors, properties))
} }
async fn apply_monitors_config(
&self,
_serial: u32,
method: u32,
logical_monitor_configs: Vec<LogicalMonitorConfiguration>,
_properties: HashMap<String, OwnedValue>,
) -> 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)] #[zbus(signal)]
pub async fn monitors_changed(ctxt: &SignalEmitter<'_>) -> zbus::Result<()>; pub async fn monitors_changed(ctxt: &SignalEmitter<'_>) -> zbus::Result<()>;
@@ -188,8 +282,14 @@ impl DisplayConfig {
} }
impl DisplayConfig { impl DisplayConfig {
pub fn new(ipc_outputs: Arc<Mutex<IpcOutputMap>>) -> Self { pub fn new(
Self { ipc_outputs } to_niri: calloop::channel::Sender<HashMap<String, Option<niri_config::Output>>>,
ipc_outputs: Arc<Mutex<IpcOutputMap>>,
) -> Self {
Self {
to_niri,
ipc_outputs,
}
} }
} }
+73 -70
View File
@@ -1343,82 +1343,85 @@ impl State {
self.niri.output_management_state.on_config_changed(config); 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<F>(&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::<OutputName>().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 temp = output_name;
// user. Since if we add a new section and some existing section also matches the &temp
// output, then our new section won't do anything. } else {
let temp; // Even if name is "make model serial", matching will work fine this way.
let match_name = if let Some(output) = self.niri.output_by_name_match(name) { temp = OutputName {
output.user_data().get::<OutputName>().unwrap() connector: name.to_owned(),
} else if let Some(output_name) = self make: None,
.backend model: None,
.tty_checked() serial: None,
.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
};
let mut config = self.niri.config.borrow_mut(); let mut config = self.niri.config.borrow_mut();
let config = if let Some(config) = config.outputs.find_mut(match_name) { let config = if let Some(config) = config.outputs.find_mut(match_name) {
config config
} else { } else {
config.outputs.0.push(niri_config::Output { config.outputs.0.push(niri_config::Output {
// Save name as set by the user. // Save name as set by the user.
name: String::from(name), name: String::from(name),
..Default::default() ..Default::default()
}); });
config.outputs.0.last_mut().unwrap() config.outputs.0.last_mut().unwrap()
}; };
match action { fun(config);
niri_ipc::OutputAction::Off => config.off = true, }
niri_ipc::OutputAction::On => config.off = false,
niri_ipc::OutputAction::Mode { mode } => { pub fn apply_transient_output_config(&mut self, name: &str, action: niri_ipc::OutputAction) {
config.mode = match mode { self.modify_output_config(name, move |config| match action {
niri_ipc::ModeToSet::Automatic => None, niri_ipc::OutputAction::Off => config.off = true,
niri_ipc::ModeToSet::Specific(mode) => Some(mode), niri_ipc::OutputAction::On => config.off = false,
} niri_ipc::OutputAction::Mode { mode } => {
} config.mode = match mode {
niri_ipc::OutputAction::Scale { scale } => { niri_ipc::ModeToSet::Automatic => None,
config.scale = match scale { niri_ipc::ModeToSet::Specific(mode) => Some(mode),
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::Vrr { vrr } => {
config.variable_refresh_rate = if vrr.vrr {
Some(niri_config::Vrr {
on_demand: vrr.on_demand,
})
} else {
None
}
} }
} }
} 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::Vrr { vrr } => {
config.variable_refresh_rate = if vrr.vrr {
Some(niri_config::Vrr {
on_demand: vrr.on_demand,
})
} else {
None
}
}
});
self.reload_output_config(); self.reload_output_config();
} }