Impl wlr-output-management

This commit is contained in:
Ottatop 2024-06-01 20:39:01 -05:00
parent 4e796ce8f6
commit a3226a3c62
7 changed files with 1017 additions and 17 deletions

View file

@ -1055,6 +1055,10 @@ impl Udev {
); );
let global = output.create_global::<State>(&self.display_handle); let global = output.create_global::<State>(&self.display_handle);
pinnacle
.output_management_manager_state
.add_head::<State>(&output);
output.with_state_mut(|state| state.serial = serial); output.with_state_mut(|state| state.serial = serial);
output.set_preferred(wl_mode); output.set_preferred(wl_mode);
@ -1215,6 +1219,10 @@ impl Udev {
output_name: Some(output.name()), output_name: Some(output.name()),
}) })
}); });
pinnacle
.output_management_manager_state
.remove_head(&output);
} }
} }

View file

@ -1,17 +1,18 @@
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
pub mod idle;
pub mod session_lock; pub mod session_lock;
pub mod window; pub mod window;
mod xdg_shell; mod xdg_shell;
mod xwayland; mod xwayland;
use std::{mem, os::fd::OwnedFd, sync::Arc}; use std::{collections::HashMap, mem, os::fd::OwnedFd, sync::Arc};
use smithay::{ use smithay::{
backend::renderer::utils::{self, with_renderer_surface_state}, backend::renderer::utils::{self, with_renderer_surface_state},
delegate_compositor, delegate_data_control, delegate_data_device, delegate_fractional_scale, delegate_compositor, delegate_data_control, delegate_data_device, delegate_fractional_scale,
delegate_idle_notify, delegate_layer_shell, delegate_output, delegate_pointer_constraints, delegate_layer_shell, delegate_output, delegate_pointer_constraints, delegate_presentation,
delegate_presentation, delegate_primary_selection, delegate_relative_pointer, delegate_seat, delegate_primary_selection, delegate_relative_pointer, delegate_seat,
delegate_security_context, delegate_shm, delegate_viewporter, delegate_xwayland_shell, delegate_security_context, delegate_shm, delegate_viewporter, delegate_xwayland_shell,
desktop::{ desktop::{
self, find_popup_root_surface, get_popup_toplevel_coords, layer_map_for_output, PopupKind, self, find_popup_root_surface, get_popup_toplevel_coords, layer_map_for_output, PopupKind,
@ -21,7 +22,7 @@ use smithay::{
pointer::{CursorImageStatus, PointerHandle}, pointer::{CursorImageStatus, PointerHandle},
Seat, SeatHandler, SeatState, Seat, SeatHandler, SeatState,
}, },
output::Output, output::{Output, Scale},
reexports::{ reexports::{
calloop::Interest, calloop::Interest,
wayland_protocols::xdg::shell::server::xdg_positioner::ConstraintAdjustment, wayland_protocols::xdg::shell::server::xdg_positioner::ConstraintAdjustment,
@ -42,7 +43,6 @@ use smithay::{
}, },
dmabuf, dmabuf,
fractional_scale::{self, FractionalScaleHandler}, fractional_scale::{self, FractionalScaleHandler},
idle_notify::{IdleNotifierHandler, IdleNotifierState},
output::OutputHandler, output::OutputHandler,
pointer_constraints::{with_pointer_constraint, PointerConstraintsHandler}, pointer_constraints::{with_pointer_constraint, PointerConstraintsHandler},
seat::WaylandFocus, seat::WaylandFocus,
@ -69,16 +69,20 @@ use smithay::{
}, },
xwayland::{X11Wm, XWaylandClientData}, xwayland::{X11Wm, XWaylandClientData},
}; };
use tracing::{error, trace, warn}; use tracing::{debug, error, trace, warn};
use crate::{ use crate::{
backend::Backend, 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}, focus::{keyboard::KeyboardFocusTarget, pointer::PointerFocusTarget},
handlers::xdg_shell::snapshot_pre_commit_hook, handlers::xdg_shell::snapshot_pre_commit_hook,
protocol::{ protocol::{
foreign_toplevel::{self, ForeignToplevelHandler, ForeignToplevelManagerState}, foreign_toplevel::{self, ForeignToplevelHandler, ForeignToplevelManagerState},
gamma_control::{GammaControlHandler, GammaControlManagerState}, gamma_control::{GammaControlHandler, GammaControlManagerState},
output_management::{
OutputConfiguration, OutputManagementHandler, OutputManagementManagerState,
},
screencopy::{Screencopy, ScreencopyHandler}, screencopy::{Screencopy, ScreencopyHandler},
}, },
render::util::snapshot::capture_snapshots_on_output, render::util::snapshot::capture_snapshots_on_output,
@ -918,12 +922,57 @@ impl XWaylandShellHandler for State {
} }
delegate_xwayland_shell!(State); delegate_xwayland_shell!(State);
impl IdleNotifierHandler for State { impl OutputManagementHandler for State {
fn idle_notifier_state(&mut self) -> &mut IdleNotifierState<Self> { fn output_management_manager_state(&mut self) -> &mut OutputManagementManagerState {
&mut self.pinnacle.idle_notifier_state &mut self.pinnacle.output_management_manager_state
}
fn apply_configuration(&mut self, config: HashMap<Output, OutputConfiguration>) -> 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<Output, OutputConfiguration>) -> bool {
debug!(?config);
true
} }
} }
delegate_idle_notify!(State); delegate_output_management!(State);
impl Pinnacle { impl Pinnacle {
fn position_popup(&self, popup: &PopupSurface) { fn position_popup(&self, popup: &PopupSurface) {

View file

@ -32,20 +32,24 @@ impl Pinnacle {
output: &Output, output: &Output,
geometries: Vec<Rectangle<i32, Logical>>, geometries: Vec<Rectangle<i32, Logical>>,
) -> Vec<(WindowElement, Serial)> { ) -> 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::<Vec<_>>(); let focused_tags = state.focused_tags().collect::<Vec<_>>();
self.windows self.windows
.iter() .iter()
.filter(|win| !win.is_x11_override_redirect()) .filter(|win| win.output(self).as_ref() == Some(output))
.filter(|win| { .cloned()
.partition::<Vec<_>, _>(|win| {
win.with_state(|state| state.tags.iter().any(|tg| focused_tags.contains(&tg))) win.with_state(|state| state.tags.iter().any(|tg| focused_tags.contains(&tg)))
}) })
.cloned()
.collect::<Vec<_>>()
}); });
for win in to_unmap {
self.space.unmap_elem(&win);
}
let tiled_windows = windows_on_foc_tags let tiled_windows = windows_on_foc_tags
.iter() .iter()
.filter(|win| !win.is_x11_override_redirect())
.filter(|win| { .filter(|win| {
win.with_state(|state| { win.with_state(|state| {
state.floating_or_tiled.is_tiled() && state.fullscreen_or_maximized.is_neither() state.floating_or_tiled.is_tiled() && state.fullscreen_or_maximized.is_neither()

View file

@ -219,5 +219,8 @@ impl Pinnacle {
lock_surface.send_configure(); lock_surface.send_configure();
} }
self.output_management_manager_state
.update_head::<State>(output);
} }
} }

View file

@ -1,3 +1,4 @@
pub mod foreign_toplevel; pub mod foreign_toplevel;
pub mod gamma_control; pub mod gamma_control;
pub mod output_management;
pub mod screencopy; pub mod screencopy;

View file

@ -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<ZwlrOutputManagerV1, OutputManagerData>,
outputs: HashMap<Output, OutputData>,
}
struct OutputManagerData {
serial: u32,
configurations: Vec<ZwlrOutputConfigurationV1>,
heads: HashMap<ZwlrOutputHeadV1, Vec<ZwlrOutputModeV1>>,
}
pub struct OutputManagementGlobalData {
filter: Box<dyn Fn(&Client) -> bool + Send + Sync>,
}
#[derive(Debug)]
enum PendingHead {
NotConfigured,
Enabled(ZwlrOutputConfigurationHeadV1),
Disabled,
}
#[derive(Debug)]
pub struct PendingOutputConfiguration {
serial: u32,
inner: Mutex<PendingOutputConfigurationInner>,
}
#[derive(Default, Debug)]
struct PendingOutputConfigurationInner {
cancelled: bool,
pending_heads: HashMap<ZwlrOutputHeadV1, PendingHead>,
}
#[derive(Debug, Copy, Clone, Default)]
pub struct PendingOutputHeadConfiguration {
pub mode: Option<(Size<i32, Physical>, Option<NonZeroU32>)>,
pub position: Option<Point<i32, Logical>>,
pub transform: Option<Transform>,
pub scale: Option<f64>,
pub adaptive_sync: Option<bool>,
}
#[derive(Debug)]
pub enum OutputConfiguration {
Disabled,
Enabled {
mode: Option<(Size<i32, Physical>, Option<NonZeroU32>)>,
position: Option<Point<i32, Logical>>,
transform: Option<Transform>,
scale: Option<f64>,
adaptive_sync: Option<bool>,
},
}
pub trait OutputManagementHandler {
fn output_management_manager_state(&mut self) -> &mut OutputManagementManagerState;
fn apply_configuration(&mut self, config: HashMap<Output, OutputConfiguration>) -> bool;
fn test_configuration(&mut self, config: HashMap<Output, OutputConfiguration>) -> bool;
}
#[derive(Debug, Clone)]
pub struct OutputData {
// modes: Vec<Mode>,
enabled: bool,
current_mode: Option<Mode>,
position: Point<i32, Logical>,
transform: Transform,
scale: f64,
adaptive_sync: bool,
}
impl OutputManagementManagerState {
pub fn add_head<D>(&mut self, output: &Output)
where
D: Dispatch<ZwlrOutputHeadV1, Output>
+ Dispatch<ZwlrOutputModeV1, Mode>
+ OutputManagementHandler
+ 'static,
{
if self.outputs.contains_key(output) {
return;
}
for (manager, manager_data) in self.managers.iter_mut() {
let (head, modes) = advertise_output::<D>(&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::<Vec<_>>();
for head in heads {
if head.data::<Output>() == 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::<Output>() == 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::<Mode>() == Some(&current_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<D>(&mut self, output: &Output)
where
D: Dispatch<ZwlrOutputHeadV1, Output>
+ Dispatch<ZwlrOutputModeV1, Mode>
+ 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::<Output>() != 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::<Mode>().unwrap()) {
wlr_mode.finished();
false
} else {
true
}
});
for mode in modes {
if !wlr_modes
.iter()
.any(|wlr_mode| wlr_mode.data::<Mode>().unwrap() == &mode)
{
if let Some(client) = head.client() {
let new_wlr_mode = client
.create_resource::<ZwlrOutputModeV1, _, D>(
&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::<Mode>() == 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::<ZwlrOutputModeV1, _, D>(
&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<D>(
display: &DisplayHandle,
manager: &ZwlrOutputManagerV1,
output: &Output,
) -> (ZwlrOutputHeadV1, Vec<ZwlrOutputModeV1>)
where
D: Dispatch<ZwlrOutputHeadV1, Output>
+ Dispatch<ZwlrOutputModeV1, Mode>
+ OutputManagementHandler
+ 'static,
{
let client = manager.client().expect("TODO");
let head = client
.create_resource::<ZwlrOutputHeadV1, _, D>(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::<ZwlrOutputModeV1, _, D>(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::<Mode>() == Some(&current_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<D, F>(display: &DisplayHandle, filter: F) -> Self
where
D: GlobalDispatch<ZwlrOutputManagerV1, OutputManagementGlobalData>
+ Dispatch<ZwlrOutputManagerV1, ()>
+ 'static,
F: Fn(&Client) -> bool + Send + Sync + 'static,
{
let global_data = OutputManagementGlobalData {
filter: Box::new(filter),
};
display.create_global::<D, ZwlrOutputManagerV1, _>(VERSION, global_data);
Self {
display_handle: display.clone(),
managers: HashMap::new(),
outputs: HashMap::new(),
}
}
}
impl<D> GlobalDispatch<ZwlrOutputManagerV1, OutputManagementGlobalData, D>
for OutputManagementManagerState
where
D: GlobalDispatch<ZwlrOutputManagerV1, OutputManagementGlobalData>
+ Dispatch<ZwlrOutputManagerV1, ()>
+ Dispatch<ZwlrOutputHeadV1, Output>
+ Dispatch<ZwlrOutputModeV1, Mode>
+ OutputManagementHandler,
{
fn bind(
state: &mut D,
handle: &DisplayHandle,
_client: &Client,
resource: wayland_server::New<ZwlrOutputManagerV1>,
_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::<D>(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<D> Dispatch<ZwlrOutputManagerV1, (), D> for OutputManagementManagerState
where
D: Dispatch<ZwlrOutputManagerV1, ()> + OutputManagementHandler,
D: Dispatch<ZwlrOutputConfigurationV1, PendingOutputConfiguration> + OutputManagementHandler,
{
fn request(
state: &mut D,
_client: &Client,
resource: &ZwlrOutputManagerV1,
request: <ZwlrOutputManagerV1 as wayland_server::Resource>::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::<HashMap<_, _>>();
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<D> Dispatch<ZwlrOutputHeadV1, Output, D> for OutputManagementManagerState {
fn request(
state: &mut D,
client: &Client,
resource: &ZwlrOutputHeadV1,
request: <ZwlrOutputHeadV1 as Resource>::Request,
data: &Output,
dhandle: &DisplayHandle,
data_init: &mut DataInit<'_, D>,
) {
match request {
zwlr_output_head_v1::Request::Release => {
// TODO:
}
_ => unreachable!(),
}
}
}
impl<D> Dispatch<ZwlrOutputModeV1, Mode, D> for OutputManagementManagerState {
fn request(
state: &mut D,
client: &Client,
resource: &ZwlrOutputModeV1,
request: <ZwlrOutputModeV1 as Resource>::Request,
data: &Mode,
dhandle: &DisplayHandle,
data_init: &mut DataInit<'_, D>,
) {
match request {
zwlr_output_mode_v1::Request::Release => {
// TODO:
}
_ => unreachable!(),
}
}
}
impl<D> Dispatch<ZwlrOutputConfigurationV1, PendingOutputConfiguration, D>
for OutputManagementManagerState
where
D: Dispatch<ZwlrOutputManagerV1, ()>
+ Dispatch<ZwlrOutputConfigurationHeadV1, Mutex<PendingOutputHeadConfiguration>>
+ OutputManagementHandler,
{
fn request(
state: &mut D,
_client: &Client,
resource: &ZwlrOutputConfigurationV1,
request: <ZwlrOutputConfigurationV1 as Resource>::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::<Output>().unwrap().clone();
let cfg = match head_cfg {
PendingHead::NotConfigured => unreachable!(),
PendingHead::Enabled(cfg_head) => {
let pending = cfg_head
.data::<Mutex<PendingOutputHeadConfiguration>>()
.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<D> Dispatch<ZwlrOutputConfigurationHeadV1, Mutex<PendingOutputHeadConfiguration>, D>
for OutputManagementManagerState
where
D: Dispatch<ZwlrOutputModeV1, Mode> + 'static,
{
fn request(
_state: &mut D,
_client: &Client,
resource: &ZwlrOutputConfigurationHeadV1,
request: <ZwlrOutputConfigurationHeadV1 as Resource>::Request,
data: &Mutex<PendingOutputHeadConfiguration>,
_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::<Mode>().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);
};
}

View file

@ -12,6 +12,7 @@ use crate::{
protocol::{ protocol::{
foreign_toplevel::{self, ForeignToplevelManagerState}, foreign_toplevel::{self, ForeignToplevelManagerState},
gamma_control::GammaControlManagerState, gamma_control::GammaControlManagerState,
output_management::OutputManagementManagerState,
screencopy::ScreencopyManagerState, screencopy::ScreencopyManagerState,
}, },
window::WindowElement, window::WindowElement,
@ -52,7 +53,12 @@ use smithay::{
}, },
xwayland::{X11Wm, XWaylandClientData}, 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 sysinfo::{ProcessRefreshKind, RefreshKind};
use tracing::{info, warn}; use tracing::{info, warn};
use xdg::BaseDirectories; use xdg::BaseDirectories;
@ -101,6 +107,7 @@ pub struct Pinnacle {
pub session_lock_manager_state: SessionLockManagerState, pub session_lock_manager_state: SessionLockManagerState,
pub xwayland_shell_state: XWaylandShellState, pub xwayland_shell_state: XWaylandShellState,
pub idle_notifier_state: IdleNotifierState<State>, pub idle_notifier_state: IdleNotifierState<State>,
pub output_management_manager_state: OutputManagementManagerState,
pub lock_state: LockState, pub lock_state: LockState,
@ -139,6 +146,9 @@ pub struct Pinnacle {
/// A cache of surfaces to their root surface. /// A cache of surfaces to their root surface.
pub root_surface_cache: HashMap<WlSurface, WlSurface>, pub root_surface_cache: HashMap<WlSurface, WlSurface>,
/// WlSurfaces with an attached idle inhibitor.
pub idle_inhibiting_surfaces: HashSet<WlSurface>,
} }
impl State { impl State {
@ -148,6 +158,7 @@ impl State {
self.pinnacle.popup_manager.cleanup(); self.pinnacle.popup_manager.cleanup();
self.update_pointer_focus(); self.update_pointer_focus();
foreign_toplevel::refresh(self); foreign_toplevel::refresh(self);
self.pinnacle.refresh_idle_inhibit();
if let Backend::Winit(winit) = &mut self.backend { if let Backend::Winit(winit) = &mut self.backend {
winit.render_if_scheduled(&mut self.pinnacle); winit.render_if_scheduled(&mut self.pinnacle);
@ -290,6 +301,10 @@ impl Pinnacle {
), ),
xwayland_shell_state: XWaylandShellState::new::<State>(&display_handle), xwayland_shell_state: XWaylandShellState::new::<State>(&display_handle),
idle_notifier_state: IdleNotifierState::new(&display_handle, loop_handle), idle_notifier_state: IdleNotifierState::new(&display_handle, loop_handle),
output_management_manager_state: OutputManagementManagerState::new::<State, _>(
&display_handle,
filter_restricted_client,
),
lock_state: LockState::default(), lock_state: LockState::default(),
@ -326,6 +341,8 @@ impl Pinnacle {
layout_state: LayoutState::default(), layout_state: LayoutState::default(),
root_surface_cache: HashMap::new(), root_surface_cache: HashMap::new(),
idle_inhibiting_surfaces: HashSet::new(),
}; };
Ok(pinnacle) Ok(pinnacle)