wayland: Add cosmic-output-management
This commit is contained in:
parent
98ec82db77
commit
5f4377bf59
8 changed files with 885 additions and 437 deletions
11
Cargo.lock
generated
11
Cargo.lock
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<i32, Global>;
|
||||
fn adaptive_sync(&self) -> bool;
|
||||
fn set_adaptive_sync(&self, vrr: bool);
|
||||
fn mirroring(&self) -> Option<Output>;
|
||||
fn set_mirroring(&self, output: Option<Output>);
|
||||
}
|
||||
|
||||
struct Vrr(AtomicBool);
|
||||
|
||||
struct Mirroring(Mutex<Option<WeakOutput>>);
|
||||
|
||||
impl OutputExt for Output {
|
||||
fn geometry(&self) -> Rectangle<i32, Global> {
|
||||
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<Output> {
|
||||
self.user_data().get::<Mirroring>().and_then(|mirroring| {
|
||||
mirroring
|
||||
.0
|
||||
.lock()
|
||||
.unwrap()
|
||||
.clone()
|
||||
.and_then(|o| o.upgrade())
|
||||
})
|
||||
}
|
||||
fn set_mirroring(&self, output: Option<Output>) {
|
||||
let user_data = self.user_data();
|
||||
user_data.insert_if_missing_threadsafe(|| Mirroring(Mutex::new(None)));
|
||||
*user_data.get::<Mirroring>().unwrap().0.lock().unwrap() =
|
||||
output.map(|output| output.downgrade());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
276
src/wayland/protocols/output_configuration/handlers/cosmic.rs
Normal file
276
src/wayland/protocols/output_configuration/handlers/cosmic.rs
Normal file
|
|
@ -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<D> GlobalDispatch<ZcosmicOutputManagerV1, OutputMngrGlobalData, D>
|
||||
for OutputConfigurationState<D>
|
||||
where
|
||||
D: GlobalDispatch<ZcosmicOutputManagerV1, OutputMngrGlobalData>
|
||||
+ Dispatch<ZcosmicOutputManagerV1, ()>
|
||||
+ Dispatch<ZcosmicOutputHeadV1, Weak<ZwlrOutputHeadV1>>
|
||||
+ Dispatch<ZcosmicOutputConfigurationV1, Weak<ZwlrOutputConfigurationV1>>
|
||||
+ Dispatch<ZcosmicOutputConfigurationHeadV1, Weak<ZwlrOutputConfigurationHeadV1>>
|
||||
+ OutputConfigurationHandler
|
||||
+ 'static,
|
||||
{
|
||||
fn bind(
|
||||
state: &mut D,
|
||||
_dh: &DisplayHandle,
|
||||
_client: &Client,
|
||||
resource: New<ZcosmicOutputManagerV1>,
|
||||
_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<D> Dispatch<ZcosmicOutputManagerV1, (), D> for OutputConfigurationState<D>
|
||||
where
|
||||
D: GlobalDispatch<ZwlrOutputManagerV1, OutputMngrGlobalData>
|
||||
+ Dispatch<ZwlrOutputManagerV1, OutputMngrInstanceData>
|
||||
+ Dispatch<ZwlrOutputHeadV1, Output>
|
||||
+ Dispatch<ZwlrOutputModeV1, Mode>
|
||||
+ Dispatch<ZwlrOutputConfigurationV1, PendingConfiguration>
|
||||
+ Dispatch<ZwlrOutputConfigurationHeadV1, PendingOutputConfiguration>
|
||||
+ GlobalDispatch<ZcosmicOutputManagerV1, OutputMngrGlobalData>
|
||||
+ Dispatch<ZcosmicOutputManagerV1, ()>
|
||||
+ Dispatch<ZcosmicOutputHeadV1, Weak<ZwlrOutputHeadV1>>
|
||||
+ Dispatch<ZcosmicOutputConfigurationV1, Weak<ZwlrOutputConfigurationV1>>
|
||||
+ Dispatch<ZcosmicOutputConfigurationHeadV1, Weak<ZwlrOutputConfigurationHeadV1>>
|
||||
+ 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::<D>(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::<PendingConfiguration>() {
|
||||
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::<PendingOutputConfiguration>() {
|
||||
let obj = data_init.init(extended, config_head.downgrade());
|
||||
pending.lock().unwrap().extension_obj = Some(obj);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> Dispatch<ZcosmicOutputHeadV1, Weak<ZwlrOutputHeadV1>, D> for OutputConfigurationState<D> {
|
||||
fn request(
|
||||
_state: &mut D,
|
||||
_client: &Client,
|
||||
_obj: &ZcosmicOutputHeadV1,
|
||||
_request: zcosmic_output_head_v1::Request,
|
||||
_data: &Weak<ZwlrOutputHeadV1>,
|
||||
_dh: &DisplayHandle,
|
||||
_data_init: &mut DataInit<'_, D>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> Dispatch<ZcosmicOutputConfigurationV1, Weak<ZwlrOutputConfigurationV1>, D>
|
||||
for OutputConfigurationState<D>
|
||||
where
|
||||
D: GlobalDispatch<ZwlrOutputManagerV1, OutputMngrGlobalData>
|
||||
+ Dispatch<ZwlrOutputManagerV1, OutputMngrInstanceData>
|
||||
+ Dispatch<ZwlrOutputHeadV1, Output>
|
||||
+ Dispatch<ZwlrOutputModeV1, Mode>
|
||||
+ Dispatch<ZwlrOutputConfigurationV1, PendingConfiguration>
|
||||
+ Dispatch<ZwlrOutputConfigurationHeadV1, PendingOutputConfiguration>
|
||||
+ GlobalDispatch<ZcosmicOutputManagerV1, OutputMngrGlobalData>
|
||||
+ Dispatch<ZcosmicOutputManagerV1, ()>
|
||||
+ Dispatch<ZcosmicOutputHeadV1, Weak<ZwlrOutputHeadV1>>
|
||||
+ Dispatch<ZcosmicOutputConfigurationV1, Weak<ZwlrOutputConfigurationV1>>
|
||||
+ Dispatch<ZcosmicOutputConfigurationHeadV1, Weak<ZwlrOutputConfigurationHeadV1>>
|
||||
+ OutputConfigurationHandler
|
||||
+ 'static,
|
||||
{
|
||||
fn request(
|
||||
_state: &mut D,
|
||||
_client: &Client,
|
||||
extension_obj: &ZcosmicOutputConfigurationV1,
|
||||
request: zcosmic_output_configuration_v1::Request,
|
||||
obj: &Weak<ZwlrOutputConfigurationV1>,
|
||||
_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::<PendingConfiguration>() {
|
||||
if let Some(output) = mirroring.data::<Output>() {
|
||||
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::<PendingOutputConfiguration>() {
|
||||
match conf.lock().unwrap().mirroring.as_ref() {
|
||||
Some(mirrored) => {
|
||||
head.data::<Output>().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::<PendingConfiguration>() {
|
||||
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::<PendingOutputConfiguration>() {
|
||||
let output_conf = data.lock().unwrap();
|
||||
output_conf.mirroring.is_none()
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
None => true,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> Dispatch<ZcosmicOutputConfigurationHeadV1, Weak<ZwlrOutputConfigurationHeadV1>, D>
|
||||
for OutputConfigurationState<D>
|
||||
where
|
||||
D: GlobalDispatch<ZwlrOutputManagerV1, OutputMngrGlobalData>
|
||||
+ Dispatch<ZwlrOutputManagerV1, OutputMngrInstanceData>
|
||||
+ Dispatch<ZwlrOutputHeadV1, Output>
|
||||
+ Dispatch<ZwlrOutputModeV1, Mode>
|
||||
+ Dispatch<ZwlrOutputConfigurationV1, PendingConfiguration>
|
||||
+ Dispatch<ZwlrOutputConfigurationHeadV1, PendingOutputConfiguration>
|
||||
+ OutputConfigurationHandler
|
||||
+ 'static,
|
||||
{
|
||||
fn request(
|
||||
_state: &mut D,
|
||||
_client: &Client,
|
||||
_extended_obj: &ZcosmicOutputConfigurationHeadV1,
|
||||
request: zcosmic_output_configuration_head_v1::Request,
|
||||
obj: &Weak<ZwlrOutputConfigurationHeadV1>,
|
||||
_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::<PendingOutputConfiguration>() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
mod cosmic;
|
||||
mod wlr;
|
||||
|
||||
pub use self::{cosmic::*, wlr::*};
|
||||
|
|
@ -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<D> {
|
||||
outputs: Vec<Output>,
|
||||
removed_outputs: Vec<Output>,
|
||||
instances: Vec<OutputMngrInstance>,
|
||||
serial_counter: u32,
|
||||
global: GlobalId,
|
||||
dh: DisplayHandle,
|
||||
_dispatch: std::marker::PhantomData<D>,
|
||||
}
|
||||
|
||||
pub trait OutputConfigurationHandler: Sized {
|
||||
fn output_configuration_state(&mut self) -> &mut OutputConfigurationState<Self>;
|
||||
|
||||
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<dyn for<'a> Fn(&'a Client) -> bool + Send + Sync>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct OutputMngrInstance {
|
||||
obj: ZwlrOutputManagerV1,
|
||||
active: Arc<AtomicBool>,
|
||||
heads: Vec<OutputHeadInstance>,
|
||||
stale_modes: Vec<ZwlrOutputModeV1>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct OutputHeadInstance {
|
||||
obj: ZwlrOutputHeadV1,
|
||||
output: Output,
|
||||
modes: Vec<ZwlrOutputModeV1>,
|
||||
finished: bool,
|
||||
}
|
||||
|
||||
pub struct OutputMngrInstanceData {
|
||||
active: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct PendingConfigurationInner {
|
||||
serial: u32,
|
||||
used: bool,
|
||||
heads: Vec<(ZwlrOutputHeadV1, Option<ZwlrOutputConfigurationHeadV1>)>,
|
||||
}
|
||||
pub type PendingConfiguration = Mutex<PendingConfigurationInner>;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ModeConfiguration<M: Clone> {
|
||||
Mode(M),
|
||||
Custom {
|
||||
size: Size<i32, Physical>,
|
||||
refresh: Option<i32>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct PendingOutputConfigurationInner {
|
||||
mode: Option<ModeConfiguration<ZwlrOutputModeV1>>,
|
||||
position: Option<Point<i32, Logical>>,
|
||||
transform: Option<Transform>,
|
||||
scale: Option<f64>,
|
||||
adaptive_sync: Option<bool>,
|
||||
}
|
||||
pub type PendingOutputConfiguration = Mutex<PendingOutputConfigurationInner>;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum OutputConfiguration {
|
||||
Enabled {
|
||||
mode: Option<ModeConfiguration<Mode>>,
|
||||
position: Option<Point<i32, Logical>>,
|
||||
transform: Option<Transform>,
|
||||
scale: Option<f64>,
|
||||
adaptive_sync: Option<bool>,
|
||||
},
|
||||
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<OutputConfiguration, Self::Error> {
|
||||
let mode = match pending.mode.clone() {
|
||||
Some(ModeConfiguration::Mode(wlr_mode)) => Some(ModeConfiguration::Mode(
|
||||
wlr_mode
|
||||
.data::<Mode>()
|
||||
.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<GlobalId>,
|
||||
}
|
||||
type OutputState = Mutex<OutputStateInner>;
|
||||
use crate::wayland::protocols::output_configuration::*;
|
||||
|
||||
impl<D> GlobalDispatch<ZwlrOutputManagerV1, OutputMngrGlobalData, D> for OutputConfigurationState<D>
|
||||
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::<PendingOutputConfiguration>() {
|
||||
if let Some(output) = head.data::<Output>() {
|
||||
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<D> OutputConfigurationState<D>
|
||||
where
|
||||
D: GlobalDispatch<ZwlrOutputManagerV1, OutputMngrGlobalData>
|
||||
+ GlobalDispatch<WlOutput, WlOutputData>
|
||||
+ Dispatch<ZwlrOutputManagerV1, OutputMngrInstanceData>
|
||||
+ Dispatch<ZwlrOutputHeadV1, Output>
|
||||
+ Dispatch<ZwlrOutputModeV1, Mode>
|
||||
+ Dispatch<ZwlrOutputConfigurationV1, PendingConfiguration>
|
||||
+ Dispatch<ZwlrOutputConfigurationHeadV1, PendingOutputConfiguration>
|
||||
+ OutputConfigurationHandler
|
||||
+ 'static,
|
||||
{
|
||||
pub fn new<F>(dh: &DisplayHandle, client_filter: F) -> OutputConfigurationState<D>
|
||||
where
|
||||
F: for<'a> Fn(&'a Client) -> bool + Send + Sync + 'static,
|
||||
{
|
||||
let global = dh.create_global::<D, ZwlrOutputManagerV1, _>(
|
||||
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<Item = &'a Output>) {
|
||||
let new_outputs = outputs
|
||||
.filter(|o| !self.outputs.contains(o))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
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<Item = &'a Output>) {
|
||||
for output in outputs {
|
||||
if self.outputs.contains(output) {
|
||||
self.removed_outputs.push(output.clone());
|
||||
if let Some(inner) = output.user_data().get::<OutputState>() {
|
||||
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::<D>(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::<OutputState>() {
|
||||
let mut inner = inner.lock().unwrap();
|
||||
inner.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn disable_head(&self, output: &Output) {
|
||||
if let Some(inner) = output.user_data().get::<OutputState>() {
|
||||
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::<OutputState>().unwrap();
|
||||
let mut inner = state.lock().unwrap();
|
||||
if inner.enabled && inner.global.is_none() {
|
||||
inner.global = Some(output.create_global::<D>(&self.dh));
|
||||
}
|
||||
if !inner.enabled && inner.global.is_some() {
|
||||
self.dh.remove_global::<D>(inner.global.take().unwrap());
|
||||
}
|
||||
}
|
||||
for manager in self.instances.iter_mut() {
|
||||
send_head_to_client::<D>(&self.dh, manager, output);
|
||||
}
|
||||
}
|
||||
|
||||
for manager in self.instances.iter() {
|
||||
manager.obj.done(self.serial_counter);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn outputs(&self) -> impl Iterator<Item = Output> {
|
||||
self.outputs.clone().into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
fn send_head_to_client<D>(dh: &DisplayHandle, mngr: &mut OutputMngrInstance, output: &Output)
|
||||
where
|
||||
D: GlobalDispatch<ZwlrOutputManagerV1, OutputMngrGlobalData>
|
||||
+ Dispatch<ZwlrOutputManagerV1, OutputMngrInstanceData>
|
||||
+ Dispatch<ZwlrOutputHeadV1, Output>
|
||||
+ Dispatch<ZwlrOutputModeV1, Mode>
|
||||
+ Dispatch<ZwlrOutputConfigurationV1, PendingConfiguration>
|
||||
+ 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::<ZwlrOutputHeadV1, _, D>(
|
||||
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::<OutputState>()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap();
|
||||
|
||||
let output_modes = output.modes();
|
||||
// remove old modes
|
||||
instance.modes.retain_mut(|m| {
|
||||
if !output_modes.contains(m.data::<Mode>().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::<Mode>().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::<ZwlrOutputModeV1, _, D>(
|
||||
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<Self>);
|
||||
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<Self>);
|
||||
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<Self>);
|
||||
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<Self>);
|
||||
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<Self>);
|
||||
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<Self>);
|
||||
};
|
||||
}
|
||||
pub(crate) use delegate_output_configuration;
|
||||
|
||||
use crate::utils::prelude::OutputExt;
|
||||
504
src/wayland/protocols/output_configuration/mod.rs
Normal file
504
src/wayland/protocols/output_configuration/mod.rs
Normal file
|
|
@ -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<D> {
|
||||
outputs: Vec<Output>,
|
||||
removed_outputs: Vec<Output>,
|
||||
instances: Vec<OutputMngrInstance>,
|
||||
extension_instances: Vec<ZcosmicOutputManagerV1>,
|
||||
serial_counter: u32,
|
||||
global: GlobalId,
|
||||
extension_global: GlobalId,
|
||||
dh: DisplayHandle,
|
||||
_dispatch: std::marker::PhantomData<D>,
|
||||
}
|
||||
|
||||
pub trait OutputConfigurationHandler: Sized {
|
||||
fn output_configuration_state(&mut self) -> &mut OutputConfigurationState<Self>;
|
||||
|
||||
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<dyn for<'a> Fn(&'a Client) -> bool + Send + Sync>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct OutputMngrInstance {
|
||||
obj: ZwlrOutputManagerV1,
|
||||
active: Arc<AtomicBool>,
|
||||
heads: Vec<OutputHeadInstance>,
|
||||
stale_modes: Vec<ZwlrOutputModeV1>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct OutputHeadInstance {
|
||||
obj: ZwlrOutputHeadV1,
|
||||
extension_obj: Option<ZcosmicOutputHeadV1>,
|
||||
output: Output,
|
||||
modes: Vec<ZwlrOutputModeV1>,
|
||||
finished: bool,
|
||||
}
|
||||
|
||||
pub struct OutputMngrInstanceData {
|
||||
active: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct PendingConfigurationInner {
|
||||
extension_obj: Option<ZcosmicOutputConfigurationV1>,
|
||||
serial: u32,
|
||||
used: bool,
|
||||
heads: Vec<(ZwlrOutputHeadV1, Option<ZwlrOutputConfigurationHeadV1>)>,
|
||||
}
|
||||
|
||||
pub type PendingConfiguration = Mutex<PendingConfigurationInner>;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ModeConfiguration<M: Clone> {
|
||||
Mode(M),
|
||||
Custom {
|
||||
size: Size<i32, Physical>,
|
||||
refresh: Option<i32>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct PendingOutputConfigurationInner {
|
||||
extension_obj: Option<ZcosmicOutputConfigurationHeadV1>,
|
||||
mirroring: Option<Output>,
|
||||
mode: Option<ModeConfiguration<ZwlrOutputModeV1>>,
|
||||
position: Option<Point<i32, Logical>>,
|
||||
transform: Option<Transform>,
|
||||
scale: Option<f64>,
|
||||
adaptive_sync: Option<bool>,
|
||||
}
|
||||
pub type PendingOutputConfiguration = Mutex<PendingOutputConfigurationInner>;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum OutputConfiguration {
|
||||
Enabled {
|
||||
mirroring: Option<Output>,
|
||||
mode: Option<ModeConfiguration<Mode>>,
|
||||
position: Option<Point<i32, Logical>>,
|
||||
transform: Option<Transform>,
|
||||
scale: Option<f64>,
|
||||
adaptive_sync: Option<bool>,
|
||||
},
|
||||
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<OutputConfiguration, Self::Error> {
|
||||
let mode = match pending.mode.clone() {
|
||||
Some(ModeConfiguration::Mode(wlr_mode)) => Some(ModeConfiguration::Mode(
|
||||
wlr_mode
|
||||
.data::<Mode>()
|
||||
.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<GlobalId>,
|
||||
}
|
||||
type OutputState = Mutex<OutputStateInner>;
|
||||
|
||||
impl<D> OutputConfigurationState<D>
|
||||
where
|
||||
D: GlobalDispatch<ZwlrOutputManagerV1, OutputMngrGlobalData>
|
||||
+ GlobalDispatch<WlOutput, WlOutputData>
|
||||
+ Dispatch<ZwlrOutputManagerV1, OutputMngrInstanceData>
|
||||
+ Dispatch<ZwlrOutputHeadV1, Output>
|
||||
+ Dispatch<ZwlrOutputModeV1, Mode>
|
||||
+ Dispatch<ZwlrOutputConfigurationV1, PendingConfiguration>
|
||||
+ Dispatch<ZwlrOutputConfigurationHeadV1, PendingOutputConfiguration>
|
||||
+ GlobalDispatch<ZcosmicOutputManagerV1, OutputMngrGlobalData>
|
||||
+ Dispatch<ZcosmicOutputManagerV1, ()>
|
||||
+ Dispatch<ZcosmicOutputHeadV1, Weak<ZwlrOutputHeadV1>>
|
||||
+ Dispatch<ZcosmicOutputConfigurationV1, Weak<ZwlrOutputConfigurationV1>>
|
||||
+ Dispatch<ZcosmicOutputConfigurationHeadV1, Weak<ZwlrOutputConfigurationHeadV1>>
|
||||
+ OutputConfigurationHandler
|
||||
+ 'static,
|
||||
{
|
||||
pub fn new<F>(dh: &DisplayHandle, client_filter: F) -> OutputConfigurationState<D>
|
||||
where
|
||||
F: for<'a> Fn(&'a Client) -> bool + Clone + Send + Sync + 'static,
|
||||
{
|
||||
let global = dh.create_global::<D, ZwlrOutputManagerV1, _>(
|
||||
4,
|
||||
OutputMngrGlobalData {
|
||||
filter: Box::new(client_filter.clone()),
|
||||
},
|
||||
);
|
||||
|
||||
let extension_global = dh.create_global::<D, ZcosmicOutputManagerV1, _>(
|
||||
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<Item = &'a Output>) {
|
||||
let new_outputs = outputs
|
||||
.filter(|o| !self.outputs.contains(o))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
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<Item = &'a Output>) {
|
||||
for output in outputs {
|
||||
if self.outputs.contains(output) {
|
||||
self.removed_outputs.push(output.clone());
|
||||
if let Some(inner) = output.user_data().get::<OutputState>() {
|
||||
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::<D>(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::<OutputState>() {
|
||||
let mut inner = inner.lock().unwrap();
|
||||
inner.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn disable_head(&self, output: &Output) {
|
||||
if let Some(inner) = output.user_data().get::<OutputState>() {
|
||||
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::<OutputState>().unwrap();
|
||||
let mut inner = state.lock().unwrap();
|
||||
if inner.enabled && inner.global.is_none() {
|
||||
inner.global = Some(output.create_global::<D>(&self.dh));
|
||||
}
|
||||
if !inner.enabled && inner.global.is_some() {
|
||||
self.dh.remove_global::<D>(inner.global.take().unwrap());
|
||||
}
|
||||
}
|
||||
for manager in self.instances.iter_mut() {
|
||||
send_head_to_client::<D>(&self.dh, manager, output);
|
||||
}
|
||||
}
|
||||
|
||||
for manager in self.instances.iter() {
|
||||
manager.obj.done(self.serial_counter);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn outputs(&self) -> impl Iterator<Item = Output> {
|
||||
self.outputs.clone().into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
fn send_head_to_client<D>(dh: &DisplayHandle, mngr: &mut OutputMngrInstance, output: &Output)
|
||||
where
|
||||
D: GlobalDispatch<ZwlrOutputManagerV1, OutputMngrGlobalData>
|
||||
+ Dispatch<ZwlrOutputManagerV1, OutputMngrInstanceData>
|
||||
+ Dispatch<ZwlrOutputHeadV1, Output>
|
||||
+ Dispatch<ZwlrOutputModeV1, Mode>
|
||||
+ Dispatch<ZwlrOutputConfigurationV1, PendingConfiguration>
|
||||
+ 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::<ZwlrOutputHeadV1, _, D>(
|
||||
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::<OutputState>()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap();
|
||||
|
||||
let output_modes = output.modes();
|
||||
// remove old modes
|
||||
instance.modes.retain_mut(|m| {
|
||||
if !output_modes.contains(m.data::<Mode>().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::<Mode>().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::<ZwlrOutputModeV1, _, D>(
|
||||
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<Self>);
|
||||
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<Self>);
|
||||
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<Self>);
|
||||
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<Self>);
|
||||
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<Self>);
|
||||
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<Self>);
|
||||
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<Self>);
|
||||
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<Self>);
|
||||
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<smithay::reexports::wayland_protocols_wlr::output_management::v1::server::zwlr_output_head_v1::ZwlrOutputHeadV1>
|
||||
] => $crate::wayland::protocols::output_configuration::OutputConfigurationState<Self>);
|
||||
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<smithay::reexports::wayland_protocols_wlr::output_management::v1::server::zwlr_output_configuration_v1::ZwlrOutputConfigurationV1>
|
||||
] => $crate::wayland::protocols::output_configuration::OutputConfigurationState<Self>);
|
||||
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<smithay::reexports::wayland_protocols_wlr::output_management::v1::server::zwlr_output_configuration_head_v1::ZwlrOutputConfigurationHeadV1>
|
||||
] => $crate::wayland::protocols::output_configuration::OutputConfigurationState<Self>);
|
||||
};
|
||||
}
|
||||
pub(crate) use delegate_output_configuration;
|
||||
|
||||
use crate::utils::prelude::OutputExt;
|
||||
Loading…
Add table
Add a link
Reference in a new issue