cosmic-comp/src/wayland/handlers/output_configuration.rs
2025-08-26 11:30:56 -04:00

237 lines
8.7 KiB
Rust

// SPDX-License-Identifier: GPL-3.0-only
use cosmic_comp_config::output::comp::{OutputConfig, OutputState, TransformDef};
use smithay::{output::Output, utils::Point};
use tracing::{error, warn};
use crate::{
state::State,
utils::prelude::OutputExt,
wayland::protocols::output_configuration::{
delegate_output_configuration, ModeConfiguration, OutputConfiguration,
OutputConfigurationHandler, OutputConfigurationState,
},
};
use std::cell::RefCell;
impl OutputConfigurationHandler for State {
fn output_configuration_state(&mut self) -> &mut OutputConfigurationState<Self> {
&mut self.common.output_configuration_state
}
fn test_configuration(&mut self, conf: Vec<(Output, OutputConfiguration)>) -> bool {
self.output_configuration(true, conf)
}
fn apply_configuration(&mut self, conf: Vec<(Output, OutputConfiguration)>) -> bool {
self.output_configuration(false, conf)
}
fn request_xwayland_primary(&mut self, primary_output: Option<Output>) {
for output in self.common.output_configuration_state.outputs() {
output.config_mut().xwayland_primary =
primary_output.as_ref().is_some_and(|o| *o == output);
}
self.common.update_xwayland_primary_output();
self.common
.config
.write_outputs(self.common.output_configuration_state.outputs());
}
}
impl State {
fn output_configuration(
&mut self,
test_only: bool,
mut conf: Vec<(Output, OutputConfiguration)>,
) -> bool {
if conf
.iter()
.all(|(_, conf)| matches!(conf, OutputConfiguration::Disabled))
{
return false; // we don't allow the user to accidentally disable all their outputs
}
// sanitize negative positions
{
let (offset_x, offset_y) = conf.iter().fold((0, 0), |mut offset, (_, conf)| {
if let OutputConfiguration::Enabled {
position: Some(position),
..
} = conf
{
if position.x.is_negative() {
offset.0 = offset.0.max(position.x.abs());
}
if position.y.is_negative() {
offset.1 = offset.1.max(position.y.abs());
}
}
offset
});
if offset_x > 0 || offset_y > 0 {
for (output, conf) in conf.iter_mut() {
if let OutputConfiguration::Enabled { position, .. } = conf {
let current_config = output
.user_data()
.get::<RefCell<OutputConfig>>()
.unwrap()
.borrow();
*position = Some(
position.unwrap_or(Point::from((
current_config.position.0 as i32,
current_config.position.1 as i32,
))) + Point::from((offset_x, offset_y)),
);
}
}
}
}
let mut backups = Vec::new();
for (output, conf) in &conf {
{
let mut current_config = output
.user_data()
.get::<RefCell<OutputConfig>>()
.unwrap()
.borrow_mut();
backups.push((output, current_config.clone()));
if let OutputConfiguration::Enabled {
mirroring,
mode,
scale,
transform,
position,
adaptive_sync,
} = conf
{
match mode {
Some(ModeConfiguration::Mode(mode)) => {
current_config.mode =
((mode.size.w, mode.size.h), Some(mode.refresh as u32));
}
Some(ModeConfiguration::Custom { size, refresh }) => {
current_config.mode = ((size.w, size.h), refresh.map(|x| x as u32));
}
_ => {}
}
if let Some(scale) = scale {
current_config.scale = *scale;
}
if let Some(transform) = transform {
current_config.transform = match transform {
smithay::utils::Transform::Normal => TransformDef::Normal,
smithay::utils::Transform::_90 => TransformDef::_90,
smithay::utils::Transform::_180 => TransformDef::_180,
smithay::utils::Transform::_270 => TransformDef::_270,
smithay::utils::Transform::Flipped => TransformDef::Flipped,
smithay::utils::Transform::Flipped90 => TransformDef::Flipped90,
smithay::utils::Transform::Flipped180 => TransformDef::Flipped180,
smithay::utils::Transform::Flipped270 => TransformDef::Flipped270,
}
}
if let Some(position) = position {
current_config.position = (position.x as u32, position.y as u32);
}
if let Some(vrr) = adaptive_sync {
current_config.vrr = *vrr;
}
if let Some(mirror) = mirroring {
current_config.enabled = OutputState::Mirroring(mirror.name());
} else {
current_config.enabled = OutputState::Enabled;
}
} else {
current_config.enabled = OutputState::Disabled;
}
}
}
let mut backend = self.backend.lock();
let res = backend.apply_config_for_outputs(
test_only,
&self.common.event_loop_handle,
self.common.config.dynamic_conf.screen_filter(),
self.common.shell.clone(),
&mut self.common.workspace_state.update(),
&self.common.xdg_activation_state,
self.common.startup_done.clone(),
&self.common.clock,
);
if let Err(err) = res {
warn!("Failed to apply config. Resetting: {:?}", err);
for (output, backup) in backups {
{
let mut current_config = output
.user_data()
.get::<RefCell<OutputConfig>>()
.unwrap()
.borrow_mut();
*current_config = backup;
}
}
if !test_only {
if let Err(err) = backend.apply_config_for_outputs(
false,
&self.common.event_loop_handle,
self.common.config.dynamic_conf.screen_filter(),
self.common.shell.clone(),
&mut self.common.workspace_state.update(),
&self.common.xdg_activation_state,
self.common.startup_done.clone(),
&self.common.clock,
) {
error!("Failed to reset output config: {:?}", err);
}
}
return false;
}
self.common.refresh();
for output in conf
.iter()
.filter(|(_, c)| {
matches!(
c,
OutputConfiguration::Enabled {
mirroring: None,
..
}
)
})
.map(|(o, _)| o)
{
self.common.output_configuration_state.enable_head(output);
}
for output in conf
.iter()
.filter(|(_, c)| {
matches!(
c,
OutputConfiguration::Disabled
| OutputConfiguration::Enabled {
mirroring: Some(_),
..
}
)
})
.map(|(o, _)| o)
{
self.common.output_configuration_state.disable_head(output);
}
self.common
.config
.write_outputs(self.common.output_configuration_state.outputs());
self.common.event_loop_handle.insert_idle(move |state| {
state.common.output_configuration_state.update();
});
true
}
}
delegate_output_configuration!(State);