From a3226a3c624fd174216ffa6d32f0ba15fe6612b3 Mon Sep 17 00:00:00 2001 From: Ottatop Date: Sat, 1 Jun 2024 20:39:01 -0500 Subject: [PATCH] Impl wlr-output-management --- src/backend/udev.rs | 8 + src/handlers.rs | 71 ++- src/layout.rs | 14 +- src/output.rs | 3 + src/protocol.rs | 1 + src/protocol/output_management.rs | 918 ++++++++++++++++++++++++++++++ src/state.rs | 19 +- 7 files changed, 1017 insertions(+), 17 deletions(-) create mode 100644 src/protocol/output_management.rs diff --git a/src/backend/udev.rs b/src/backend/udev.rs index 464b13a..6d9fe26 100644 --- a/src/backend/udev.rs +++ b/src/backend/udev.rs @@ -1055,6 +1055,10 @@ impl Udev { ); let global = output.create_global::(&self.display_handle); + pinnacle + .output_management_manager_state + .add_head::(&output); + output.with_state_mut(|state| state.serial = serial); output.set_preferred(wl_mode); @@ -1215,6 +1219,10 @@ impl Udev { output_name: Some(output.name()), }) }); + + pinnacle + .output_management_manager_state + .remove_head(&output); } } diff --git a/src/handlers.rs b/src/handlers.rs index 84a554c..2194fdd 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -1,17 +1,18 @@ // SPDX-License-Identifier: GPL-3.0-or-later +pub mod idle; pub mod session_lock; pub mod window; mod xdg_shell; mod xwayland; -use std::{mem, os::fd::OwnedFd, sync::Arc}; +use std::{collections::HashMap, mem, os::fd::OwnedFd, sync::Arc}; use smithay::{ backend::renderer::utils::{self, with_renderer_surface_state}, delegate_compositor, delegate_data_control, delegate_data_device, delegate_fractional_scale, - delegate_idle_notify, delegate_layer_shell, delegate_output, delegate_pointer_constraints, - delegate_presentation, delegate_primary_selection, delegate_relative_pointer, delegate_seat, + delegate_layer_shell, delegate_output, delegate_pointer_constraints, delegate_presentation, + delegate_primary_selection, delegate_relative_pointer, delegate_seat, delegate_security_context, delegate_shm, delegate_viewporter, delegate_xwayland_shell, desktop::{ self, find_popup_root_surface, get_popup_toplevel_coords, layer_map_for_output, PopupKind, @@ -21,7 +22,7 @@ use smithay::{ pointer::{CursorImageStatus, PointerHandle}, Seat, SeatHandler, SeatState, }, - output::Output, + output::{Output, Scale}, reexports::{ calloop::Interest, wayland_protocols::xdg::shell::server::xdg_positioner::ConstraintAdjustment, @@ -42,7 +43,6 @@ use smithay::{ }, dmabuf, fractional_scale::{self, FractionalScaleHandler}, - idle_notify::{IdleNotifierHandler, IdleNotifierState}, output::OutputHandler, pointer_constraints::{with_pointer_constraint, PointerConstraintsHandler}, seat::WaylandFocus, @@ -69,16 +69,20 @@ use smithay::{ }, xwayland::{X11Wm, XWaylandClientData}, }; -use tracing::{error, trace, warn}; +use tracing::{debug, error, trace, warn}; use crate::{ backend::Backend, - delegate_foreign_toplevel, delegate_gamma_control, delegate_screencopy, + delegate_foreign_toplevel, delegate_gamma_control, delegate_output_management, + delegate_screencopy, focus::{keyboard::KeyboardFocusTarget, pointer::PointerFocusTarget}, handlers::xdg_shell::snapshot_pre_commit_hook, protocol::{ foreign_toplevel::{self, ForeignToplevelHandler, ForeignToplevelManagerState}, gamma_control::{GammaControlHandler, GammaControlManagerState}, + output_management::{ + OutputConfiguration, OutputManagementHandler, OutputManagementManagerState, + }, screencopy::{Screencopy, ScreencopyHandler}, }, render::util::snapshot::capture_snapshots_on_output, @@ -918,12 +922,57 @@ impl XWaylandShellHandler for State { } delegate_xwayland_shell!(State); -impl IdleNotifierHandler for State { - fn idle_notifier_state(&mut self) -> &mut IdleNotifierState { - &mut self.pinnacle.idle_notifier_state +impl OutputManagementHandler for State { + fn output_management_manager_state(&mut self) -> &mut OutputManagementManagerState { + &mut self.pinnacle.output_management_manager_state + } + + fn apply_configuration(&mut self, config: HashMap) -> bool { + for (output, config) in config { + match config { + OutputConfiguration::Disabled => todo!(), + OutputConfiguration::Enabled { + mode, + position, + transform, + scale, + adaptive_sync, + } => { + let snapshots = self.backend.with_renderer(|renderer| { + capture_snapshots_on_output(&mut self.pinnacle, renderer, &output, []) + }); + + self.pinnacle.change_output_state( + &output, + None, + transform, + scale.map(Scale::Fractional), + position, + ); + + if let Some((a, b)) = snapshots { + output.with_state_mut(|state| { + state.new_wait_layout_transaction( + self.pinnacle.loop_handle.clone(), + a, + b, + ) + }); + } + + self.pinnacle.request_layout(&output); + } + } + } + true + } + + fn test_configuration(&mut self, config: HashMap) -> bool { + debug!(?config); + true } } -delegate_idle_notify!(State); +delegate_output_management!(State); impl Pinnacle { fn position_popup(&self, popup: &PopupSurface) { diff --git a/src/layout.rs b/src/layout.rs index 7e9f8fe..3dbb0a5 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -32,20 +32,24 @@ impl Pinnacle { output: &Output, geometries: Vec>, ) -> Vec<(WindowElement, Serial)> { - let windows_on_foc_tags = output.with_state(|state| { + let (windows_on_foc_tags, to_unmap) = output.with_state(|state| { let focused_tags = state.focused_tags().collect::>(); self.windows .iter() - .filter(|win| !win.is_x11_override_redirect()) - .filter(|win| { + .filter(|win| win.output(self).as_ref() == Some(output)) + .cloned() + .partition::, _>(|win| { win.with_state(|state| state.tags.iter().any(|tg| focused_tags.contains(&tg))) }) - .cloned() - .collect::>() }); + for win in to_unmap { + self.space.unmap_elem(&win); + } + let tiled_windows = windows_on_foc_tags .iter() + .filter(|win| !win.is_x11_override_redirect()) .filter(|win| { win.with_state(|state| { state.floating_or_tiled.is_tiled() && state.fullscreen_or_maximized.is_neither() diff --git a/src/output.rs b/src/output.rs index f965a93..66e5503 100644 --- a/src/output.rs +++ b/src/output.rs @@ -219,5 +219,8 @@ impl Pinnacle { lock_surface.send_configure(); } + + self.output_management_manager_state + .update_head::(output); } } diff --git a/src/protocol.rs b/src/protocol.rs index d1779b7..a58b48b 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -1,3 +1,4 @@ pub mod foreign_toplevel; pub mod gamma_control; +pub mod output_management; pub mod screencopy; diff --git a/src/protocol/output_management.rs b/src/protocol/output_management.rs new file mode 100644 index 0000000..e3eb3b4 --- /dev/null +++ b/src/protocol/output_management.rs @@ -0,0 +1,918 @@ +use smithay::{ + output::Output, + reexports::{ + wayland_protocols_wlr::output_management::v1::server::{ + zwlr_output_configuration_head_v1::{self, ZwlrOutputConfigurationHeadV1}, + zwlr_output_configuration_v1, + zwlr_output_head_v1::{self, AdaptiveSyncState}, + zwlr_output_mode_v1::{self, ZwlrOutputModeV1}, + }, + wayland_server::{Resource, WEnum}, + }, + utils::{Logical, Physical, Point, Size, Transform, SERIAL_COUNTER}, +}; +use std::{collections::HashMap, num::NonZeroU32, sync::Mutex}; + +use smithay::{ + output::Mode, + reexports::{ + wayland_protocols_wlr::output_management::v1::server::{ + zwlr_output_configuration_v1::ZwlrOutputConfigurationV1, + zwlr_output_head_v1::ZwlrOutputHeadV1, + zwlr_output_manager_v1::{self, ZwlrOutputManagerV1}, + }, + wayland_server::{ + self, backend::ClientId, Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, + }, + }, +}; + +use crate::state::WithState; + +const VERSION: u32 = 4; + +pub struct OutputManagementManagerState { + display_handle: DisplayHandle, + managers: HashMap, + outputs: HashMap, +} + +struct OutputManagerData { + serial: u32, + configurations: Vec, + heads: HashMap>, +} + +pub struct OutputManagementGlobalData { + filter: Box bool + Send + Sync>, +} + +#[derive(Debug)] +enum PendingHead { + NotConfigured, + Enabled(ZwlrOutputConfigurationHeadV1), + Disabled, +} + +#[derive(Debug)] +pub struct PendingOutputConfiguration { + serial: u32, + inner: Mutex, +} + +#[derive(Default, Debug)] +struct PendingOutputConfigurationInner { + cancelled: bool, + pending_heads: HashMap, +} + +#[derive(Debug, Copy, Clone, Default)] +pub struct PendingOutputHeadConfiguration { + pub mode: Option<(Size, Option)>, + pub position: Option>, + pub transform: Option, + pub scale: Option, + pub adaptive_sync: Option, +} + +#[derive(Debug)] +pub enum OutputConfiguration { + Disabled, + Enabled { + mode: Option<(Size, Option)>, + position: Option>, + transform: Option, + scale: Option, + adaptive_sync: Option, + }, +} + +pub trait OutputManagementHandler { + fn output_management_manager_state(&mut self) -> &mut OutputManagementManagerState; + fn apply_configuration(&mut self, config: HashMap) -> bool; + fn test_configuration(&mut self, config: HashMap) -> bool; +} + +#[derive(Debug, Clone)] +pub struct OutputData { + // modes: Vec, + enabled: bool, + current_mode: Option, + position: Point, + transform: Transform, + scale: f64, + adaptive_sync: bool, +} + +impl OutputManagementManagerState { + pub fn add_head(&mut self, output: &Output) + where + D: Dispatch + + Dispatch + + OutputManagementHandler + + 'static, + { + if self.outputs.contains_key(output) { + return; + } + + for (manager, manager_data) in self.managers.iter_mut() { + let (head, modes) = advertise_output::(&self.display_handle, manager, output); + manager_data.heads.insert(head, modes); + } + + let output_data = OutputData { + enabled: true, + current_mode: output.current_mode(), + position: output.current_location(), + transform: output.current_transform(), + scale: output.current_scale().fractional_scale(), + adaptive_sync: false, // TODO: + }; + + self.outputs.insert(output.clone(), output_data); + } + + pub fn remove_head(&mut self, output: &Output) { + self.outputs.remove(output); + + for data in self.managers.values_mut() { + let heads = data.heads.keys().cloned().collect::>(); + for head in heads { + if head.data::() == Some(output) { + let modes = data.heads.remove(&head); + if let Some(modes) = modes { + for mode in modes { + mode.finished(); + } + } + head.finished(); + } + } + } + } + + pub fn set_head_enabled(&mut self, output: &Output, enabled: bool) { + let Some(output_data) = self.outputs.get_mut(output) else { + return; + }; + + output_data.enabled = enabled; + + for manager_data in self.managers.values() { + for (head, wlr_modes) in manager_data.heads.iter() { + if head.data::() == Some(output) { + head.enabled(enabled as i32); + + if enabled { + if let Some(current_mode) = output.current_mode() { + let wlr_current_mode = wlr_modes + .iter() + .find(|wlr_mode| wlr_mode.data::() == Some(¤t_mode)); + if let Some(wlr_current_mode) = wlr_current_mode { + head.current_mode(wlr_current_mode); + } + } + head.position(output.current_location().x, output.current_location().y); + head.transform(output.current_transform().into()); + head.scale(output.current_scale().fractional_scale()); + } + } + } + } + } + + pub fn update_head(&mut self, output: &Output) + where + D: Dispatch + + Dispatch + + OutputManagementHandler + + 'static, + { + let Some(output_data) = self.outputs.get_mut(output) else { + tracing::error!("Called `update_head` without `advertise_output`"); + return; + }; + + for (manager, manager_data) in self.managers.iter_mut() { + for (head, wlr_modes) in manager_data.heads.iter_mut() { + if head.data::() != Some(output) { + continue; + } + + // TODO: modes + let modes = output.with_state(|state| state.modes.clone()); + + wlr_modes.retain(|wlr_mode| { + if !modes.contains(wlr_mode.data::().unwrap()) { + wlr_mode.finished(); + false + } else { + true + } + }); + + for mode in modes { + if !wlr_modes + .iter() + .any(|wlr_mode| wlr_mode.data::().unwrap() == &mode) + { + if let Some(client) = head.client() { + let new_wlr_mode = client + .create_resource::( + &self.display_handle, + head.version(), + mode, + ) + .expect("TODO"); + + new_wlr_mode.size(mode.size.w, mode.size.h); + new_wlr_mode.refresh(mode.refresh); + + if Some(mode) == output.preferred_mode() { + new_wlr_mode.preferred(); + } + + head.mode(&new_wlr_mode); + + wlr_modes.push(new_wlr_mode); + } + } + } + + // enabled handled in `set_head_enabled` + + if output.current_mode() != output_data.current_mode { + if let Some(new_cur_mode) = output.current_mode() { + let new_cur_wlr_mode = wlr_modes + .iter() + .find(|wlr_mode| wlr_mode.data::() == Some(&new_cur_mode)); + + match new_cur_wlr_mode { + Some(new_cur_wlr_mode) => { + head.current_mode(new_cur_wlr_mode); + } + // TODO: don't do this branch + None => { + if let Some(client) = head.client() { + let new_cur_wlr_mode = client + .create_resource::( + &self.display_handle, + head.version(), + new_cur_mode, + ) + .expect("TODO"); + + new_cur_wlr_mode.size(new_cur_mode.size.w, new_cur_mode.size.h); + new_cur_wlr_mode.refresh(new_cur_mode.refresh); + + if Some(new_cur_mode) == output.preferred_mode() { + new_cur_wlr_mode.preferred(); + } + + head.mode(&new_cur_wlr_mode); + head.current_mode(&new_cur_wlr_mode); + wlr_modes.push(new_cur_wlr_mode); + } + } + } + + output_data.current_mode = Some(new_cur_mode); + } + } + + if output.current_location() != output_data.position { + let new_loc = output.current_location(); + head.position(new_loc.x, new_loc.y); + output_data.position = new_loc; + } + + if output.current_transform() != output_data.transform { + let new_transform = output.current_transform(); + head.transform(new_transform.into()); + output_data.transform = new_transform; + } + + if output.current_scale().fractional_scale() != output_data.scale { + let new_scale = output.current_scale().fractional_scale(); + head.scale(new_scale); + output_data.scale = new_scale; + } + + // TODO: adaptive sync + } + + let serial = u32::from(SERIAL_COUNTER.next_serial()); + + manager_data.serial = serial; + manager.done(serial); + } + } +} + +fn advertise_output( + display: &DisplayHandle, + manager: &ZwlrOutputManagerV1, + output: &Output, +) -> (ZwlrOutputHeadV1, Vec) +where + D: Dispatch + + Dispatch + + OutputManagementHandler + + 'static, +{ + let client = manager.client().expect("TODO"); + + let head = client + .create_resource::(display, manager.version(), output.clone()) + .unwrap(); + + manager.head(&head); + + head.name(output.name()); + head.description(output.description()); + + let physical_props = output.physical_properties(); + head.physical_size(physical_props.size.w, physical_props.size.h); + + let mut wlr_modes = Vec::new(); + for mode in output.modes() { + let wlr_mode = client + .create_resource::(display, manager.version(), mode) + .unwrap(); + head.mode(&wlr_mode); + wlr_mode.size(mode.size.w, mode.size.h); + wlr_mode.refresh(mode.refresh); + if Some(mode) == output.preferred_mode() { + wlr_mode.preferred(); + } + wlr_modes.push(wlr_mode); + } + + if head.version() >= zwlr_output_head_v1::EVT_MAKE_SINCE { + head.make(physical_props.make); + head.model(physical_props.model); + + if let Some(serial_number) = output.with_state(|state| state.serial) { + head.serial_number(serial_number.to_string()); + } + } + + // TODO: + // SINCE FOUR + // head.adaptive_sync(match data.adaptive_sync { + // true => AdaptiveSyncState::Enabled, + // false => AdaptiveSyncState::Disabled, + // }); + + // TODO: + // head.enabled(data.enabled as i32); + head.enabled(true as i32); + if true + /* data.enabled */ + { + if let Some(current_mode) = output.current_mode() { + let wlr_current_mode = wlr_modes + .iter() + .find(|wlr_mode| wlr_mode.data::() == Some(¤t_mode)); + if let Some(wlr_current_mode) = wlr_current_mode { + head.current_mode(wlr_current_mode); + } + } + head.position(output.current_location().x, output.current_location().y); + head.transform(output.current_transform().into()); + head.scale(output.current_scale().fractional_scale()); + } + + (head, wlr_modes) +} + +fn manager_for_configuration<'a, D>( + state: &'a mut D, + configuration: &ZwlrOutputConfigurationV1, +) -> Option<(&'a ZwlrOutputManagerV1, &'a mut OutputManagerData)> +where + D: OutputManagementHandler, +{ + state + .output_management_manager_state() + .managers + .iter_mut() + .find(|(_, manager_data)| manager_data.configurations.contains(configuration)) +} + +impl OutputManagementManagerState { + pub fn new(display: &DisplayHandle, filter: F) -> Self + where + D: GlobalDispatch + + Dispatch + + 'static, + F: Fn(&Client) -> bool + Send + Sync + 'static, + { + let global_data = OutputManagementGlobalData { + filter: Box::new(filter), + }; + + display.create_global::(VERSION, global_data); + + Self { + display_handle: display.clone(), + managers: HashMap::new(), + outputs: HashMap::new(), + } + } +} + +impl GlobalDispatch + for OutputManagementManagerState +where + D: GlobalDispatch + + Dispatch + + Dispatch + + Dispatch + + OutputManagementHandler, +{ + fn bind( + state: &mut D, + handle: &DisplayHandle, + _client: &Client, + resource: wayland_server::New, + _global_data: &OutputManagementGlobalData, + data_init: &mut DataInit<'_, D>, + ) { + let manager = data_init.init(resource, ()); + + let heads = state + .output_management_manager_state() + .outputs + .keys() + .map(|output| advertise_output::(handle, &manager, output)) + .collect(); + + let serial = u32::from(SERIAL_COUNTER.next_serial()); + + manager.done(serial); + + let state = state.output_management_manager_state(); + + let data = OutputManagerData { + serial, + configurations: Vec::new(), + heads, + }; + + state.managers.insert(manager, data); + } + + fn can_view(client: Client, global_data: &OutputManagementGlobalData) -> bool { + (global_data.filter)(&client) + } +} + +impl Dispatch for OutputManagementManagerState +where + D: Dispatch + OutputManagementHandler, + D: Dispatch + OutputManagementHandler, +{ + fn request( + state: &mut D, + _client: &Client, + resource: &ZwlrOutputManagerV1, + request: ::Request, + _data: &(), + _dhandle: &DisplayHandle, + data_init: &mut DataInit<'_, D>, + ) { + match request { + zwlr_output_manager_v1::Request::CreateConfiguration { id, serial } => { + let Some(manager_data) = state + .output_management_manager_state() + .managers + .get_mut(resource) + else { + let config = PendingOutputConfiguration { + serial, + inner: Mutex::new(PendingOutputConfigurationInner { + cancelled: false, + pending_heads: HashMap::new(), + }), + }; + + let config = data_init.init(id, config); + + config.cancelled(); + return; + }; + + let pending_heads = manager_data + .heads + .keys() + .map(|head| (head.clone(), PendingHead::NotConfigured)) + .collect::>(); + + let config = PendingOutputConfiguration { + serial, + inner: Mutex::new(PendingOutputConfigurationInner { + cancelled: false, + pending_heads, + }), + }; + + let config = data_init.init(id, config); + + let correct_serial = manager_data.serial == serial; + + if !correct_serial { + config.cancelled(); + return; + } + + manager_data.configurations.push(config); + } + zwlr_output_manager_v1::Request::Stop => { + resource.finished(); + + state + .output_management_manager_state() + .managers + .remove(resource); + } + _ => unreachable!(), + } + } + + fn destroyed(state: &mut D, _client: ClientId, resource: &ZwlrOutputManagerV1, _data: &()) { + state + .output_management_manager_state() + .managers + .remove(resource); + } +} + +impl Dispatch for OutputManagementManagerState { + fn request( + state: &mut D, + client: &Client, + resource: &ZwlrOutputHeadV1, + request: ::Request, + data: &Output, + dhandle: &DisplayHandle, + data_init: &mut DataInit<'_, D>, + ) { + match request { + zwlr_output_head_v1::Request::Release => { + // TODO: + } + _ => unreachable!(), + } + } +} + +impl Dispatch for OutputManagementManagerState { + fn request( + state: &mut D, + client: &Client, + resource: &ZwlrOutputModeV1, + request: ::Request, + data: &Mode, + dhandle: &DisplayHandle, + data_init: &mut DataInit<'_, D>, + ) { + match request { + zwlr_output_mode_v1::Request::Release => { + // TODO: + } + _ => unreachable!(), + } + } +} + +impl Dispatch + for OutputManagementManagerState +where + D: Dispatch + + Dispatch> + + OutputManagementHandler, +{ + fn request( + state: &mut D, + _client: &Client, + resource: &ZwlrOutputConfigurationV1, + request: ::Request, + pending_data: &PendingOutputConfiguration, + _dhandle: &DisplayHandle, + data_init: &mut DataInit<'_, D>, + ) { + match request { + zwlr_output_configuration_v1::Request::EnableHead { id, head } => { + let config_head = + data_init.init(id, Mutex::new(PendingOutputHeadConfiguration::default())); + + let mut data = pending_data.inner.lock().unwrap(); + + let manager_serial = + manager_for_configuration(state, resource).map(|(_, data)| data.serial); + + if manager_serial != Some(pending_data.serial) { + resource.cancelled(); + data.cancelled = true; + } + + if data.cancelled { + return; + } + + if let Some(pending_data) = data.pending_heads.get_mut(&head) { + if !matches!(pending_data, PendingHead::NotConfigured) { + head.post_error( + zwlr_output_configuration_v1::Error::AlreadyConfiguredHead, + "head has already been configured", + ); + return; + } + + *pending_data = PendingHead::Enabled(config_head); + } + } + zwlr_output_configuration_v1::Request::DisableHead { head } => { + let mut data = pending_data.inner.lock().unwrap(); + + let manager_serial = + manager_for_configuration(state, resource).map(|(_, data)| data.serial); + + if manager_serial != Some(pending_data.serial) { + resource.cancelled(); + data.cancelled = true; + } + + if data.cancelled { + return; + } + + if let Some(pending_data) = data.pending_heads.get_mut(&head) { + if !matches!(pending_data, PendingHead::NotConfigured) { + head.post_error( + zwlr_output_configuration_v1::Error::AlreadyConfiguredHead, + "head has already been configured", + ); + return; + } + + *pending_data = PendingHead::Disabled; + } + } + req @ (zwlr_output_configuration_v1::Request::Apply + | zwlr_output_configuration_v1::Request::Test) => { + let mut data = pending_data.inner.lock().unwrap(); + + let manager_serial = + manager_for_configuration(state, resource).map(|(_, data)| data.serial); + + if manager_serial != Some(pending_data.serial) { + resource.cancelled(); + data.cancelled = true; + } + + if data.cancelled { + return; + } + + if data + .pending_heads + .values() + .any(|cfg| matches!(cfg, PendingHead::NotConfigured)) + { + resource.post_error( + zwlr_output_configuration_v1::Error::UnconfiguredHead, + "a head was unconfigured", + ); + return; + } + + let config = data + .pending_heads + .iter() + .map(|(head, head_cfg)| { + let output = head.data::().unwrap().clone(); + + let cfg = match head_cfg { + PendingHead::NotConfigured => unreachable!(), + PendingHead::Enabled(cfg_head) => { + let pending = cfg_head + .data::>() + .unwrap() + .lock() + .unwrap(); + OutputConfiguration::Enabled { + mode: pending.mode, + position: pending.position, + transform: pending.transform, + scale: pending.scale, + adaptive_sync: pending.adaptive_sync, + } + } + PendingHead::Disabled => OutputConfiguration::Disabled, + }; + + (output, cfg) + }) + .collect(); + + let apply = matches!(req, zwlr_output_configuration_v1::Request::Apply); + let success = if apply { + state.apply_configuration(config) + } else { + state.test_configuration(config) + }; + + if success { + resource.succeeded(); + } else { + resource.failed(); + } + } + zwlr_output_configuration_v1::Request::Destroy => (), + _ => unreachable!(), + } + } + + fn destroyed( + state: &mut D, + _client: ClientId, + resource: &ZwlrOutputConfigurationV1, + _data: &PendingOutputConfiguration, + ) { + for output_manager_data in state + .output_management_manager_state() + .managers + .values_mut() + { + output_manager_data + .configurations + .retain(|config| config != resource); + } + } +} + +impl Dispatch, D> + for OutputManagementManagerState +where + D: Dispatch + 'static, +{ + fn request( + _state: &mut D, + _client: &Client, + resource: &ZwlrOutputConfigurationHeadV1, + request: ::Request, + data: &Mutex, + _dhandle: &DisplayHandle, + _data_init: &mut DataInit<'_, D>, + ) { + match request { + zwlr_output_configuration_head_v1::Request::SetMode { mode } => { + let mut data = data.lock().unwrap(); + if data.mode.is_some() { + resource.post_error( + zwlr_output_configuration_head_v1::Error::AlreadySet, + "mode has already been set", + ); + return; + } + + let mode = mode.data::().unwrap(); + + let mode = (mode.size, NonZeroU32::new(mode.refresh as u32)); + + data.mode = Some(mode); + } + zwlr_output_configuration_head_v1::Request::SetCustomMode { + width, + height, + refresh, + } => { + let mut data = data.lock().unwrap(); + if data.mode.is_some() { + resource.post_error( + zwlr_output_configuration_head_v1::Error::AlreadySet, + "mode has already been set", + ); + return; + } + + if width <= 0 || height <= 0 || refresh < 0 { + resource.post_error( + zwlr_output_configuration_head_v1::Error::InvalidCustomMode, + "invalid custom mode", + ); + return; + } + + data.mode = Some(((width, height).into(), NonZeroU32::new(refresh as u32))); + } + zwlr_output_configuration_head_v1::Request::SetPosition { x, y } => { + let mut data = data.lock().unwrap(); + if data.position.is_some() { + resource.post_error( + zwlr_output_configuration_head_v1::Error::AlreadySet, + "position has already been set", + ); + return; + } + + data.position = Some((x, y).into()); + } + zwlr_output_configuration_head_v1::Request::SetTransform { transform } => { + let mut data = data.lock().unwrap(); + if data.transform.is_some() { + resource.post_error( + zwlr_output_configuration_head_v1::Error::AlreadySet, + "transform has already been set", + ); + return; + } + + let transform = match transform { + WEnum::Value(transform) => transform, + WEnum::Unknown(val) => { + resource.post_error( + zwlr_output_configuration_head_v1::Error::InvalidTransform, + format!("transform has an invalid value of {val}"), + ); + return; + } + }; + + data.transform = Some(transform.into()); + } + zwlr_output_configuration_head_v1::Request::SetScale { scale } => { + let mut data = data.lock().unwrap(); + if data.scale.is_some() { + resource.post_error( + zwlr_output_configuration_head_v1::Error::AlreadySet, + "scale has already been set", + ); + return; + } + + data.scale = Some(scale); + } + zwlr_output_configuration_head_v1::Request::SetAdaptiveSync { state } => { + let mut data = data.lock().unwrap(); + if data.adaptive_sync.is_some() { + resource.post_error( + zwlr_output_configuration_head_v1::Error::AlreadySet, + "adaptive sync has already been set", + ); + return; + } + + let adaptive_sync = match state { + WEnum::Value(adaptive_sync) => match adaptive_sync { + AdaptiveSyncState::Disabled => false, + AdaptiveSyncState::Enabled => true, + _ => unreachable!(), + }, + WEnum::Unknown(val) => { + resource.post_error( + zwlr_output_configuration_head_v1::Error::InvalidAdaptiveSyncState, + format!("adaptive sync has an invalid value of {val}"), + ); + return; + } + }; + + data.adaptive_sync = Some(adaptive_sync); + } + _ => unreachable!(), + } + } +} + +#[macro_export] +macro_rules! delegate_output_management { + ($(@<$( $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::protocol::output_management::OutputManagementGlobalData + ] => $crate::protocol::output_management::OutputManagementManagerState); + + 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::protocol::output_management::OutputManagementManagerState); + + 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::protocol::output_management::OutputManagementManagerState); + + 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::protocol::output_management::OutputManagementManagerState); + + 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::protocol::output_management::PendingOutputConfiguration + ] => $crate::protocol::output_management::OutputManagementManagerState); + + 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: std::sync::Mutex<$crate::protocol::output_management::PendingOutputHeadConfiguration> + ] => $crate::protocol::output_management::OutputManagementManagerState); + }; +} diff --git a/src/state.rs b/src/state.rs index 3dc6d32..3608a86 100644 --- a/src/state.rs +++ b/src/state.rs @@ -12,6 +12,7 @@ use crate::{ protocol::{ foreign_toplevel::{self, ForeignToplevelManagerState}, gamma_control::GammaControlManagerState, + output_management::OutputManagementManagerState, screencopy::ScreencopyManagerState, }, window::WindowElement, @@ -52,7 +53,12 @@ use smithay::{ }, xwayland::{X11Wm, XWaylandClientData}, }; -use std::{cell::RefCell, collections::HashMap, path::PathBuf, sync::Arc}; +use std::{ + cell::RefCell, + collections::{HashMap, HashSet}, + path::PathBuf, + sync::Arc, +}; use sysinfo::{ProcessRefreshKind, RefreshKind}; use tracing::{info, warn}; use xdg::BaseDirectories; @@ -101,6 +107,7 @@ pub struct Pinnacle { pub session_lock_manager_state: SessionLockManagerState, pub xwayland_shell_state: XWaylandShellState, pub idle_notifier_state: IdleNotifierState, + pub output_management_manager_state: OutputManagementManagerState, pub lock_state: LockState, @@ -139,6 +146,9 @@ pub struct Pinnacle { /// A cache of surfaces to their root surface. pub root_surface_cache: HashMap, + + /// WlSurfaces with an attached idle inhibitor. + pub idle_inhibiting_surfaces: HashSet, } impl State { @@ -148,6 +158,7 @@ impl State { self.pinnacle.popup_manager.cleanup(); self.update_pointer_focus(); foreign_toplevel::refresh(self); + self.pinnacle.refresh_idle_inhibit(); if let Backend::Winit(winit) = &mut self.backend { winit.render_if_scheduled(&mut self.pinnacle); @@ -290,6 +301,10 @@ impl Pinnacle { ), xwayland_shell_state: XWaylandShellState::new::(&display_handle), idle_notifier_state: IdleNotifierState::new(&display_handle, loop_handle), + output_management_manager_state: OutputManagementManagerState::new::( + &display_handle, + filter_restricted_client, + ), lock_state: LockState::default(), @@ -326,6 +341,8 @@ impl Pinnacle { layout_state: LayoutState::default(), root_surface_cache: HashMap::new(), + + idle_inhibiting_surfaces: HashSet::new(), }; Ok(pinnacle)