diff --git a/Cargo.lock b/Cargo.lock index ec126bf9..092a0249 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -929,11 +929,12 @@ dependencies = [ [[package]] name = "cosmic-protocols" version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-protocols?branch=main#1cc4a1393d0f8be4d444666e260fdb811b400f49" +source = "git+https://github.com/pop-os/cosmic-protocols?branch=main#1316f9e1148ec65351471d8a046ffc82171b066e" dependencies = [ "bitflags 2.4.2", "wayland-backend", "wayland-protocols", + "wayland-protocols-wlr", "wayland-scanner", "wayland-server", ] @@ -1082,7 +1083,7 @@ version = "0.19.0" source = "git+https://github.com/gfx-rs/wgpu?rev=20fda69#20fda698341efbdc870b8027d6d49f5bf3f36109" dependencies = [ "bitflags 2.4.2", - "libloading 0.8.1", + "libloading 0.7.4", "winapi", ] @@ -1233,7 +1234,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ - "libloading 0.8.1", + "libloading 0.7.4", ] [[package]] @@ -2167,7 +2168,7 @@ dependencies = [ "bitflags 2.4.2", "com", "libc", - "libloading 0.8.1", + "libloading 0.7.4", "thiserror", "widestring", "winapi", @@ -5749,7 +5750,7 @@ dependencies = [ "js-sys", "khronos-egl", "libc", - "libloading 0.8.1", + "libloading 0.7.4", "log", "metal", "naga", diff --git a/src/state.rs b/src/state.rs index d175d88a..a23c0496 100644 --- a/src/state.rs +++ b/src/state.rs @@ -2,7 +2,7 @@ use crate::{ backend::{kms::KmsState, winit::WinitState, x11::X11State}, - config::{Config, OutputConfig}, + config::{Config, OutputConfig, OutputState}, input::gestures::GestureState, shell::{grabs::SeatMoveGrabState, CosmicSurface, SeatExt, Shell}, utils::prelude::OutputExt, @@ -299,6 +299,13 @@ impl BackendData { output.change_current_state(mode, transform, scale.map(Scale::Fractional), location); output.set_adaptive_sync(final_config.vrr); + output.set_mirroring(match &final_config.enabled { + OutputState::Mirroring(conn) => shell + .outputs() + .find(|output| &output.name() == conn) + .cloned(), + _ => None, + }); layer_map_for_output(output).arrange(); } diff --git a/src/utils/prelude.rs b/src/utils/prelude.rs index 052e5d7b..dd3fe8e7 100644 --- a/src/utils/prelude.rs +++ b/src/utils/prelude.rs @@ -1,5 +1,5 @@ use smithay::{ - output::Output, + output::{Output, WeakOutput}, utils::{Rectangle, Transform}, }; @@ -8,16 +8,23 @@ pub use crate::shell::{SeatExt, Shell, Workspace}; pub use crate::state::{Common, State}; pub use crate::wayland::handlers::xdg_shell::popup::update_reactive_popups; -use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Mutex, +}; pub trait OutputExt { fn geometry(&self) -> Rectangle; fn adaptive_sync(&self) -> bool; fn set_adaptive_sync(&self, vrr: bool); + fn mirroring(&self) -> Option; + fn set_mirroring(&self, output: Option); } struct Vrr(AtomicBool); +struct Mirroring(Mutex>); + impl OutputExt for Output { fn geometry(&self) -> Rectangle { Rectangle::from_loc_and_size(self.current_location(), { @@ -49,4 +56,21 @@ impl OutputExt for Output { .0 .store(vrr, Ordering::SeqCst); } + + fn mirroring(&self) -> Option { + self.user_data().get::().and_then(|mirroring| { + mirroring + .0 + .lock() + .unwrap() + .clone() + .and_then(|o| o.upgrade()) + }) + } + fn set_mirroring(&self, output: Option) { + let user_data = self.user_data(); + user_data.insert_if_missing_threadsafe(|| Mirroring(Mutex::new(None))); + *user_data.get::().unwrap().0.lock().unwrap() = + output.map(|output| output.downgrade()); + } } diff --git a/src/wayland/handlers/output_configuration.rs b/src/wayland/handlers/output_configuration.rs index 6523ca1d..73d7b9a1 100644 --- a/src/wayland/handlers/output_configuration.rs +++ b/src/wayland/handlers/output_configuration.rs @@ -51,6 +51,7 @@ impl State { backups.push((output, current_config.clone())); if let OutputConfiguration::Enabled { + mirroring, mode, scale, transform, @@ -80,7 +81,11 @@ impl State { if let Some(vrr) = adaptive_sync { current_config.vrr = *vrr; } - current_config.enabled = OutputState::Enabled; + if let Some(mirror) = mirroring { + current_config.enabled = OutputState::Mirroring(mirror.name()); + } else { + current_config.enabled = OutputState::Enabled; + } } else { current_config.enabled = OutputState::Disabled; } diff --git a/src/wayland/protocols/output_configuration/handlers/cosmic.rs b/src/wayland/protocols/output_configuration/handlers/cosmic.rs new file mode 100644 index 00000000..04dfcb54 --- /dev/null +++ b/src/wayland/protocols/output_configuration/handlers/cosmic.rs @@ -0,0 +1,276 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use smithay::{ + output::{Mode, Output}, + reexports::{ + wayland_protocols_wlr::output_management::v1::server::{ + zwlr_output_configuration_head_v1::{self, ZwlrOutputConfigurationHeadV1}, + zwlr_output_configuration_v1::{self, ZwlrOutputConfigurationV1}, + zwlr_output_head_v1::ZwlrOutputHeadV1, + }, + wayland_server::{ + Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, Weak, + }, + }, +}; + +use cosmic_protocols::output_management::v1::server::{ + zcosmic_output_configuration_head_v1::{self, ZcosmicOutputConfigurationHeadV1}, + zcosmic_output_configuration_v1::{self, ZcosmicOutputConfigurationV1}, + zcosmic_output_head_v1::{self, ZcosmicOutputHeadV1}, + zcosmic_output_manager_v1::{self, ZcosmicOutputManagerV1}, +}; + +use crate::wayland::protocols::output_configuration::*; + +impl GlobalDispatch + for OutputConfigurationState +where + D: GlobalDispatch + + Dispatch + + Dispatch> + + Dispatch> + + Dispatch> + + OutputConfigurationHandler + + 'static, +{ + fn bind( + state: &mut D, + _dh: &DisplayHandle, + _client: &Client, + resource: New, + _global_data: &OutputMngrGlobalData, + data_init: &mut DataInit<'_, D>, + ) { + let obj = data_init.init(resource, ()); + let mngr_state = state.output_configuration_state(); + mngr_state.extension_instances.push(obj); + } + + fn can_view(client: Client, global_data: &OutputMngrGlobalData) -> bool { + (global_data.filter)(&client) + } +} + +impl Dispatch for OutputConfigurationState +where + D: GlobalDispatch + + Dispatch + + Dispatch + + Dispatch + + Dispatch + + Dispatch + + GlobalDispatch + + Dispatch + + Dispatch> + + Dispatch> + + Dispatch> + + OutputConfigurationHandler + + 'static, +{ + fn request( + state: &mut D, + _client: &Client, + _obj: &ZcosmicOutputManagerV1, + request: zcosmic_output_manager_v1::Request, + _data: &(), + dh: &DisplayHandle, + data_init: &mut DataInit<'_, D>, + ) { + match request { + zcosmic_output_manager_v1::Request::GetHead { extended, head } => { + let inner = state.output_configuration_state(); + if let Some(mngr) = inner + .instances + .iter_mut() + .find(|instance| instance.heads.iter().any(|instance| instance.obj == head)) + { + let head_data = mngr + .heads + .iter_mut() + .find(|instance| instance.obj == head) + .unwrap(); + let obj = data_init.init(extended, head.downgrade()); + head_data.extension_obj = Some(obj); + let output = head_data.output.clone(); + + send_head_to_client::(dh, mngr, &output); + for manager in inner.instances.iter() { + manager.obj.done(inner.serial_counter); + } + } + } + zcosmic_output_manager_v1::Request::GetConfiguration { extended, config } => { + if let Some(pending) = config.data::() { + let obj = data_init.init(extended, config.downgrade()); + pending.lock().unwrap().extension_obj = Some(obj); + } + } + zcosmic_output_manager_v1::Request::GetConfigurationHead { + extended, + config_head, + } => { + if let Some(pending) = config_head.data::() { + let obj = data_init.init(extended, config_head.downgrade()); + pending.lock().unwrap().extension_obj = Some(obj); + } + } + _ => {} + } + } +} + +impl Dispatch, D> for OutputConfigurationState { + fn request( + _state: &mut D, + _client: &Client, + _obj: &ZcosmicOutputHeadV1, + _request: zcosmic_output_head_v1::Request, + _data: &Weak, + _dh: &DisplayHandle, + _data_init: &mut DataInit<'_, D>, + ) { + } +} + +impl Dispatch, D> + for OutputConfigurationState +where + D: GlobalDispatch + + Dispatch + + Dispatch + + Dispatch + + Dispatch + + Dispatch + + GlobalDispatch + + Dispatch + + Dispatch> + + Dispatch> + + Dispatch> + + OutputConfigurationHandler + + 'static, +{ + fn request( + _state: &mut D, + _client: &Client, + extension_obj: &ZcosmicOutputConfigurationV1, + request: zcosmic_output_configuration_v1::Request, + obj: &Weak, + _dh: &DisplayHandle, + data_init: &mut DataInit<'_, D>, + ) { + match request { + zcosmic_output_configuration_v1::Request::MirrorHead { + id, + head, + mirroring, + } => { + if let Ok(obj) = obj.upgrade() { + if let Some(data) = obj.data::() { + if let Some(output) = mirroring.data::() { + let mut pending = data.lock().unwrap(); + if pending.heads.iter().any(|(h, _)| *h == head) { + obj.post_error( + zwlr_output_configuration_v1::Error::AlreadyConfiguredHead, + format!("{:?} was already configured", head), + ); + return; + } + + if pending.heads.iter().any(|(h, c)| { + match c.as_ref() { + Some(c) => { + if let Some(conf) = c.data::() { + match conf.lock().unwrap().mirroring.as_ref() { + Some(mirrored) => { + head.data::().is_some_and(|o| o == mirrored) // we are already a mirror target -> invalid + || *h == mirroring // our target already mirrors -> invalid + } + None => false, + } + } else { + *h == mirroring // unknown state for our mirror target -> invalid + } + } + None => *h == mirroring, // disabled state for our mirror target -> invalid + } + }) { + extension_obj.post_error( + zcosmic_output_configuration_v1::Error::MirroredHeadBusy, + format!("{:?} can't mirror, it is either a mirror target itself or {:?} is not enabled/already mirroring", head, mirroring), + ); + } + + let output_conf = PendingOutputConfiguration::default(); + output_conf.lock().unwrap().mirroring = Some(output.clone()); + let conf_head = data_init.init(id, output_conf); + pending.heads.push((head, Some(conf_head))); + } + } + } + } + zcosmic_output_configuration_v1::Request::Destroy => { + if let Ok(obj) = obj.upgrade() { + if let Some(data) = obj.data::() { + let mut pending = data.lock().unwrap(); + let _ = pending.extension_obj.take(); + pending.heads.retain(|(_, conf)| match conf { + Some(head) => { + if let Some(data) = head.data::() { + let output_conf = data.lock().unwrap(); + output_conf.mirroring.is_none() + } else { + true + } + } + None => true, + }) + } + } + } + _ => {} + } + } +} + +impl Dispatch, D> + for OutputConfigurationState +where + D: GlobalDispatch + + Dispatch + + Dispatch + + Dispatch + + Dispatch + + Dispatch + + OutputConfigurationHandler + + 'static, +{ + fn request( + _state: &mut D, + _client: &Client, + _extended_obj: &ZcosmicOutputConfigurationHeadV1, + request: zcosmic_output_configuration_head_v1::Request, + obj: &Weak, + _dh: &DisplayHandle, + _data_init: &mut DataInit<'_, D>, + ) { + match request { + zcosmic_output_configuration_head_v1::Request::SetScale1000 { scale_1000 } => { + if let Ok(obj) = obj.upgrade() { + if let Some(data) = obj.data::() { + let mut pending = data.lock().unwrap(); + if pending.scale.is_some() { + obj.post_error( + zwlr_output_configuration_head_v1::Error::AlreadySet, + format!("{:?} already had a scale configured", obj), + ); + return; + } + pending.scale = Some((scale_1000 as f64) / 1000.0); + } + } + } + _ => {} + } + } +} diff --git a/src/wayland/protocols/output_configuration/handlers/mod.rs b/src/wayland/protocols/output_configuration/handlers/mod.rs new file mode 100644 index 00000000..f898f90b --- /dev/null +++ b/src/wayland/protocols/output_configuration/handlers/mod.rs @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: GPL-3.0-only + +mod cosmic; +mod wlr; + +pub use self::{cosmic::*, wlr::*}; diff --git a/src/wayland/protocols/output_configuration.rs b/src/wayland/protocols/output_configuration/handlers/wlr.rs similarity index 52% rename from src/wayland/protocols/output_configuration.rs rename to src/wayland/protocols/output_configuration/handlers/wlr.rs index 0f53badc..425146b7 100644 --- a/src/wayland/protocols/output_configuration.rs +++ b/src/wayland/protocols/output_configuration/handlers/wlr.rs @@ -1,5 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-only +use cosmic_protocols::output_management::v1::server::zcosmic_output_configuration_v1; use smithay::{ output::{Mode, Output}, reexports::{ @@ -11,135 +12,21 @@ use smithay::{ zwlr_output_mode_v1::{self, ZwlrOutputModeV1}, }, wayland_server::{ - backend::{ClientId, GlobalId}, - protocol::wl_output::WlOutput, - Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, + backend::ClientId, Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, + Resource, }, }, - utils::{Logical, Physical, Point, Size, Transform}, - wayland::output::WlOutputData, + utils::{Point, Size}, }; use std::{ - convert::{TryFrom, TryInto}, + convert::TryInto, sync::{ atomic::{AtomicBool, Ordering}, - Arc, Mutex, + Arc, }, }; -#[derive(Debug)] -pub struct OutputConfigurationState { - outputs: Vec, - removed_outputs: Vec, - instances: Vec, - serial_counter: u32, - 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, - output: Output, - modes: Vec, - finished: bool, -} - -pub struct OutputMngrInstanceData { - active: Arc, -} - -#[derive(Debug, Default)] -pub struct PendingConfigurationInner { - 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 { - mode: Option>, - position: Option>, - transform: Option, - scale: Option, - adaptive_sync: Option, -} -pub type PendingOutputConfiguration = Mutex; - -#[derive(Debug, Clone)] -pub enum OutputConfiguration { - Enabled { - 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, - position: pending.position, - transform: pending.transform, - scale: pending.scale, - adaptive_sync: pending.adaptive_sync, - }) - } -} - -struct OutputStateInner { - enabled: bool, - global: Option, -} -type OutputState = Mutex; +use crate::wayland::protocols::output_configuration::*; impl GlobalDispatch for OutputConfigurationState where @@ -209,6 +96,7 @@ where let conf = data_init.init( id, PendingConfiguration::new(PendingConfigurationInner { + extension_obj: None, serial, used: false, heads: Vec::new(), @@ -350,6 +238,36 @@ where ); return; } + + if pending.heads.iter().any(|(_, c)| match c { + Some(conf) => { + if let Some(output_conf) = conf.data::() { + if let Some(output) = head.data::() { + let pending_conf = output_conf.lock().unwrap(); + pending_conf.mirroring.as_ref().is_some_and(|o| o == output) + } else { + false + } + } else { + false + } + } + None => false, + }) { + if let Some(extension_obj) = pending.extension_obj.as_ref() { + extension_obj.post_error( + zcosmic_output_configuration_v1::Error::MirroredHeadBusy, + format!("{:?} is disabled and mirrored", head), + ); + } else { + // unreachable? + head.post_error( + zwlr_output_configuration_v1::Error::UnconfiguredHead, + format!("{:?} is disabled and mirrored", head), + ); + } + } + pending.heads.push((head, None)); } x @ zwlr_output_configuration_v1::Request::Apply @@ -367,6 +285,9 @@ where let inner = state.output_configuration_state(); if pending.serial != inner.serial_counter { obj.cancelled(); + if let Some(extension_obj) = pending.extension_obj.take() { + extension_obj.finished(); + } return; } @@ -432,6 +353,9 @@ where }) { obj.cancelled(); + if let Some(extension_obj) = pending.extension_obj.take() { + extension_obj.finished(); + } return; } @@ -446,8 +370,16 @@ where } else { obj.failed(); } + if let Some(extension_obj) = pending.extension_obj.take() { + extension_obj.finished(); + } + } + zwlr_output_configuration_v1::Request::Destroy => { + let mut pending = data.lock().unwrap(); + if let Some(extension_obj) = pending.extension_obj.take() { + extension_obj.finished(); + } } - zwlr_output_configuration_v1::Request::Destroy => {} _ => {} } } @@ -573,310 +505,3 @@ where } } } - -impl OutputConfigurationState -where - D: GlobalDispatch - + GlobalDispatch - + Dispatch - + Dispatch - + Dispatch - + Dispatch - + Dispatch - + OutputConfigurationHandler - + 'static, -{ - pub fn new(dh: &DisplayHandle, client_filter: F) -> OutputConfigurationState - where - F: for<'a> Fn(&'a Client) -> bool + Send + Sync + 'static, - { - let global = dh.create_global::( - 4, - OutputMngrGlobalData { - filter: Box::new(client_filter), - }, - ); - - OutputConfigurationState { - outputs: Vec::new(), - removed_outputs: Vec::new(), - instances: Vec::new(), - serial_counter: 0, - global, - dh: dh.clone(), - _dispatch: std::marker::PhantomData, - } - } - - pub fn global_id(&self) -> GlobalId { - self.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, - 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()); - instance - .obj - .scale(output.current_scale().fractional_scale()); - 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); - }; -} -pub(crate) use delegate_output_configuration; - -use crate::utils::prelude::OutputExt; diff --git a/src/wayland/protocols/output_configuration/mod.rs b/src/wayland/protocols/output_configuration/mod.rs new file mode 100644 index 00000000..ed97841c --- /dev/null +++ b/src/wayland/protocols/output_configuration/mod.rs @@ -0,0 +1,504 @@ +// 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;