diff --git a/src/backend/kms/mod.rs b/src/backend/kms/mod.rs index 6f8f68b8..ec3a062e 100644 --- a/src/backend/kms/mod.rs +++ b/src/backend/kms/mod.rs @@ -68,6 +68,7 @@ use smithay::{ utils::{DeviceFd, Size, Transform}, wayland::{ dmabuf::{get_dmabuf, DmabufFeedbackBuilder, DmabufGlobal}, + drm_lease::{DrmLease, DrmLeaseState}, relative_pointer::RelativePointerManagerState, seat::WaylandFocus, shm::{shm_format_to_fourcc, with_buffer_contents}, @@ -96,7 +97,7 @@ const MIN_RENDER_TIME: Duration = Duration::from_millis(3); #[derive(Debug)] pub struct KmsState { - devices: HashMap, + pub devices: HashMap, pub input_devices: HashMap, pub api: GpuManager>, pub primary: DrmNode, @@ -107,11 +108,14 @@ pub struct KmsState { pub struct Device { render_node: DrmNode, surfaces: HashMap, - drm: DrmDevice, + pub drm: DrmDevice, gbm: GbmDevice, allocator: Box>, formats: HashSet, supports_atomic: bool, + pub non_desktop_connectors: Vec<(connector::Handle, crtc::Handle)>, + pub leasing_global: Option, + pub active_leases: Vec, event_token: Option, socket: Option, } @@ -126,6 +130,9 @@ impl fmt::Debug for Device { .field("allocator", &"...") .field("formats", &self.formats) .field("supports_atomic", &self.supports_atomic) + .field("non_desktop_connectors", &self.non_desktop_connectors) + .field("leasing_global", &self.leasing_global) + .field("active_leases", &self.active_leases) .field("event_token", &self.event_token) .field("socket", &self.socket) .finish() @@ -272,8 +279,11 @@ pub fn init_backend( if let Err(err) = libinput_context.resume() { error!(?err, "Failed to resume libinput context."); } - for device in state.backend.kms().devices.values() { + for device in state.backend.kms().devices.values_mut() { device.drm.activate(); + if let Some(lease_state) = device.leasing_global.as_mut() { + lease_state.resume::(); + } } let dispatcher = dispatcher.clone(); handle.insert_idle(move |state| { @@ -341,6 +351,9 @@ pub fn init_backend( libinput_context.suspend(); for device in state.backend.kms().devices.values_mut() { device.drm.pause(); + if let Some(lease_state) = device.leasing_global.as_mut() { + lease_state.suspend(); + } for surface in device.surfaces.values_mut() { surface.surface = None; if let Some(token) = surface.render_timer_token.take() { @@ -593,6 +606,18 @@ impl State { drm, formats, supports_atomic, + non_desktop_connectors: Vec::new(), + leasing_global: DrmLeaseState::new::(dh, &drm_node) + .map_err(|err| { + // TODO: replace with inspect_err, once stable + warn!( + ?err, + "Failed to initialize drm lease global for: {}", drm_node + ); + err + }) + .ok(), + active_leases: Vec::new(), event_token: Some(token), socket, }; @@ -624,19 +649,62 @@ impl State { init_shaders(&mut renderer).expect("Failed to initialize renderer"); for (crtc, conn) in outputs { - match device.setup_surface(crtc, conn, (w, 0), &mut renderer) { - Ok(output) => { - w += output - .user_data() - .get::>() - .unwrap() - .borrow() - .mode_size() - .w; - wl_outputs.push(output); + let non_desktop = + match drm_helpers::get_property_val(&device.drm, conn, "non-desktop") { + Ok((val_type, value)) => { + val_type.convert_value(value).as_boolean().unwrap() + } + Err(err) => { + warn!( + ?err, + "Failed to determine if connector is meant desktop usage, assuming so." + ); + false + } + }; + + if non_desktop { + let Ok(output_name) = drm_helpers::interface_name(&device.drm, conn) else { + continue + }; + let drm_helpers::EdidInfo { + model, + manufacturer, + } = match drm_helpers::edid_info(&device.drm, conn) { + Ok(info) => info, + Err(_) => drm_helpers::EdidInfo { + model: "Unknown".into(), + manufacturer: "Unknown".into(), + }, + }; + + device.non_desktop_connectors.push((conn, crtc)); + info!( + "Connector {} is non-desktop, setting up for leasing", + output_name + ); + if let Some(lease_state) = device.leasing_global.as_mut() { + lease_state.add_connector::( + conn, + output_name, + format!("{} {}", manufacturer, model), + ); } - Err(err) => warn!(?err, "Failed to initialize output."), - }; + } else { + match device.setup_surface(crtc, conn, (w, 0), &mut renderer) { + Ok(output) => { + w += output + .user_data() + .get::>() + .unwrap() + .borrow() + .mode_size() + .w; + wl_outputs.push(output); + } + Err(err) => warn!(?err, "Failed to initialize output."), + }; + } } backend.devices.insert(drm_node, device); } @@ -714,7 +782,16 @@ impl State { let changes = device.enumerate_surfaces()?; let mut w = self.common.shell.global_space().size.w; for crtc in changes.removed { - if let Some(surface) = device.surfaces.remove(&crtc) { + if let Some(pos) = device + .non_desktop_connectors + .iter() + .position(|(_, handle)| *handle == crtc) + { + let (conn, _) = device.non_desktop_connectors.remove(pos); + if let Some(leasing_state) = device.leasing_global.as_mut() { + leasing_state.withdraw_connector(conn); + } + } else if let Some(surface) = device.surfaces.remove(&crtc) { if let Some(token) = surface.render_timer_token { self.common.event_loop_handle.remove(token); } @@ -723,26 +800,69 @@ impl State { } } for (crtc, conn) in changes.added { - let mut renderer = match backend.api.single_renderer(&device.render_node) { - Ok(renderer) => renderer, - Err(err) => { - warn!(?err, "Failed to initialize output."); - continue; - } - }; - match device.setup_surface(crtc, conn, (w, 0), &mut renderer) { - Ok(output) => { - w += output - .user_data() - .get::>() - .unwrap() - .borrow() - .mode_size() - .w; - outputs_added.push(output); + let non_desktop = + match drm_helpers::get_property_val(&device.drm, conn, "non-desktop") { + Ok((val_type, value)) => { + val_type.convert_value(value).as_boolean().unwrap() + } + Err(err) => { + warn!( + ?err, + "Failed to determine if connector is meant desktop usage, assuming so." + ); + false + } + }; + + if non_desktop { + let Ok(output_name) = drm_helpers::interface_name(&device.drm, conn) else { + continue + }; + let drm_helpers::EdidInfo { + model, + manufacturer, + } = match drm_helpers::edid_info(&device.drm, conn) { + Ok(info) => info, + Err(_) => drm_helpers::EdidInfo { + model: "Unknown".into(), + manufacturer: "Unknown".into(), + }, + }; + + device.non_desktop_connectors.push((conn, crtc)); + info!( + "Connector {} is non-desktop, setting up for leasing", + output_name + ); + if let Some(lease_state) = device.leasing_global.as_mut() { + lease_state.add_connector::( + conn, + output_name, + format!("{} {}", manufacturer, model), + ); } - Err(err) => warn!(?err, "Failed to initialize output."), - }; + } else { + let mut renderer = match backend.api.single_renderer(&device.render_node) { + Ok(renderer) => renderer, + Err(err) => { + warn!(?err, "Failed to initialize output."); + continue; + } + }; + match device.setup_surface(crtc, conn, (w, 0), &mut renderer) { + Ok(output) => { + w += output + .user_data() + .get::>() + .unwrap() + .borrow() + .mode_size() + .w; + outputs_added.push(output); + } + Err(err) => warn!(?err, "Failed to initialize output."), + }; + } } } } @@ -779,6 +899,9 @@ impl State { let mut outputs_removed = Vec::new(); let backend = self.backend.kms(); if let Some(mut device) = backend.devices.remove(&drm_node) { + if let Some(mut leasing_global) = device.leasing_global.take() { + leasing_global.disable_global::(); + } backend.api.as_mut().remove_node(&device.render_node); for surface in device.surfaces.values_mut() { if let Some(token) = surface.render_timer_token.take() { @@ -837,6 +960,11 @@ impl Device { .surfaces .iter() .map(|(c, s)| (*c, s.connector)) + .chain( + self.non_desktop_connectors + .iter() + .map(|(conn, crtc)| (*crtc, *conn)), + ) .collect::>(); let added = config diff --git a/src/wayland/handlers/drm_lease.rs b/src/wayland/handlers/drm_lease.rs new file mode 100644 index 00000000..35c0499b --- /dev/null +++ b/src/wayland/handlers/drm_lease.rs @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use crate::state::State; +use smithay::{ + backend::drm::DrmNode, + delegate_drm_lease, + wayland::drm_lease::{ + DrmLease, DrmLeaseBuilder, DrmLeaseHandler, DrmLeaseRequest, DrmLeaseState, LeaseRejected, + }, +}; + +impl DrmLeaseHandler for State { + fn drm_lease_state(&mut self, node: DrmNode) -> &mut DrmLeaseState { + self.backend + .kms() + .devices + .get_mut(&node) + .unwrap() + .leasing_global + .as_mut() + .unwrap() + } + + fn lease_request( + &mut self, + node: DrmNode, + request: DrmLeaseRequest, + ) -> Result { + let backend = self + .backend + .kms() + .devices + .get_mut(&node) + .ok_or(LeaseRejected::default())?; + + let mut builder = DrmLeaseBuilder::new(&backend.drm); + for conn in request.connectors { + if let Some((_, crtc)) = backend + .non_desktop_connectors + .iter() + .find(|(handle, _)| *handle == conn) + { + builder.add_connector(conn); + builder.add_crtc(*crtc); + let planes = backend + .drm + .planes(crtc) + .map_err(LeaseRejected::with_cause)?; + builder.add_plane(planes.primary.handle); + if let Some(cursor) = planes.cursor { + builder.add_plane(cursor.handle); + } + } else { + tracing::warn!( + ?conn, + "Lease requested for desktop connector, denying request" + ); + return Err(LeaseRejected::default()); + } + } + + Ok(builder) + } + + fn new_active_lease(&mut self, node: DrmNode, lease: DrmLease) { + if let Some(backend) = self.backend.kms().devices.get_mut(&node) { + backend.active_leases.push(lease); + } + // else the backend is gone, drop the lease + } + + fn lease_destroyed(&mut self, node: DrmNode, lease: u32) { + if let Some(backend) = self.backend.kms().devices.get_mut(&node) { + backend.active_leases.retain(|l| l.id() != lease); + } + } +} + +delegate_drm_lease!(State); diff --git a/src/wayland/handlers/mod.rs b/src/wayland/handlers/mod.rs index 8107ec71..57a0a3d6 100644 --- a/src/wayland/handlers/mod.rs +++ b/src/wayland/handlers/mod.rs @@ -5,6 +5,7 @@ pub mod compositor; pub mod data_device; pub mod decoration; pub mod dmabuf; +pub mod drm_lease; pub mod fractional_scale; pub mod keyboard_shortcuts_inhibit; pub mod layer_shell;