// SPDX-License-Identifier: GPL-3.0-only use cosmic_protocols::output_management::v1::server::{ zcosmic_output_configuration_head_v1::ZcosmicOutputConfigurationHeadV1, zcosmic_output_configuration_v1::ZcosmicOutputConfigurationV1, zcosmic_output_head_v1::ZcosmicOutputHeadV1, zcosmic_output_manager_v1::ZcosmicOutputManagerV1, }; use smithay::{ output::{Mode, Output}, reexports::{ wayland_protocols_wlr::output_management::v1::server::{ zwlr_output_configuration_head_v1::{self, ZwlrOutputConfigurationHeadV1}, zwlr_output_configuration_v1::ZwlrOutputConfigurationV1, zwlr_output_head_v1::{self, ZwlrOutputHeadV1}, zwlr_output_manager_v1::ZwlrOutputManagerV1, zwlr_output_mode_v1::{self, ZwlrOutputModeV1}, }, wayland_server::{ backend::GlobalId, protocol::wl_output::WlOutput, Client, Dispatch, DisplayHandle, GlobalDispatch, Resource, Weak, }, }, utils::{Logical, Physical, Point, Size, Transform}, wayland::output::WlOutputData, }; use std::{ convert::TryFrom, sync::{ atomic::{AtomicBool, Ordering}, Arc, Mutex, }, }; mod handlers; #[derive(Debug)] pub struct OutputConfigurationState { outputs: Vec, removed_outputs: Vec, instances: Vec, extension_instances: Vec, serial_counter: u32, global: GlobalId, extension_global: GlobalId, dh: DisplayHandle, _dispatch: std::marker::PhantomData, } pub trait OutputConfigurationHandler: Sized { fn output_configuration_state(&mut self) -> &mut OutputConfigurationState; fn test_configuration(&mut self, conf: Vec<(Output, OutputConfiguration)>) -> bool; fn apply_configuration(&mut self, conf: Vec<(Output, OutputConfiguration)>) -> bool; } pub struct OutputMngrGlobalData { filter: Box Fn(&'a Client) -> bool + Send + Sync>, } #[derive(Debug)] struct OutputMngrInstance { obj: ZwlrOutputManagerV1, active: Arc, heads: Vec, stale_modes: Vec, } #[derive(Debug)] struct OutputHeadInstance { obj: ZwlrOutputHeadV1, extension_obj: Option, output: Output, modes: Vec, finished: bool, } pub struct OutputMngrInstanceData { active: Arc, } #[derive(Debug, Default)] pub struct PendingConfigurationInner { extension_obj: Option, serial: u32, used: bool, heads: Vec<(ZwlrOutputHeadV1, Option)>, } pub type PendingConfiguration = Mutex; #[derive(Debug, Clone)] pub enum ModeConfiguration { Mode(M), Custom { size: Size, refresh: Option, }, } #[derive(Debug, Default, Clone)] pub struct PendingOutputConfigurationInner { extension_obj: Option, mirroring: Option, mode: Option>, position: Option>, transform: Option, scale: Option, adaptive_sync: Option, } pub type PendingOutputConfiguration = Mutex; #[derive(Debug, Clone)] pub enum OutputConfiguration { Enabled { mirroring: Option, mode: Option>, position: Option>, transform: Option, scale: Option, adaptive_sync: Option, }, Disabled, } impl<'a> TryFrom<&'a mut PendingOutputConfigurationInner> for OutputConfiguration { type Error = zwlr_output_configuration_head_v1::Error; fn try_from( pending: &'a mut PendingOutputConfigurationInner, ) -> Result { let mode = match pending.mode.clone() { Some(ModeConfiguration::Mode(wlr_mode)) => Some(ModeConfiguration::Mode( wlr_mode .data::() .cloned() .ok_or_else(|| zwlr_output_configuration_head_v1::Error::InvalidMode)?, )), Some(ModeConfiguration::Custom { size, refresh }) => { Some(ModeConfiguration::Custom { size, refresh }) } None => None, }; Ok(OutputConfiguration::Enabled { mode, mirroring: pending.mirroring.clone(), position: pending.position, transform: pending.transform, scale: pending.scale, adaptive_sync: pending.adaptive_sync, }) } } struct OutputStateInner { enabled: bool, global: Option, } type OutputState = Mutex; impl OutputConfigurationState where D: GlobalDispatch + GlobalDispatch + Dispatch + Dispatch + Dispatch + Dispatch + Dispatch + GlobalDispatch + Dispatch + Dispatch> + Dispatch> + Dispatch> + OutputConfigurationHandler + 'static, { pub fn new(dh: &DisplayHandle, client_filter: F) -> OutputConfigurationState where F: for<'a> Fn(&'a Client) -> bool + Clone + Send + Sync + 'static, { let global = dh.create_global::( 4, OutputMngrGlobalData { filter: Box::new(client_filter.clone()), }, ); let extension_global = dh.create_global::( 1, OutputMngrGlobalData { filter: Box::new(client_filter), }, ); OutputConfigurationState { outputs: Vec::new(), removed_outputs: Vec::new(), instances: Vec::new(), extension_instances: Vec::new(), serial_counter: 0, global, extension_global, dh: dh.clone(), _dispatch: std::marker::PhantomData, } } pub fn global_id(&self) -> GlobalId { self.global.clone() } pub fn extension_global_id(&self) -> GlobalId { self.extension_global.clone() } pub fn add_heads<'a>(&mut self, outputs: impl Iterator) { let new_outputs = outputs .filter(|o| !self.outputs.contains(o)) .collect::>(); for output in new_outputs { output.user_data().insert_if_missing(|| { OutputState::new(OutputStateInner { enabled: true, global: None, }) }); self.outputs.push(output.clone()); } } pub fn remove_heads<'a>(&mut self, outputs: impl Iterator) { for output in outputs { if self.outputs.contains(output) { self.removed_outputs.push(output.clone()); if let Some(inner) = output.user_data().get::() { let mut inner = inner.lock().unwrap(); // if it gets re-added it should start with being enabled and no global inner.enabled = true; if let Some(global) = inner.global.take() { self.dh.remove_global::(global); } } } } self.outputs.retain(|x| !self.removed_outputs.contains(x)); } pub fn enable_head(&self, output: &Output) { if let Some(inner) = output.user_data().get::() { let mut inner = inner.lock().unwrap(); inner.enabled = true; } } pub fn disable_head(&self, output: &Output) { if let Some(inner) = output.user_data().get::() { let mut inner = inner.lock().unwrap(); inner.enabled = false; } } pub fn update(&mut self) { self.instances.retain(|x| x.active.load(Ordering::SeqCst)); self.serial_counter += 1; for output in std::mem::take(&mut self.removed_outputs).into_iter() { for instance in &mut self.instances { let mut removed_heads = Vec::new(); for head in &mut instance.heads { if &head.output == &output { if head.obj.version() < zwlr_output_head_v1::REQ_RELEASE_SINCE { removed_heads.push(head.obj.clone()); } for mode in &mut head.modes { mode.finished(); if mode.version() < zwlr_output_mode_v1::REQ_RELEASE_SINCE { // on >=v3 we keep the obj around until we get a release-request // otherwise we will drop this with the head instance.stale_modes.push(mode.clone()); } } head.obj.finished(); head.finished = true; } } instance.heads.retain(|h| !removed_heads.contains(&h.obj)) } } for output in &self.outputs { { let state = output.user_data().get::().unwrap(); let mut inner = state.lock().unwrap(); if inner.enabled && inner.global.is_none() { inner.global = Some(output.create_global::(&self.dh)); } if !inner.enabled && inner.global.is_some() { self.dh.remove_global::(inner.global.take().unwrap()); } } for manager in self.instances.iter_mut() { send_head_to_client::(&self.dh, manager, output); } } for manager in self.instances.iter() { manager.obj.done(self.serial_counter); } } pub fn outputs(&self) -> impl Iterator { self.outputs.clone().into_iter() } } fn send_head_to_client(dh: &DisplayHandle, mngr: &mut OutputMngrInstance, output: &Output) where D: GlobalDispatch + Dispatch + Dispatch + Dispatch + Dispatch + OutputConfigurationHandler + 'static, { let instance = match mngr .heads .iter_mut() .filter(|i| !i.finished) .find(|i| i.output == *output) { Some(i) => i, None => { if let Ok(client) = dh.get_client(mngr.obj.id()) { if let Ok(head) = client.create_resource::( dh, mngr.obj.version(), output.clone(), ) { mngr.obj.head(&head); let data = OutputHeadInstance { obj: head, extension_obj: None, modes: Vec::new(), output: output.clone(), finished: false, }; mngr.heads.push(data); mngr.heads.last_mut().unwrap() } else { return; } } else { return; } } }; instance.obj.name(output.name()); instance.obj.description(output.description()); let physical = output.physical_properties(); if !(physical.size.w == 0 || physical.size.h == 0) { instance.obj.physical_size(physical.size.w, physical.size.h); } let inner = output .user_data() .get::() .unwrap() .lock() .unwrap(); let output_modes = output.modes(); // remove old modes instance.modes.retain_mut(|m| { if !output_modes.contains(m.data::().unwrap()) { m.finished(); if m.version() < zwlr_output_mode_v1::REQ_RELEASE_SINCE { // on >=v3 we keep the obj around until we get a release-request mngr.stale_modes.push(m.clone()); } false } else { true } }); // update other modes for output_mode in output_modes.into_iter() { if let Some(mode) = if let Some(wlr_mode) = instance .modes .iter() .find(|mode| *mode.data::().unwrap() == output_mode) { Some(wlr_mode) } else if let Ok(client) = dh.get_client(instance.obj.id()) { // create the mode if it does not exist yet if let Ok(mode) = client.create_resource::( dh, instance.obj.version(), output_mode, ) { instance.obj.mode(&mode); mode.size(output_mode.size.w, output_mode.size.h); mode.refresh(output_mode.refresh); if output .preferred_mode() .map(|p| p == output_mode) .unwrap_or(false) { mode.preferred(); } instance.modes.push(mode); instance.modes.last() } else { None } } else { None } { if inner.enabled && output .current_mode() .map(|c| c == output_mode) .unwrap_or(false) { instance.obj.current_mode(&*mode); } } } instance.obj.enabled(if inner.enabled { 1 } else { 0 }); if inner.enabled { let point = output.current_location(); instance.obj.position(point.x, point.y); instance.obj.transform(output.current_transform().into()); let scale = output.current_scale().fractional_scale(); instance.obj.scale(scale); if let Some(extension_obj) = instance.extension_obj.as_ref() { extension_obj.scale_1000((scale * 1000.0).round() as i32); extension_obj.mirroring(output.mirroring().map(|o| o.name())); } if instance.obj.version() >= zwlr_output_head_v1::EVT_ADAPTIVE_SYNC_SINCE { instance.obj.adaptive_sync(if output.adaptive_sync() { zwlr_output_head_v1::AdaptiveSyncState::Enabled } else { zwlr_output_head_v1::AdaptiveSyncState::Disabled }); } } if instance.obj.version() >= zwlr_output_head_v1::EVT_MAKE_SINCE { if physical.make != "Unknown" { instance.obj.make(physical.make.clone()); } if physical.model != "Unknown" { instance.obj.model(physical.model); } } } macro_rules! delegate_output_configuration { ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ smithay::reexports::wayland_protocols_wlr::output_management::v1::server::zwlr_output_manager_v1::ZwlrOutputManagerV1: $crate::wayland::protocols::output_configuration::OutputMngrGlobalData ] => $crate::wayland::protocols::output_configuration::OutputConfigurationState); smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ smithay::reexports::wayland_protocols_wlr::output_management::v1::server::zwlr_output_manager_v1::ZwlrOutputManagerV1: $crate::wayland::protocols::output_configuration::OutputMngrInstanceData ] => $crate::wayland::protocols::output_configuration::OutputConfigurationState); smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ smithay::reexports::wayland_protocols_wlr::output_management::v1::server::zwlr_output_head_v1::ZwlrOutputHeadV1: smithay::output::Output ] => $crate::wayland::protocols::output_configuration::OutputConfigurationState); smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ smithay::reexports::wayland_protocols_wlr::output_management::v1::server::zwlr_output_mode_v1::ZwlrOutputModeV1: smithay::output::Mode ] => $crate::wayland::protocols::output_configuration::OutputConfigurationState); smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ smithay::reexports::wayland_protocols_wlr::output_management::v1::server::zwlr_output_configuration_v1::ZwlrOutputConfigurationV1: $crate::wayland::protocols::output_configuration::PendingConfiguration ] => $crate::wayland::protocols::output_configuration::OutputConfigurationState); smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ smithay::reexports::wayland_protocols_wlr::output_management::v1::server::zwlr_output_configuration_head_v1::ZwlrOutputConfigurationHeadV1: $crate::wayland::protocols::output_configuration::PendingOutputConfiguration ] => $crate::wayland::protocols::output_configuration::OutputConfigurationState); smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ cosmic_protocols::output_management::v1::server::zcosmic_output_manager_v1::ZcosmicOutputManagerV1: $crate::wayland::protocols::output_configuration::OutputMngrGlobalData ] => $crate::wayland::protocols::output_configuration::OutputConfigurationState); smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ cosmic_protocols::output_management::v1::server::zcosmic_output_manager_v1::ZcosmicOutputManagerV1: () ] => $crate::wayland::protocols::output_configuration::OutputConfigurationState); smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ cosmic_protocols::output_management::v1::server::zcosmic_output_head_v1::ZcosmicOutputHeadV1: smithay::reexports::wayland_server::Weak ] => $crate::wayland::protocols::output_configuration::OutputConfigurationState); smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ cosmic_protocols::output_management::v1::server::zcosmic_output_configuration_v1::ZcosmicOutputConfigurationV1: smithay::reexports::wayland_server::Weak ] => $crate::wayland::protocols::output_configuration::OutputConfigurationState); smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ cosmic_protocols::output_management::v1::server::zcosmic_output_configuration_head_v1::ZcosmicOutputConfigurationHeadV1: smithay::reexports::wayland_server::Weak ] => $crate::wayland::protocols::output_configuration::OutputConfigurationState); }; } pub(crate) use delegate_output_configuration; use crate::utils::prelude::OutputExt;