diff --git a/src/api.rs b/src/api.rs index 852b506..d501f1d 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1035,9 +1035,14 @@ impl output_service_server::OutputService for OutputService { if let Some(y) = y { loc.y = y; } - state - .pinnacle - .change_output_state(&output, None, None, None, Some(loc)); + state.pinnacle.change_output_state( + &mut state.backend, + &output, + None, + None, + None, + Some(loc), + ); debug!("Mapping output {} to {loc:?}", output.name()); state.pinnacle.request_layout(&output); }) @@ -1067,7 +1072,15 @@ impl output_service_server::OutputService for OutputService { return; }; - state.resize_output(&output, mode); + state.pinnacle.change_output_state( + &mut state.backend, + &output, + Some(mode), + None, + None, + None, + ); + state.pinnacle.request_layout(&output); }) .await } @@ -1102,6 +1115,7 @@ impl output_service_server::OutputService for OutputService { }); state.pinnacle.change_output_state( + &mut state.backend, &output, None, None, @@ -1154,9 +1168,14 @@ impl output_service_server::OutputService for OutputService { return; }; - state - .pinnacle - .change_output_state(&output, None, Some(smithay_transform), None, None); + state.pinnacle.change_output_state( + &mut state.backend, + &output, + None, + Some(smithay_transform), + None, + None, + ); state.pinnacle.request_layout(&output); state.schedule_render(&output); }) diff --git a/src/backend.rs b/src/backend.rs index cbd8490..2a0b8c8 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -151,6 +151,8 @@ pub trait BackendData: 'static { // INFO: only for udev in anvil, maybe shouldn't be a trait fn? fn early_import(&mut self, surface: &WlSurface); + + fn set_output_mode(&mut self, output: &Output, mode: smithay::output::Mode); } impl BackendData for Backend { @@ -180,6 +182,15 @@ impl BackendData for Backend { Backend::Dummy(dummy) => dummy.early_import(surface), } } + + fn set_output_mode(&mut self, output: &Output, mode: smithay::output::Mode) { + match self { + Backend::Winit(winit) => winit.set_output_mode(output, mode), + Backend::Udev(udev) => udev.set_output_mode(output, mode), + #[cfg(feature = "testing")] + Backend::Dummy(dummy) => dummy.set_output_mode(output, mode), + } + } } /// Update surface primary scanout outputs and send frames and dmabuf feedback to visible windows diff --git a/src/backend/dummy.rs b/src/backend/dummy.rs index 274757f..2170976 100644 --- a/src/backend/dummy.rs +++ b/src/backend/dummy.rs @@ -1,6 +1,4 @@ -use pinnacle_api_defs::pinnacle::signal::v0alpha1::{ - OutputConnectResponse, OutputDisconnectResponse, -}; +use pinnacle_api_defs::pinnacle::signal::v0alpha1::OutputConnectResponse; use smithay::backend::renderer::test::DummyRenderer; use smithay::backend::renderer::ImportMemWl; use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface; @@ -50,6 +48,10 @@ impl BackendData for Dummy { fn reset_buffers(&mut self, _output: &Output) {} fn early_import(&mut self, _surface: &WlSurface) {} + + fn set_output_mode(&mut self, _output: &Output, _mode: smithay::output::Mode) { + // TODO: + } } impl Dummy { @@ -141,14 +143,4 @@ impl Pinnacle { }); }); } - - pub fn remove_output(&mut self, output: &Output) { - self.space.unmap_output(output); - - self.signal_state.output_disconnect.signal(|buffer| { - buffer.push_back(OutputDisconnectResponse { - output_name: Some(output.name()), - }) - }); - } } diff --git a/src/backend/udev.rs b/src/backend/udev.rs index 6d9fe26..519a9a9 100644 --- a/src/backend/udev.rs +++ b/src/backend/udev.rs @@ -71,7 +71,6 @@ use smithay::{ presentation_time::server::wp_presentation_feedback, }, wayland_server::{ - backend::GlobalId, protocol::{wl_shm, wl_surface::WlSurface}, DisplayHandle, }, @@ -527,9 +526,12 @@ impl Udev { /// Schedule a new render that will cause the compositor to redraw everything. pub fn schedule_render(&mut self, loop_handle: &LoopHandle, output: &Output) { let Some(surface) = render_surface_for_output(output, &mut self.backends) else { + tracing::info!("no render surface on output {}", output.name()); return; }; + // tracing::info!(state = ?surface.render_state, name = output.name()); + match &surface.render_state { RenderState::Idle => { let output = output.clone(); @@ -577,6 +579,12 @@ impl Udev { { warn!("Failed to reset compositor state on crtc {crtc:?}: {err}"); } + + if let Some(surface) = render_surface_for_output(output, &mut self.backends) { + if let RenderState::Scheduled(idle) = std::mem::take(&mut surface.render_state) { + idle.cancel(); + } + } } } } @@ -638,51 +646,6 @@ impl State { // ); } } - - /// Resize the output with the given mode. - /// - /// TODO: This is in udev.rs but is also used in winit.rs. - /// | I've got no clue how to make things public without making a mess. - pub fn resize_output(&mut self, output: &Output, mode: smithay::output::Mode) { - if let Backend::Udev(udev) = &mut self.backend { - let drm_mode = udev.backends.iter().find_map(|(_, backend)| { - backend - .drm_scanner - .crtcs() - .find(|(_, handle)| { - output - .user_data() - .get::() - .is_some_and(|data| &data.crtc == handle) - }) - .and_then(|(info, _)| { - info.modes() - .iter() - .find(|m| smithay::output::Mode::from(**m) == mode) - }) - .copied() - }); - - if let Some(drm_mode) = drm_mode { - if let Some(render_surface) = render_surface_for_output(output, &mut udev.backends) - { - match render_surface.compositor.use_mode(drm_mode) { - Ok(()) => { - self.pinnacle - .change_output_state(output, Some(mode), None, None, None); - } - Err(err) => error!("Failed to resize output: {err}"), - } - } - } - } else { - self.pinnacle - .change_output_state(output, Some(mode), None, None, None); - } - - self.pinnacle.request_layout(output); - self.schedule_render(output); - } } impl BackendData for Udev { @@ -705,6 +668,45 @@ impl BackendData for Udev { warn!("early buffer import failed: {}", err); } } + + fn set_output_mode(&mut self, output: &Output, mode: smithay::output::Mode) { + let drm_mode = self.backends.iter().find_map(|(_, backend)| { + backend + .drm_scanner + .crtcs() + .find(|(_, handle)| { + output + .user_data() + .get::() + .is_some_and(|data| &data.crtc == handle) + }) + .and_then(|(info, _)| { + info.modes() + .iter() + .find(|m| smithay::output::Mode::from(**m) == mode) + }) + .copied() + }); + + if let Some(drm_mode) = drm_mode { + if let Some(render_surface) = render_surface_for_output(output, &mut self.backends) { + match render_surface.compositor.use_mode(drm_mode) { + Ok(()) => { + output.change_current_state(Some(mode), None, None, None); + output.with_state_mut(|state| { + if !state.modes.contains(&mode) { + state.modes.push(mode); + } + }); + } + Err(err) => warn!("Failed to resize output: {err}"), + } + } + } else { + // TODO: create new drm mode with cvt + tracing::info!("no drm mode for mode"); + } + } } // TODO: document desperately @@ -810,7 +812,6 @@ enum RenderState { /// The idle token from a render being scheduled. /// This is used to cancel renders if, for example, /// the output being rendered is removed. - #[allow(dead_code)] // TODO: Idle<'static>, ), /// A frame was rendered and scheduled and we are waiting for vblank. @@ -823,10 +824,6 @@ enum RenderState { /// Render surface for an output. struct RenderSurface { - /// The output global id. - global: Option, - /// A display handle used to remove the global on drop. - display_handle: DisplayHandle, /// The node from `connector_connected`. device_id: DrmNode, /// The node rendering to the screen? idk @@ -862,15 +859,6 @@ struct ScreencopyCommitState { _cursor: CommitCounter, } -impl Drop for RenderSurface { - // Stop advertising this output to clients on drop. - fn drop(&mut self) { - if let Some(global) = self.global.take() { - self.display_handle.remove_global::(global); - } - } -} - type GbmDrmCompositor = DrmCompositor< GbmAllocator, GbmDevice, @@ -1055,11 +1043,12 @@ impl Udev { ); let global = output.create_global::(&self.display_handle); - pinnacle - .output_management_manager_state - .add_head::(&output); + pinnacle.outputs.insert(output.clone(), global); - output.with_state_mut(|state| state.serial = serial); + output.with_state_mut(|state| { + state.serial = serial; + state.powered = true; + }); output.set_preferred(wl_mode); @@ -1071,6 +1060,10 @@ impl Udev { .collect::>(); output.with_state_mut(|state| state.modes = modes); + pinnacle + .output_management_manager_state + .add_head::(&output); + let x = pinnacle.space.outputs().fold(0, |acc, o| { let Some(geo) = pinnacle.space.output_geometry(o) else { unreachable!() @@ -1131,10 +1124,8 @@ impl Udev { ); let surface = RenderSurface { - display_handle: self.display_handle.clone(), device_id: node, render_node: device.render_node, - global: Some(global), compositor, dmabuf_feedback, render_state: RenderState::Idle, @@ -1145,7 +1136,7 @@ impl Udev { device.surfaces.insert(crtc, surface); - pinnacle.change_output_state(&output, Some(wl_mode), None, None, Some(position)); + pinnacle.change_output_state(self, &output, Some(wl_mode), None, None, Some(position)); // If there is saved connector state, the connector was previously plugged in. // In this case, restore its tags and location. @@ -1157,7 +1148,7 @@ impl Udev { { let ConnectorSavedState { loc, tags, scale } = saved_state; output.with_state_mut(|state| state.tags.clone_from(tags)); - pinnacle.change_output_state(&output, None, None, *scale, Some(*loc)); + pinnacle.change_output_state(self, &output, None, None, *scale, Some(*loc)); } else { pinnacle.signal_state.output_connect.signal(|buffer| { buffer.push_back(OutputConnectResponse { @@ -1223,6 +1214,11 @@ impl Udev { pinnacle .output_management_manager_state .remove_head(&output); + + if let Some(global) = pinnacle.outputs.remove(&output) { + // TODO: disable ahead of time + pinnacle.display_handle.remove_global::(global); + } } } @@ -1298,11 +1294,15 @@ impl Udev { return; }; - let output = if let Some(output) = pinnacle.space.outputs().find(|o| { - let udev_op_data = o.user_data().get::(); - udev_op_data - .is_some_and(|data| data.device_id == surface.device_id && data.crtc == crtc) - }) { + let output = if let Some(output) = pinnacle + .outputs + .keys() + .chain(pinnacle.unmapped_outputs.iter()) + .find(|o| { + let udev_op_data = o.user_data().get::(); + udev_op_data + .is_some_and(|data| data.device_id == surface.device_id && data.crtc == crtc) + }) { output.clone() } else { // somehow we got called with an invalid output @@ -1394,6 +1394,11 @@ impl Udev { assert!(matches!(surface.render_state, RenderState::Scheduled(_))); + if !pinnacle.outputs.contains_key(output) { + surface.render_state = RenderState::Idle; + return; + } + // TODO: possibly lift this out and make it so that scheduling a render // does nothing on powered off outputs if output.with_state(|state| !state.powered) { diff --git a/src/backend/winit.rs b/src/backend/winit.rs index f02ec8f..c3f2261 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -67,6 +67,10 @@ impl BackendData for Winit { } fn early_import(&mut self, _surface: &WlSurface) {} + + fn set_output_mode(&mut self, output: &Output, mode: smithay::output::Mode) { + output.change_current_state(Some(mode), None, None, None); + } } impl Backend { @@ -201,6 +205,7 @@ impl Winit { refresh: 144_000, }; state.pinnacle.change_output_state( + &mut state.backend, &output, Some(mode), None, diff --git a/src/handlers.rs b/src/handlers.rs index 2194fdd..5a0d3dd 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -22,7 +22,7 @@ use smithay::{ pointer::{CursorImageStatus, PointerHandle}, Seat, SeatHandler, SeatState, }, - output::{Output, Scale}, + output::{Mode, Output, Scale}, reexports::{ calloop::Interest, wayland_protocols::xdg::shell::server::xdg_positioner::ConstraintAdjustment, @@ -930,21 +930,55 @@ impl OutputManagementHandler for State { fn apply_configuration(&mut self, config: HashMap) -> bool { for (output, config) in config { match config { - OutputConfiguration::Disabled => todo!(), + OutputConfiguration::Disabled => { + self.pinnacle.set_output_enabled(&output, false); + // TODO: split + self.backend.set_output_powered(&output, false); + } OutputConfiguration::Enabled { mode, position, transform, scale, - adaptive_sync, + adaptive_sync: _, } => { + self.pinnacle.set_output_enabled(&output, true); + // TODO: split + self.backend.set_output_powered(&output, true); + + self.schedule_render(&output); + let snapshots = self.backend.with_renderer(|renderer| { capture_snapshots_on_output(&mut self.pinnacle, renderer, &output, []) }); + let mode = mode.map(|(size, refresh)| { + if let Some(refresh) = refresh { + Mode { + size, + refresh: refresh.get() as i32, + } + } else { + output + .with_state(|state| { + state + .modes + .iter() + .filter(|mode| mode.size == size) + .max_by_key(|mode| mode.refresh) + .copied() + }) + .unwrap_or(Mode { + size, + refresh: 60_000, + }) + } + }); + self.pinnacle.change_output_state( + &mut self.backend, &output, - None, + mode, transform, scale.map(Scale::Fractional), position, diff --git a/src/output.rs b/src/output.rs index 66e5503..f00ae21 100644 --- a/src/output.rs +++ b/src/output.rs @@ -2,7 +2,9 @@ use std::{cell::RefCell, num::NonZeroU32}; -use pinnacle_api_defs::pinnacle::signal::v0alpha1::{OutputMoveResponse, OutputResizeResponse}; +use pinnacle_api_defs::pinnacle::signal::v0alpha1::{ + OutputDisconnectResponse, OutputMoveResponse, OutputResizeResponse, +}; use smithay::{ desktop::layer_map_for_output, output::{Mode, Output, Scale}, @@ -12,6 +14,8 @@ use smithay::{ }; use crate::{ + backend::BackendData, + config::ConnectorSavedState, focus::WindowKeyboardFocusStack, layout::transaction::{LayoutTransaction, SnapshotTarget}, protocol::screencopy::Screencopy, @@ -51,7 +55,7 @@ pub enum BlankingState { } /// The state of an output -#[derive(Debug)] +#[derive(Debug, Default)] pub struct OutputState { pub tags: Vec, pub focus_stack: WindowKeyboardFocusStack, @@ -69,22 +73,6 @@ pub struct OutputState { pub powered: bool, } -impl Default for OutputState { - fn default() -> Self { - Self { - tags: Default::default(), - focus_stack: Default::default(), - screencopy: Default::default(), - serial: Default::default(), - modes: Default::default(), - lock_surface: Default::default(), - blanking_state: Default::default(), - layout_transaction: Default::default(), - powered: true, - } - } -} - impl WithState for Output { type State = OutputState; @@ -135,10 +123,9 @@ impl OutputState { } impl Pinnacle { - /// A wrapper around [`Output::change_current_state`] that additionally sends an output - /// geometry signal. pub fn change_output_state( &mut self, + backend: &mut impl BackendData, output: &Output, mode: Option, transform: Option, @@ -147,7 +134,7 @@ impl Pinnacle { ) { let old_scale = output.current_scale().fractional_scale(); - output.change_current_state(mode, transform, scale, location); + output.change_current_state(None, transform, scale, location); if let Some(location) = location { self.space.map_output(output, location); self.signal_state.output_move.signal(|buf| { @@ -158,6 +145,11 @@ impl Pinnacle { }); }); } + + if let Some(mode) = mode { + backend.set_output_mode(output, mode); + } + if mode.is_some() || transform.is_some() || scale.is_some() { layer_map_for_output(output).arrange(); self.signal_state.output_resize.signal(|buf| { @@ -169,10 +161,6 @@ impl Pinnacle { }); }); } - if let Some(mode) = mode { - output.set_preferred(mode); - output.with_state_mut(|state| state.modes.push(mode)); - } if let Some(scale) = scale { let pos_multiplier = old_scale / scale.fractional_scale(); @@ -223,4 +211,79 @@ impl Pinnacle { self.output_management_manager_state .update_head::(output); } + + pub fn set_output_enabled(&mut self, output: &Output, enabled: bool) { + if enabled { + self.unmapped_outputs.remove(output); + if !self.outputs.contains_key(output) { + let global = output.create_global::(&self.display_handle); + self.outputs.insert(output.clone(), global); + } + self.space.map_output(output, output.current_location()); + + // TODO: output connect? + } else { + let global = self.outputs.remove(output); + if let Some(global) = global { + self.display_handle.remove_global::(global); + } + self.space.unmap_output(output); + self.unmapped_outputs.insert(output.clone()); + + // TODO: should this trigger the signal? + self.signal_state.output_disconnect.signal(|buffer| { + buffer.push_back(OutputDisconnectResponse { + output_name: Some(output.name()), + }) + }); + + self.gamma_control_manager_state.output_removed(output); + + self.config.connector_saved_states.insert( + OutputName(output.name()), + ConnectorSavedState { + loc: output.current_location(), + tags: output.with_state(|state| state.tags.clone()), + scale: Some(output.current_scale()), + }, + ); + + for layer in layer_map_for_output(output).layers() { + layer.layer_surface().send_close(); + } + } + } + + pub fn remove_output(&mut self, output: &Output) { + let global = self.outputs.remove(output); + if let Some(global) = global { + self.display_handle.remove_global::(global); + } + self.unmapped_outputs.remove(output); + + self.space.unmap_output(output); + + self.gamma_control_manager_state.output_removed(output); + + self.output_management_manager_state.remove_head(output); + + self.signal_state.output_disconnect.signal(|buffer| { + buffer.push_back(OutputDisconnectResponse { + output_name: Some(output.name()), + }) + }); + + self.config.connector_saved_states.insert( + OutputName(output.name()), + ConnectorSavedState { + loc: output.current_location(), + tags: output.with_state(|state| state.tags.clone()), + scale: Some(output.current_scale()), + }, + ); + + for layer in layer_map_for_output(output).layers() { + layer.layer_surface().send_close(); + } + } } diff --git a/src/protocol/output_management.rs b/src/protocol/output_management.rs index e3eb3b4..d975b58 100644 --- a/src/protocol/output_management.rs +++ b/src/protocol/output_management.rs @@ -101,7 +101,7 @@ pub struct OutputData { position: Point, transform: Transform, scale: f64, - adaptive_sync: bool, + _adaptive_sync: bool, } impl OutputManagementManagerState { @@ -127,7 +127,7 @@ impl OutputManagementManagerState { position: output.current_location(), transform: output.current_transform(), scale: output.current_scale().fractional_scale(), - adaptive_sync: false, // TODO: + _adaptive_sync: false, // TODO: }; self.outputs.insert(output.clone(), output_data); @@ -336,7 +336,7 @@ where head.physical_size(physical_props.size.w, physical_props.size.h); let mut wlr_modes = Vec::new(); - for mode in output.modes() { + for mode in output.with_state(|state| state.modes.clone()) { let wlr_mode = client .create_resource::(display, manager.version(), mode) .unwrap(); @@ -551,13 +551,13 @@ where impl Dispatch for OutputManagementManagerState { fn request( - state: &mut D, - client: &Client, - resource: &ZwlrOutputHeadV1, + _state: &mut D, + _client: &Client, + _resource: &ZwlrOutputHeadV1, request: ::Request, - data: &Output, - dhandle: &DisplayHandle, - data_init: &mut DataInit<'_, D>, + _data: &Output, + _dhandle: &DisplayHandle, + _data_init: &mut DataInit<'_, D>, ) { match request { zwlr_output_head_v1::Request::Release => { @@ -570,13 +570,13 @@ impl Dispatch for OutputManagementManagerState { impl Dispatch for OutputManagementManagerState { fn request( - state: &mut D, - client: &Client, - resource: &ZwlrOutputModeV1, + _state: &mut D, + _client: &Client, + _resource: &ZwlrOutputModeV1, request: ::Request, - data: &Mode, - dhandle: &DisplayHandle, - data_init: &mut DataInit<'_, D>, + _data: &Mode, + _dhandle: &DisplayHandle, + _data_init: &mut DataInit<'_, D>, ) { match request { zwlr_output_mode_v1::Request::Release => { diff --git a/src/state.rs b/src/state.rs index 3608a86..4c19287 100644 --- a/src/state.rs +++ b/src/state.rs @@ -22,10 +22,11 @@ use pinnacle_api_defs::pinnacle::v0alpha1::ShutdownWatchResponse; use smithay::{ desktop::{PopupManager, Space}, input::{keyboard::XkbConfig, pointer::CursorImageStatus, Seat, SeatState}, + output::Output, reexports::{ calloop::{generic::Generic, Interest, LoopHandle, LoopSignal, Mode, PostAction}, wayland_server::{ - backend::{ClientData, ClientId, DisconnectReason}, + backend::{ClientData, ClientId, DisconnectReason, GlobalId}, protocol::wl_surface::WlSurface, Client, Display, DisplayHandle, }, @@ -149,6 +150,9 @@ pub struct Pinnacle { /// WlSurfaces with an attached idle inhibitor. pub idle_inhibiting_surfaces: HashSet, + + pub outputs: HashMap, + pub unmapped_outputs: HashSet, } impl State { @@ -343,6 +347,9 @@ impl Pinnacle { root_surface_cache: HashMap::new(), idle_inhibiting_surfaces: HashSet::new(), + + outputs: HashMap::new(), + unmapped_outputs: HashSet::new(), }; Ok(pinnacle)