mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-22 02:01:55 +07:00
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:
committed by
Ivan Molodetskikh
parent
b853d5b124
commit
890bbff007
+19
-1
@@ -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());
|
||||||
|
|||||||
@@ -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
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user