Skip to content

Commit

Permalink
dbus: DisplayConfig: implement apply_monitors_config
Browse files Browse the repository at this point in the history
This enables gnome-control-center to apply display configuration
changes. Only temporarily, persistence is ignored currently.
  • Loading branch information
valpackett committed Jan 5, 2025
1 parent 74f2509 commit 2b56418
Show file tree
Hide file tree
Showing 3 changed files with 194 additions and 71 deletions.
20 changes: 19 additions & 1 deletion src/dbus/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
109 changes: 106 additions & 3 deletions src/dbus/mutter_display_config.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -13,6 +14,7 @@ use crate::backend::IpcOutputMap;
use crate::utils::{is_laptop_panel, scale::supported_scales};

pub struct DisplayConfig {
to_niri: calloop::channel::Sender<HashMap<String, Option<niri_config::Output>>>,
ipc_outputs: Arc<Mutex<IpcOutputMap>>,
}

Expand Down Expand Up @@ -45,6 +47,17 @@ pub struct LogicalMonitor {
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")]
impl DisplayConfig {
async fn get_current_state(
Expand Down Expand Up @@ -157,6 +170,90 @@ impl DisplayConfig {
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()
.find(|o| o.name == connector)
.is_none()
{
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
))
})?),
..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<()>;

Expand Down Expand Up @@ -189,8 +286,14 @@ impl DisplayConfig {
}

impl DisplayConfig {
pub fn new(ipc_outputs: Arc<Mutex<IpcOutputMap>>) -> Self {
Self { ipc_outputs }
pub fn new(
to_niri: calloop::channel::Sender<HashMap<String, Option<niri_config::Output>>>,
ipc_outputs: Arc<Mutex<IpcOutputMap>>,
) -> Self {
Self {
to_niri,
ipc_outputs,
}
}
}

Expand Down
136 changes: 69 additions & 67 deletions src/niri.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1342,82 +1342,84 @@ 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<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
// 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))
{
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()
};
fun(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),
}
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();
}
Expand Down

0 comments on commit 2b56418

Please sign in to comment.