From 88a396575c37dcfefb968d4e53cb84f9ba3914f0 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Fri, 26 Apr 2024 15:43:55 +0200 Subject: [PATCH 1/7] metal: use manual Debug implementations --- src/backends/metal/video.rs | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 9d17d04c..1a9d95ff 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -67,7 +67,6 @@ pub struct MetalRenderContext { pub gfx: Rc, } -#[derive(Debug)] pub struct MetalDrmDevice { pub backend: Rc, pub id: DrmDeviceId, @@ -91,6 +90,12 @@ pub struct MetalDrmDevice { pub is_nvidia: bool, } +impl Debug for MetalDrmDevice { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("MetalDrmDevice").finish_non_exhaustive() + } +} + impl MetalDrmDevice { pub fn is_render_device(&self) -> bool { if let Some(ctx) = self.backend.ctx.get() { @@ -189,7 +194,6 @@ impl ConnectorDisplayData { } } -#[derive(Debug)] pub struct MetalConnector { pub id: DrmConnector, pub master: Rc, @@ -240,7 +244,12 @@ pub struct MetalConnector { pub direct_scanout_active: Cell, } -#[derive(Debug)] +impl Debug for MetalConnector { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("MetalConnnector").finish_non_exhaustive() + } +} + pub struct MetalHardwareCursor { pub generation: u64, pub connector: Rc, @@ -253,6 +262,13 @@ pub struct MetalHardwareCursor { pub have_changes: Cell, } +impl Debug for MetalHardwareCursor { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("MetalHardwareCursor") + .finish_non_exhaustive() + } +} + impl HardwareCursor for MetalHardwareCursor { fn set_enabled(&self, enabled: bool) { if self.cursor_enabled_pending.replace(enabled) != enabled { @@ -929,7 +945,6 @@ impl Connector for MetalConnector { } } -#[derive(Debug)] pub struct MetalCrtc { pub id: DrmCrtc, pub idx: usize, @@ -946,6 +961,12 @@ pub struct MetalCrtc { pub mode_blob: CloneCell>>, } +impl Debug for MetalCrtc { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("MetalCrtc").finish_non_exhaustive() + } +} + #[derive(Debug)] pub struct MetalEncoder { pub id: DrmEncoder, @@ -965,7 +986,6 @@ pub struct PlaneFormat { modifiers: IndexSet, } -#[derive(Debug)] pub struct MetalPlane { pub id: DrmPlane, pub master: Rc, @@ -993,6 +1013,12 @@ pub struct MetalPlane { pub fb_id: DrmProperty, } +impl Debug for MetalPlane { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("MetalPlane").finish_non_exhaustive() + } +} + fn get_connectors( backend: &Rc, dev: &Rc, From b5f1166360d64e1c928a6ad88b83a2c287d6dff6 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Thu, 25 Apr 2024 13:27:28 +0200 Subject: [PATCH 2/7] metal: don't use non-desktop screens --- src/backends/metal/video.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 1a9d95ff..13b542fc 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -172,6 +172,7 @@ pub struct ConnectorDisplayData { pub modes: Vec, pub mode: Option, pub refresh: u32, + pub non_desktop: bool, pub monitor_manufacturer: String, pub monitor_name: String, @@ -1190,6 +1191,7 @@ fn create_connector_display_data( modes: info.modes, mode, refresh, + non_desktop: props.get("non-desktop")?.value.get() != 0, monitor_manufacturer: manufacturer, monitor_name: name, monitor_serial_number: serial_number, @@ -1744,8 +1746,7 @@ impl MetalBackend { if let Err(e) = self.update_device_properties(dev) { return Err(MetalError::UpdateProperties(e)); } - let mut preserve = Preserve::default(); - self.init_drm_device(dev, &mut preserve)?; + self.init_drm_device(dev, &mut Preserve::default())?; for connector in dev.connectors.lock().values() { if connector.primary_plane.is_some() { connector.schedule_present(); @@ -2011,8 +2012,7 @@ impl MetalBackend { } fn re_init_drm_device(&self, dev: &Rc) { - let mut preserve = Preserve::default(); - if let Err(e) = self.init_drm_device(dev, &mut preserve) { + if let Err(e) = self.init_drm_device(dev, &mut Preserve::default()) { log::error!("Could not initialize device: {}", ErrorFmt(e)); } for connector in dev.connectors.lock().values() { @@ -2076,9 +2076,9 @@ impl MetalBackend { for connector in dev.connectors.lock().values() { let dd = connector.display.borrow_mut(); - if !connector.enabled.get() || dd.connection != ConnectorStatus::Connected { + if should_ignore(connector, &dd) { if dd.crtc_id.value.get().is_some() { - log::debug!("Connector is not connected but has an assigned crtc"); + log::debug!("Connector should be ignored but has an assigned crtc"); return false; } continue; @@ -2306,7 +2306,7 @@ impl MetalBackend { changes: &mut Change, ) -> Result<(), MetalError> { let dd = connector.display.borrow_mut(); - if !connector.enabled.get() || dd.connection != ConnectorStatus::Connected { + if should_ignore(connector, &dd) { return Ok(()); } let crtc = 'crtc: { @@ -2521,3 +2521,7 @@ fn modes_equal(a: &DrmModeInfo, b: &DrmModeInfo) -> bool { && a.vrefresh == b.vrefresh && a.flags == b.flags } + +fn should_ignore(connector: &MetalConnector, dd: &ConnectorDisplayData) -> bool { + !connector.enabled.get() || dd.connection != ConnectorStatus::Connected || dd.non_desktop +} From fa3d8709355e9afed33b89d065b9db05594bbdc2 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Thu, 25 Apr 2024 15:13:29 +0200 Subject: [PATCH 3/7] tree: access outputs via root node --- src/backends/metal/video.rs | 4 ++-- src/ifs/ext_session_lock_v1.rs | 4 ++-- src/ifs/jay_compositor.rs | 4 ++-- src/ifs/wl_seat.rs | 6 +++--- src/ifs/wl_seat/event_handling.rs | 12 ++++++------ src/ifs/zwlr_layer_shell_v1.rs | 4 ++-- src/it/testrun.rs | 4 ++-- src/state.rs | 5 +++-- src/tasks/connector.rs | 18 +++++++++--------- 9 files changed, 31 insertions(+), 30 deletions(-) diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 13b542fc..5c3bd83d 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -1804,11 +1804,11 @@ impl MetalBackend { } let dd = connector.display.borrow_mut(); { - let global = self.state.outputs.get(&connector.connector_id); + let global = self.state.root.outputs.get(&connector.connector_id); let mut rr = connector.render_result.borrow_mut(); if let Some(g) = &global { let refresh = dd.refresh; - let bindings = g.node.global.bindings.borrow_mut(); + let bindings = g.global.bindings.borrow_mut(); for fb in rr.presentation_feedbacks.drain(..) { if let Some(bindings) = bindings.get(&fb.client.id) { for binding in bindings.values() { diff --git a/src/ifs/ext_session_lock_v1.rs b/src/ifs/ext_session_lock_v1.rs index 6fad8599..51479322 100644 --- a/src/ifs/ext_session_lock_v1.rs +++ b/src/ifs/ext_session_lock_v1.rs @@ -90,8 +90,8 @@ impl ExtSessionLockV1RequestHandler for ExtSessionLockV1 { let state = &self.client.state; state.lock.locked.set(false); state.lock.lock.take(); - for output in state.outputs.lock().values() { - if let Some(surface) = output.node.set_lock_surface(None) { + for output in state.root.outputs.lock().values() { + if let Some(surface) = output.set_lock_surface(None) { surface.destroy_node(); } } diff --git a/src/ifs/jay_compositor.rs b/src/ifs/jay_compositor.rs index a25ff3ee..ddbdba84 100644 --- a/src/ifs/jay_compositor.rs +++ b/src/ifs/jay_compositor.rs @@ -220,8 +220,8 @@ impl JayCompositorRequestHandler for JayCompositor { if let Some(lock) = state.lock.lock.take() { lock.finish(); } - for output in state.outputs.lock().values() { - if let Some(surface) = output.node.set_lock_surface(None) { + for output in state.root.outputs.lock().values() { + if let Some(surface) = output.set_lock_surface(None) { surface.destroy_node(); } } diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index 6fc3a530..abfda057 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -596,10 +596,10 @@ impl WlSeatGlobal { self.update_hardware_cursor_position(); self.trigger_tree_changed(); let output = 'set_output: { - let outputs = self.state.outputs.lock(); + let outputs = self.state.root.outputs.lock(); for output in outputs.values() { - if output.node.global.pos.get().contains(x, y) { - break 'set_output output.node.clone(); + if output.global.pos.get().contains(x, y) { + break 'set_output output.clone(); } } self.state.dummy_output.get().unwrap() diff --git a/src/ifs/wl_seat/event_handling.rs b/src/ifs/wl_seat/event_handling.rs index 92d86a21..deb943cb 100644 --- a/src/ifs/wl_seat/event_handling.rs +++ b/src/ifs/wl_seat/event_handling.rs @@ -241,12 +241,12 @@ impl WlSeatGlobal { mut x: Fixed, mut y: Fixed, ) { - let output = match self.state.outputs.get(&connector) { + let output = match self.state.root.outputs.get(&connector) { Some(o) => o, _ => return, }; - self.set_output(&output.node); - let pos = output.node.global.pos.get(); + self.set_output(&output); + let pos = output.global.pos.get(); x += Fixed::from_int(pos.x1()); y += Fixed::from_int(pos.y1()); if let Some(c) = self.constraint.get() { @@ -314,10 +314,10 @@ impl WlSeatGlobal { let mut y_int = y.round_down(); if !pos.contains(x_int, y_int) { 'warp: { - let outputs = self.state.outputs.lock(); + let outputs = self.state.root.outputs.lock(); for output in outputs.values() { - if output.node.global.pos.get().contains(x_int, y_int) { - self.set_output(&output.node); + if output.global.pos.get().contains(x_int, y_int) { + self.set_output(output); break 'warp; } } diff --git a/src/ifs/zwlr_layer_shell_v1.rs b/src/ifs/zwlr_layer_shell_v1.rs index c192fb38..ec1b13b1 100644 --- a/src/ifs/zwlr_layer_shell_v1.rs +++ b/src/ifs/zwlr_layer_shell_v1.rs @@ -65,9 +65,9 @@ impl ZwlrLayerShellV1RequestHandler for ZwlrLayerShellV1 { break 'get_output output.global.opt.clone(); } } - let outputs = self.client.state.outputs.lock(); + let outputs = self.client.state.root.outputs.lock(); if let Some(output) = outputs.values().next() { - break 'get_output output.node.global.opt.clone(); + break 'get_output output.global.opt.clone(); } return Err(ZwlrLayerShellV1Error::NoOutputs); } diff --git a/src/it/testrun.rs b/src/it/testrun.rs index 5f26c758..46f6953d 100644 --- a/src/it/testrun.rs +++ b/src/it/testrun.rs @@ -109,9 +109,9 @@ impl TestRun { self.backend.install_default()?; let seat = self.get_seat("default")?; self.state.eng.yield_now().await; - let output = match self.state.outputs.lock().values().next() { + let output = match self.state.root.outputs.lock().values().next() { None => bail!("No output"), - Some(d) => d.node.clone(), + Some(d) => d.clone(), }; self.cfg .set_input_device_seat(self.backend.default_kb.common.id, seat.id())?; diff --git a/src/state.rs b/src/state.rs index be8797f7..7e75a0c5 100644 --- a/src/state.rs +++ b/src/state.rs @@ -731,8 +731,9 @@ impl State { connector.handler.take(); connector.async_event.clear(); } - for (_, output) in self.outputs.lock().drain() { - output.node.clear(); + self.outputs.clear(); + for output in self.root.outputs.lock().values() { + output.clear(); } self.dbus.clear(); self.pending_container_layout.clear(); diff --git a/src/tasks/connector.rs b/src/tasks/connector.rs index 2b574556..a100b36f 100644 --- a/src/tasks/connector.rs +++ b/src/tasks/connector.rs @@ -158,10 +158,13 @@ impl ConnectorHandler { node: on.clone(), }); self.state.outputs.set(self.id, output_data); + on.schedule_update_render_data(); + self.state.root.outputs.set(self.id, on.clone()); + self.state.root.update_extents(); global.opt.node.set(Some(on.clone())); global.opt.global.set(Some(global.clone())); let mut ws_to_move = VecDeque::new(); - if self.state.outputs.len() == 1 { + if self.state.root.outputs.len() == 1 { let seats = self.state.globals.seats.lock(); let pos = global.pos.get(); let x = (pos.x1() + pos.x2()) / 2; @@ -177,11 +180,11 @@ impl ConnectorHandler { ws_to_move.push_back(ws); } } - for source in self.state.outputs.lock().values() { - if source.node.id == on.id { + for source in self.state.root.outputs.lock().values() { + if source.id == on.id { continue; } - for ws in source.node.workspaces.iter() { + for ws in source.workspaces.iter() { if ws.is_dummy { continue; } @@ -203,9 +206,6 @@ impl ConnectorHandler { if let Some(config) = self.state.config.get() { config.connector_connected(self.id); } - on.schedule_update_render_data(); - self.state.root.outputs.set(self.id, on.clone()); - self.state.root.update_extents(); self.state.add_global(&global); self.state.tree_changed(); 'outer: loop { @@ -255,8 +255,8 @@ impl ConnectorHandler { surface.send_closed(); } } - let target = match self.state.outputs.lock().values().next() { - Some(o) => o.node.clone(), + let target = match self.state.root.outputs.lock().values().next() { + Some(o) => o.clone(), _ => self.state.dummy_output.get().unwrap(), }; for ws in on.workspaces.iter() { From 24d08918c45b4c030927d9e58f080d8e7154fc0a Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Thu, 25 Apr 2024 16:31:29 +0200 Subject: [PATCH 4/7] metal: send non-desktop outputs to the frontend --- src/backend.rs | 1 + src/backends/metal/video.rs | 11 ++- src/backends/x.rs | 1 + src/cli/randr.rs | 30 ++++++- src/config/handler.rs | 55 +++++++------ src/ifs/jay_randr.rs | 87 ++++++++++++++------- src/it/test_backend.rs | 1 + src/it/tests/t0034_workspace_restoration.rs | 1 + src/state.rs | 2 +- src/tasks/connector.rs | 49 +++++++++++- wire/jay_randr.txt | 8 ++ 11 files changed, 184 insertions(+), 62 deletions(-) diff --git a/src/backend.rs b/src/backend.rs index da81118f..a6a122e9 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -59,6 +59,7 @@ pub struct MonitorInfo { pub initial_mode: Mode, pub width_mm: i32, pub height_mm: i32, + pub non_desktop: bool, } #[derive(Copy, Clone, Debug)] diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 5c3bd83d..6f9991ad 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -409,9 +409,7 @@ impl MetalConnector { fn connected(&self) -> bool { let dd = self.display.borrow_mut(); - self.enabled.get() - && dd.connection == ConnectorStatus::Connected - && self.primary_plane.is_some() + self.enabled.get() && dd.connection == ConnectorStatus::Connected } pub fn schedule_present(&self) { @@ -909,6 +907,9 @@ impl Connector for MetalConnector { fn set_mode(&self, be_mode: Mode) { let mut dd = self.display.borrow_mut(); + if dd.non_desktop { + return; + } let Some(mode) = dd.modes.iter().find(|m| m.to_backend() == be_mode) else { log::warn!("Connector does not support mode {:?}", be_mode); return; @@ -1561,6 +1562,7 @@ impl MetalBackend { initial_mode: dd.mode.clone().unwrap().to_backend(), width_mm: dd.mm_width as _, height_mm: dd.mm_height as _, + non_desktop: dd.non_desktop, })); connector.connect_sent.set(true); connector.send_hardware_cursor(); @@ -2457,6 +2459,9 @@ impl MetalBackend { if !connector.connect_sent.get() { self.send_connected(connector, &dd); } + if connector.primary_plane.is_none() { + return; + } if log_mode { log::info!( "Initialized connector {}-{} with mode {:?}", diff --git a/src/backends/x.rs b/src/backends/x.rs index 88f2c8ac..3b8ed707 100644 --- a/src/backends/x.rs +++ b/src/backends/x.rs @@ -574,6 +574,7 @@ impl XBackend { }, width_mm: output.width.get(), height_mm: output.height.get(), + non_desktop: false, })); output.changed(); self.present(output).await; diff --git a/src/cli/randr.rs b/src/cli/randr.rs index f459d29d..63db0fba 100644 --- a/src/cli/randr.rs +++ b/src/cli/randr.rs @@ -217,6 +217,7 @@ struct Output { pub height_mm: i32, pub current_mode: Option, pub modes: Vec, + pub non_desktop: bool, } #[derive(Copy, Clone, Debug)] @@ -479,12 +480,16 @@ impl Randr { println!(" product: {}", o.product); println!(" manufacturer: {}", o.manufacturer); println!(" serial number: {}", o.serial_number); - println!(" position: {} x {}", o.x, o.y); - println!(" logical size: {} x {}", o.width, o.height); println!( " physical size: {}mm x {}mm", o.width_mm, o.height_mm ); + if o.non_desktop { + println!(" non-desktop"); + return; + } + println!(" position: {} x {}", o.x, o.y); + println!(" logical size: {} x {}", o.width, o.height); if let Some(mode) = &o.current_mode { print!(" mode: "); self.print_mode(mode, false); @@ -570,6 +575,27 @@ impl Randr { height_mm: msg.height_mm, modes: Default::default(), current_mode: None, + non_desktop: false, + }); + }); + jay_randr::NonDesktopOutput::handle(tc, randr, data.clone(), |data, msg| { + let mut data = data.borrow_mut(); + let c = data.connectors.last_mut().unwrap(); + c.output = Some(Output { + scale: 1.0, + width: 0, + height: 0, + x: 0, + y: 0, + transform: Transform::None, + manufacturer: msg.manufacturer.to_string(), + product: msg.product.to_string(), + serial_number: msg.serial_number.to_string(), + width_mm: msg.width_mm, + height_mm: msg.height_mm, + modes: Default::default(), + current_mode: None, + non_desktop: true, }); }); jay_randr::Mode::handle(tc, randr, data.clone(), |data, msg| { diff --git a/src/config/handler.rs b/src/config/handler.rs index a5b93022..a3061c8d 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -514,6 +514,14 @@ impl ConfigProxyHandler { } } + fn get_output_node(&self, connector: Connector) -> Result, CphError> { + let data = self.get_output(connector)?; + match data.node.clone() { + Some(d) => Ok(d), + _ => Err(CphError::OutputIsNotDesktop(connector)), + } + } + fn get_drm_device(&self, dev: DrmDevice) -> Result, CphError> { match self.state.drm_devs.get(&DrmDeviceId::from_raw(dev.0 as _)) { Some(dev) => Ok(dev), @@ -783,7 +791,7 @@ impl ConfigProxyHandler { workspace: WorkspaceSource, connector: Connector, ) -> Result<(), CphError> { - let output = self.get_output(connector)?; + let output = self.get_output_node(connector)?; let ws = match workspace { WorkspaceSource::Explicit(ws) => { let name = self.get_workspace(ws)?; @@ -797,10 +805,10 @@ impl ConfigProxyHandler { _ => return Ok(()), }, }; - if ws.is_dummy || output.node.is_dummy { + if ws.is_dummy || output.is_dummy { return Ok(()); } - if ws.output.get().id == output.node.id { + if ws.output.get().id == output.id { return Ok(()); } let link = match &*ws.output_link.borrow() { @@ -811,8 +819,8 @@ impl ConfigProxyHandler { make_visible_if_empty: true, source_is_destroyed: false, }; - move_ws_to_output(&link, &output.node, config); - ws.desired_output.set(output.node.global.output_id.clone()); + move_ws_to_output(&link, &output, config); + ws.desired_output.set(output.global.output_id.clone()); self.state.tree_changed(); self.state.damage(); Ok(()) @@ -856,8 +864,8 @@ impl ConfigProxyHandler { } fn handle_connector_mode(&self, connector: Connector) -> Result<(), CphError> { - let connector = self.get_output(connector)?; - let mode = connector.node.global.mode.get(); + let connector = self.get_output_node(connector)?; + let mode = connector.global.mode.get(); self.respond(Response::ConnectorMode { width: mode.width, height: mode.height, @@ -881,10 +889,9 @@ impl ConfigProxyHandler { } fn handle_connector_modes(&self, connector: Connector) -> Result<(), CphError> { - let connector = self.get_output(connector)?; + let connector = self.get_output_node(connector)?; self.respond(Response::ConnectorModes { modes: connector - .node .global .modes .iter() @@ -964,8 +971,8 @@ impl ConfigProxyHandler { } fn handle_connector_size(&self, connector: Connector) -> Result<(), CphError> { - let connector = self.get_output(connector)?; - let pos = connector.node.global.pos.get(); + let connector = self.get_output_node(connector)?; + let pos = connector.global.pos.get(); self.respond(Response::ConnectorSize { width: pos.width(), height: pos.height(), @@ -974,9 +981,9 @@ impl ConfigProxyHandler { } fn handle_connector_get_scale(&self, connector: Connector) -> Result<(), CphError> { - let connector = self.get_output(connector)?; + let connector = self.get_output_node(connector)?; self.respond(Response::ConnectorGetScale { - scale: connector.node.global.persistent.scale.get().to_f64(), + scale: connector.global.persistent.scale.get().to_f64(), }); Ok(()) } @@ -989,8 +996,8 @@ impl ConfigProxyHandler { return Err(CphError::ScaleTooLarge(scale)); } let scale = Scale::from_f64(scale); - let connector = self.get_output(connector)?; - connector.node.set_preferred_scale(scale); + let connector = self.get_output_node(connector)?; + connector.set_preferred_scale(scale); self.state.damage(); Ok(()) } @@ -1000,8 +1007,8 @@ impl ConfigProxyHandler { connector: Connector, transform: Transform, ) -> Result<(), CphError> { - let connector = self.get_output(connector)?; - connector.node.update_transform(transform); + let connector = self.get_output_node(connector)?; + connector.update_transform(transform); self.state.damage(); Ok(()) } @@ -1012,15 +1019,15 @@ impl ConfigProxyHandler { x: i32, y: i32, ) -> Result<(), CphError> { - let connector = self.get_output(connector)?; + let connector = self.get_output_node(connector)?; if x < 0 || y < 0 || x > MAX_EXTENTS || y > MAX_EXTENTS { return Err(CphError::InvalidConnectorPosition(x, y)); } - let old_pos = connector.node.global.pos.get(); - connector.node.set_position(x, y); + let old_pos = connector.global.pos.get(); + connector.set_position(x, y); let seats = self.state.globals.seats.lock(); for seat in seats.values() { - if seat.get_output().id == connector.node.id { + if seat.get_output().id == connector.id { let seat_pos = seat.position(); seat.set_position( seat_pos.0.round_down() + x - old_pos.x1(), @@ -1032,8 +1039,8 @@ impl ConfigProxyHandler { } fn handle_connector_get_position(&self, connector: Connector) -> Result<(), CphError> { - let connector = self.get_output(connector)?; - let (x, y) = connector.node.global.pos.get().position(); + let connector = self.get_output_node(connector)?; + let (x, y) = connector.global.pos.get().position(); self.respond(Response::ConnectorGetPosition { x, y }); Ok(()) } @@ -1813,6 +1820,8 @@ enum CphError { TimerDoesNotExist(JayTimer), #[error("Connector {0:?} does not exist or is not connected")] OutputDoesNotExist(Connector), + #[error("Output {0:?} is not a desktop output")] + OutputIsNotDesktop(Connector), #[error("{0}x{1} is not a valid connector position")] InvalidConnectorPosition(i32, i32), #[error("Keymap {0:?} does not exist")] diff --git a/src/ifs/jay_randr.rs b/src/ifs/jay_randr.rs index 923e70cc..5c70fd88 100644 --- a/src/ifs/jay_randr.rs +++ b/src/ifs/jay_randr.rs @@ -7,6 +7,7 @@ use { object::{Object, Version}, scale::Scale, state::{ConnectorData, DrmDevData, OutputData}, + tree::OutputNode, utils::{gfx_api_ext::GfxApiExt, transform_ext::TransformExt}, wire::{jay_randr::*, JayRandrId}, }, @@ -64,33 +65,47 @@ impl JayRandr { enabled: data.connector.enabled() as _, name: &data.name, }); - if let Some(output) = self.client.state.outputs.get(&data.connector.id()) { - let global = &output.node.global; - let pos = global.pos.get(); - self.client.event(Output { - self_id: self.id, - scale: global.persistent.scale.get().to_wl(), - width: pos.width(), - height: pos.height(), - x: pos.x1(), - y: pos.y1(), - transform: global.persistent.transform.get().to_wl(), - manufacturer: &output.monitor_info.manufacturer, - product: &output.monitor_info.product, - serial_number: &output.monitor_info.serial_number, - width_mm: global.width_mm, - height_mm: global.height_mm, - }); - let current_mode = global.mode.get(); - for mode in &global.modes { - self.client.event(Mode { + let Some(output) = self.client.state.outputs.get(&data.connector.id()) else { + return; + }; + let global = match output.node.as_ref().map(|n| &n.global) { + Some(g) => g, + _ => { + self.client.event(NonDesktopOutput { self_id: self.id, - width: mode.width, - height: mode.height, - refresh_rate_millihz: mode.refresh_rate_millihz, - current: (mode == ¤t_mode) as _, + manufacturer: &output.monitor_info.manufacturer, + product: &output.monitor_info.product, + serial_number: &output.monitor_info.serial_number, + width_mm: output.monitor_info.width_mm, + height_mm: output.monitor_info.height_mm, }); + return; } + }; + let pos = global.pos.get(); + self.client.event(Output { + self_id: self.id, + scale: global.persistent.scale.get().to_wl(), + width: pos.width(), + height: pos.height(), + x: pos.x1(), + y: pos.y1(), + transform: global.persistent.transform.get().to_wl(), + manufacturer: &output.monitor_info.manufacturer, + product: &output.monitor_info.product, + serial_number: &output.monitor_info.serial_number, + width_mm: global.width_mm, + height_mm: global.height_mm, + }); + let current_mode = global.mode.get(); + for mode in &global.modes { + self.client.event(Mode { + self_id: self.id, + width: mode.width, + height: mode.height, + refresh_rate_millihz: mode.refresh_rate_millihz, + current: (mode == ¤t_mode) as _, + }); } } @@ -144,6 +159,18 @@ impl JayRandr { } None } + + fn get_output_node(&self, name: &str) -> Option> { + let output = self.get_output(name)?; + match output.node.clone() { + Some(n) => return Some(n), + _ => self.send_error(&format!( + "Display connected to {} is not a desktop display", + output.connector.name + )), + } + None + } } impl JayRandrRequestHandler for JayRandr { @@ -203,22 +230,22 @@ impl JayRandrRequestHandler for JayRandr { } fn set_transform(&self, req: SetTransform, _slf: &Rc) -> Result<(), Self::Error> { - let Some(c) = self.get_output(req.output) else { + let Some(c) = self.get_output_node(req.output) else { return Ok(()); }; let Some(transform) = Transform::from_wl(req.transform) else { self.send_error(&format!("Unknown transform {}", req.transform)); return Ok(()); }; - c.node.update_transform(transform); + c.update_transform(transform); Ok(()) } fn set_scale(&self, req: SetScale, _slf: &Rc) -> Result<(), Self::Error> { - let Some(c) = self.get_output(req.output) else { + let Some(c) = self.get_output_node(req.output) else { return Ok(()); }; - c.node.set_preferred_scale(Scale::from_wl(req.scale)); + c.set_preferred_scale(Scale::from_wl(req.scale)); Ok(()) } @@ -235,7 +262,7 @@ impl JayRandrRequestHandler for JayRandr { } fn set_position(&self, req: SetPosition, _slf: &Rc) -> Result<(), Self::Error> { - let Some(c) = self.get_output(req.output) else { + let Some(c) = self.get_output_node(req.output) else { return Ok(()); }; if req.x < 0 || req.y < 0 { @@ -246,7 +273,7 @@ impl JayRandrRequestHandler for JayRandr { self.send_error(&format!("x and y cannot be greater than {MAX_EXTENTS}")); return Ok(()); } - c.node.set_position(req.x, req.y); + c.set_position(req.x, req.y); Ok(()) } diff --git a/src/it/test_backend.rs b/src/it/test_backend.rs index b1be7de0..9691ce03 100644 --- a/src/it/test_backend.rs +++ b/src/it/test_backend.rs @@ -106,6 +106,7 @@ impl TestBackend { initial_mode: mode, width_mm: 80, height_mm: 60, + non_desktop: false, }; Self { state: state.clone(), diff --git a/src/it/tests/t0034_workspace_restoration.rs b/src/it/tests/t0034_workspace_restoration.rs index 7699fc55..b400d747 100644 --- a/src/it/tests/t0034_workspace_restoration.rs +++ b/src/it/tests/t0034_workspace_restoration.rs @@ -42,6 +42,7 @@ async fn test(run: Rc) -> TestResult { }, width_mm: 0, height_mm: 0, + non_desktop: false, }; run.backend .state diff --git a/src/state.rs b/src/state.rs index 7e75a0c5..a051a165 100644 --- a/src/state.rs +++ b/src/state.rs @@ -268,7 +268,7 @@ pub struct ConnectorData { pub struct OutputData { pub connector: Rc, pub monitor_info: MonitorInfo, - pub node: Rc, + pub node: Option>, } pub struct DrmDevData { diff --git a/src/tasks/connector.rs b/src/tasks/connector.rs index a100b36f..67f87ade 100644 --- a/src/tasks/connector.rs +++ b/src/tasks/connector.rs @@ -1,6 +1,7 @@ use { crate::{ backend::{Connector, ConnectorEvent, ConnectorId, MonitorInfo}, + globals::GlobalName, ifs::wl_output::{OutputId, PersistentOutputState, WlOutputGlobal}, state::{ConnectorData, OutputData, State}, tree::{move_ws_to_output, OutputNode, OutputRenderData, WsMoveConfig}, @@ -70,6 +71,9 @@ impl ConnectorHandler { } self.data.async_event.triggered().await; } + if let Some(dev) = &self.data.drm_dev { + dev.connectors.remove(&self.id); + } if let Some(config) = self.state.config.get() { config.del_connector(self.id); } @@ -87,6 +91,21 @@ impl ConnectorHandler { model: info.product.clone(), serial_number: info.serial_number.clone(), }); + if info.non_desktop { + self.handle_non_desktop_connected(info).await; + } else { + self.handle_desktop_connected(info, name, output_id).await; + } + self.data.connected.set(false); + log::info!("Connector {} disconnected", self.data.connector.kernel_id()); + } + + async fn handle_desktop_connected( + &self, + info: MonitorInfo, + name: GlobalName, + output_id: Rc, + ) { let desired_state = match self.state.persistent_output_states.get(&output_id) { Some(ds) => ds, _ => { @@ -155,7 +174,7 @@ impl ConnectorHandler { let output_data = Rc::new(OutputData { connector: self.data.clone(), monitor_info: info, - node: on.clone(), + node: Some(on.clone()), }); self.state.outputs.set(self.id, output_data); on.schedule_update_render_data(); @@ -224,7 +243,6 @@ impl ConnectorHandler { } self.data.async_event.triggered().await; } - log::info!("Connector {} disconnected", self.data.connector.kernel_id()); if let Some(config) = self.state.config.get() { config.connector_disconnected(self.id); } @@ -242,7 +260,6 @@ impl ConnectorHandler { global.destroyed.set(true); self.state.root.outputs.remove(&self.id); self.state.root.update_extents(); - self.data.connected.set(false); self.state.outputs.remove(&self.id); on.lock_surface.take(); { @@ -285,4 +302,30 @@ impl ConnectorHandler { self.state.tree_changed(); self.state.damage(); } + + async fn handle_non_desktop_connected(&self, monitor_info: MonitorInfo) { + let output_data = Rc::new(OutputData { + connector: self.data.clone(), + monitor_info, + node: None, + }); + self.state.outputs.set(self.id, output_data); + if let Some(config) = self.state.config.get() { + config.connector_connected(self.id); + } + 'outer: loop { + while let Some(event) = self.data.connector.event() { + match event { + ConnectorEvent::Disconnected => break 'outer, + ConnectorEvent::HardwareCursor(None) => {} + ev => unreachable!("received unexpected event {:?}", ev), + } + } + self.data.async_event.triggered().await; + } + self.state.outputs.remove(&self.id); + if let Some(config) = self.state.config.get() { + config.connector_disconnected(self.id); + } + } } diff --git a/wire/jay_randr.txt b/wire/jay_randr.txt index ba3cef0d..0792f380 100644 --- a/wire/jay_randr.txt +++ b/wire/jay_randr.txt @@ -99,3 +99,11 @@ event mode { event error { msg: str, } + +event non_desktop_output { + manufacturer: str, + product: str, + serial_number: str, + width_mm: i32, + height_mm: i32, +} From 1f18b461359edb5f36b3378d1b88c4c1cab6a06b Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Thu, 25 Apr 2024 18:39:29 +0200 Subject: [PATCH 5/7] cli: allow overriding non-desktop setting --- src/backend.rs | 3 +++ src/backends/metal/video.rs | 43 +++++++++++++++++++++++++++++++------ src/cli/randr.rs | 27 ++++++++++++++++++++++- src/ifs/jay_randr.rs | 13 +++++++++++ wire/jay_randr.txt | 5 +++++ 5 files changed, 84 insertions(+), 7 deletions(-) diff --git a/src/backend.rs b/src/backend.rs index a6a122e9..406d3ab6 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -91,6 +91,9 @@ pub trait Connector { None } fn set_mode(&self, mode: Mode); + fn set_non_desktop_override(&self, non_desktop: Option) { + let _ = non_desktop; + } } #[derive(Debug)] diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 6f9991ad..6b7637c7 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -173,6 +173,7 @@ pub struct ConnectorDisplayData { pub mode: Option, pub refresh: u32, pub non_desktop: bool, + pub non_desktop_effective: bool, pub monitor_manufacturer: String, pub monitor_name: String, @@ -209,6 +210,7 @@ pub struct MetalConnector { pub next_buffer: NumCell, pub enabled: Cell, + pub non_desktop_override: Cell>, pub can_present: Cell, pub has_damage: Cell, @@ -907,7 +909,7 @@ impl Connector for MetalConnector { fn set_mode(&self, be_mode: Mode) { let mut dd = self.display.borrow_mut(); - if dd.non_desktop { + if dd.non_desktop_effective { return; } let Some(mode) = dd.modes.iter().find(|m| m.to_backend() == be_mode) else { @@ -945,6 +947,25 @@ impl Connector for MetalConnector { log::warn!("Could not restore the previous mode: {}", ErrorFmt(e)); }; } + + fn set_non_desktop_override(&self, non_desktop: Option) { + if self.non_desktop_override.replace(non_desktop) == non_desktop { + return; + } + let mut dd = self.display.borrow_mut(); + let non_desktop_effective = non_desktop.unwrap_or(dd.non_desktop); + if dd.non_desktop_effective == non_desktop_effective { + return; + } + dd.non_desktop_effective = non_desktop_effective; + drop(dd); + if let Some(dev) = self.backend.device_holder.drm_devices.get(&self.dev.devnum) { + if let Err(e) = self.backend.handle_drm_change_(&dev, true) { + dev.unprocessed_change.set(true); + log::error!("Could not override non-desktop setting: {}", ErrorFmt(e)); + } + } + } } pub struct MetalCrtc { @@ -1052,7 +1073,7 @@ fn create_connector( connector: DrmConnector, dev: &Rc, ) -> Result<(Rc, ConnectorFutures), DrmError> { - let display = create_connector_display_data(connector, dev)?; + let display = create_connector_display_data(connector, dev, None)?; let slf = Rc::new(MetalConnector { id: connector, master: dev.master.clone(), @@ -1063,6 +1084,7 @@ fn create_connector( buffers: Default::default(), next_buffer: Default::default(), enabled: Cell::new(true), + non_desktop_override: Default::default(), can_present: Cell::new(true), has_damage: Cell::new(true), primary_plane: Default::default(), @@ -1100,6 +1122,7 @@ fn create_connector( fn create_connector_display_data( connector: DrmConnector, dev: &Rc, + non_desktop_override: Option, ) -> Result { let info = dev.master.get_connector_info(connector, true)?; let mut crtcs = AHashMap::new(); @@ -1186,13 +1209,15 @@ fn create_connector_display_data( } let props = collect_properties(&dev.master, connector)?; let connector_type = ConnectorType::from_drm(info.connector_type); + let non_desktop = props.get("non-desktop")?.value.get() != 0; Ok(ConnectorDisplayData { crtc_id: props.get("CRTC_ID")?.map(|v| DrmCrtc(v as _)), crtcs, modes: info.modes, mode, refresh, - non_desktop: props.get("non-desktop")?.value.get() != 0, + non_desktop, + non_desktop_effective: non_desktop_override.unwrap_or(non_desktop), monitor_manufacturer: manufacturer, monitor_name: name, monitor_serial_number: serial_number, @@ -1486,7 +1511,8 @@ impl MetalBackend { } let mut preserve = Preserve::default(); for c in dev.connectors.lock().values() { - let mut dd = match create_connector_display_data(c.id, &dev.dev) { + let dd = create_connector_display_data(c.id, &dev.dev, c.non_desktop_override.get()); + let mut dd = match dd { Ok(d) => d, Err(e) => { log::error!( @@ -1509,6 +1535,7 @@ impl MetalBackend { if !c.enabled.get() || old.connection != ConnectorStatus::Connected || !old.is_same_monitor(&dd) + || c.primary_plane.is_none() != old.non_desktop_effective { c.on_change.send_event(ConnectorEvent::Disconnected); c.connect_sent.set(false); @@ -1562,7 +1589,7 @@ impl MetalBackend { initial_mode: dd.mode.clone().unwrap().to_backend(), width_mm: dd.mm_width as _, height_mm: dd.mm_height as _, - non_desktop: dd.non_desktop, + non_desktop: dd.non_desktop_effective, })); connector.connect_sent.set(true); connector.send_hardware_cursor(); @@ -1857,6 +1884,8 @@ impl MetalBackend { if preserve.connectors.contains(&connector.id) { continue; } + connector.buffers.set(None); + connector.cursor_buffers.set(None); connector.primary_plane.set(None); connector.cursor_plane.set(None); connector.cursor_enabled.set(false); @@ -2528,5 +2557,7 @@ fn modes_equal(a: &DrmModeInfo, b: &DrmModeInfo) -> bool { } fn should_ignore(connector: &MetalConnector, dd: &ConnectorDisplayData) -> bool { - !connector.enabled.get() || dd.connection != ConnectorStatus::Connected || dd.non_desktop + !connector.enabled.get() + || dd.connection != ConnectorStatus::Connected + || dd.non_desktop_effective } diff --git a/src/cli/randr.rs b/src/cli/randr.rs index 63db0fba..f627bd83 100644 --- a/src/cli/randr.rs +++ b/src/cli/randr.rs @@ -6,7 +6,7 @@ use { utils::transform_ext::TransformExt, wire::{jay_compositor, jay_randr, JayRandrId}, }, - clap::{Args, Subcommand}, + clap::{Args, Subcommand, ValueEnum}, isnt::std_1::vec::IsntVecExt, jay_config::video::Transform, std::{ @@ -115,6 +115,21 @@ pub enum OutputCommand { Enable, /// Disable the output. Disable, + /// Override the display's non-desktop setting. + NonDesktop(NonDesktopArgs), +} + +#[derive(ValueEnum, Debug, Clone)] +pub enum NonDesktopType { + Default, + False, + True, +} + +#[derive(Args, Debug, Clone)] +pub struct NonDesktopArgs { + /// Whether this output is a non-desktop output. + pub setting: NonDesktopType, } #[derive(Args, Debug, Clone)] @@ -374,6 +389,16 @@ impl Randr { enabled: enable as _, }); } + OutputCommand::NonDesktop(a) => { + self.handle_error(randr, move |msg| { + eprintln!("Could not change the non-desktop setting: {}", msg); + }); + tc.send(jay_randr::SetNonDesktop { + self_id: randr, + output: &args.output, + non_desktop: a.setting as _, + }); + } } tc.round_trip().await; } diff --git a/src/ifs/jay_randr.rs b/src/ifs/jay_randr.rs index 5c70fd88..e6c975fa 100644 --- a/src/ifs/jay_randr.rs +++ b/src/ifs/jay_randr.rs @@ -284,6 +284,19 @@ impl JayRandrRequestHandler for JayRandr { c.connector.set_enabled(req.enabled != 0); Ok(()) } + + fn set_non_desktop(&self, req: SetNonDesktop<'_>, _slf: &Rc) -> Result<(), Self::Error> { + let Some(c) = self.get_connector(req.output) else { + return Ok(()); + }; + let non_desktop = match req.non_desktop { + 0 => None, + 1 => Some(false), + _ => Some(true), + }; + c.connector.set_non_desktop_override(non_desktop); + Ok(()) + } } object_base! { diff --git a/wire/jay_randr.txt b/wire/jay_randr.txt index 0792f380..96ab32ed 100644 --- a/wire/jay_randr.txt +++ b/wire/jay_randr.txt @@ -50,6 +50,11 @@ request set_enabled { enabled: u32, } +request set_non_desktop { + output: str, + non_desktop: u32, +} + # events event global { From e92c92bf495fe0f6d7a00f9aabc242f8faa65226 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Thu, 25 Apr 2024 22:20:29 +0200 Subject: [PATCH 6/7] metal: add lease infrastructure --- src/backend.rs | 20 +- src/backends/metal/monitor.rs | 35 ++- src/backends/metal/video.rs | 437 ++++++++++++++++++++++++++++++---- src/tasks/connector.rs | 3 +- src/video/drm.rs | 50 +++- src/video/drm/sys.rs | 15 ++ 6 files changed, 502 insertions(+), 58 deletions(-) diff --git a/src/backend.rs b/src/backend.rs index 406d3ab6..fbe976d6 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -15,7 +15,7 @@ use { fmt::{Debug, Display, Formatter}, rc::Rc, }, - uapi::c, + uapi::{c, OwnedFd}, }; linear_ids!(ConnectorIds, ConnectorId); @@ -103,6 +103,8 @@ pub enum ConnectorEvent { Disconnected, Removed, ModeChanged(Mode), + Unavailable, + Available, } pub trait HardwareCursor: Debug { @@ -286,4 +288,20 @@ pub trait BackendDrmDevice { fn version(&self) -> Result; fn set_direct_scanout_enabled(&self, enabled: bool); fn is_render_device(&self) -> bool; + fn create_lease( + self: Rc, + lessee: Rc, + connector_ids: &[ConnectorId], + ) { + let _ = lessee; + let _ = connector_ids; + } +} + +pub trait BackendDrmLease { + fn fd(&self) -> &Rc; +} + +pub trait BackendDrmLessee { + fn created(&self, lease: Rc); } diff --git a/src/backends/metal/monitor.rs b/src/backends/metal/monitor.rs index 425c5a11..acfa51e8 100644 --- a/src/backends/metal/monitor.rs +++ b/src/backends/metal/monitor.rs @@ -1,13 +1,15 @@ use { crate::{ - backend::BackendEvent, + backend::{BackendEvent, ConnectorEvent}, backends::metal::{ - video::{MetalDrmDeviceData, PendingDrmDevice}, + video::{FrontState, MetalDrmDeviceData, PendingDrmDevice}, MetalBackend, MetalDevice, MetalError, MetalInputDevice, }, dbus::{DbusError, TRUE}, udev::UdevDevice, - utils::{bitflags::BitflagsExt, errorfmt::ErrorFmt, nonblock::set_nonblock}, + utils::{ + bitflags::BitflagsExt, cell_ext::CellExt, errorfmt::ErrorFmt, nonblock::set_nonblock, + }, video::drm::DrmMaster, wire_dbus::org::freedesktop::login1::session::{ PauseDevice, ResumeDevice, TakeDeviceReply, @@ -89,6 +91,18 @@ impl MetalBackend { fn handle_drm_device_resume(self: &Rc, dev: &Rc, _fd: Rc) { log::info!("Device resumed: {}", dev.dev.devnode.to_bytes().as_bstr()); + dev.dev.paused.set(false); + self.break_leases(dev); + for c in dev.connectors.lock().values() { + match c.frontend_state.get() { + FrontState::Removed | FrontState::Disconnected | FrontState::Connected { .. } => {} + FrontState::Unavailable => { + if c.lease.is_none() { + c.send_event(ConnectorEvent::Available); + } + } + } + } if let Err(e) = self.resume_drm_device(dev) { log::error!("Could not resume drm device: {}", ErrorFmt(e)); } @@ -149,6 +163,21 @@ impl MetalBackend { } fn handle_drm_device_paused(self: &Rc, dev: &Rc) { + dev.dev.paused.set(true); + for c in dev.connectors.lock().values() { + match c.frontend_state.get() { + FrontState::Removed + | FrontState::Disconnected + | FrontState::Unavailable + | FrontState::Connected { non_desktop: false } => {} + FrontState::Connected { non_desktop: true } => { + c.send_event(ConnectorEvent::Unavailable); + } + } + } + for (lease_id, lease) in dev.dev.leases.lock().drain() { + dev.dev.leases_to_break.set(lease_id, lease); + } log::info!("Device paused: {}", dev.dev.devnode.to_bytes().as_bstr()); } diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 6b7637c7..1f156dbd 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -2,8 +2,9 @@ use { crate::{ async_engine::{Phase, SpawnedFuture}, backend::{ - BackendDrmDevice, BackendEvent, Connector, ConnectorEvent, ConnectorId, - ConnectorKernelId, DrmDeviceId, HardwareCursor, Mode, MonitorInfo, + BackendDrmDevice, BackendDrmLease, BackendDrmLessee, BackendEvent, Connector, + ConnectorEvent, ConnectorId, ConnectorKernelId, DrmDeviceId, HardwareCursor, Mode, + MonitorInfo, }, backends::metal::{MetalBackend, MetalError}, drm_feedback::DrmFeedback, @@ -20,7 +21,7 @@ use { tree::OutputNode, udev::UdevDevice, utils::{ - asyncevent::AsyncEvent, bitflags::BitflagsExt, clonecell::CloneCell, + asyncevent::AsyncEvent, bitflags::BitflagsExt, cell_ext::CellExt, clonecell::CloneCell, copyhashmap::CopyHashMap, debug_fn::debug_fn, errorfmt::ErrorFmt, numcell::NumCell, on_change::OnChange, opaque_cell::OpaqueCell, oserror::OsError, transform_ext::TransformExt, @@ -29,10 +30,10 @@ use { dmabuf::DmaBufId, drm::{ drm_mode_modeinfo, Change, ConnectorStatus, ConnectorType, DrmBlob, DrmConnector, - DrmCrtc, DrmEncoder, DrmError, DrmEvent, DrmFramebuffer, DrmMaster, DrmModeInfo, - DrmObject, DrmPlane, DrmProperty, DrmPropertyDefinition, DrmPropertyType, - DrmVersion, PropBlob, DRM_CLIENT_CAP_ATOMIC, DRM_MODE_ATOMIC_ALLOW_MODESET, - DRM_MODE_ATOMIC_NONBLOCK, DRM_MODE_PAGE_FLIP_EVENT, + DrmCrtc, DrmEncoder, DrmError, DrmEvent, DrmFramebuffer, DrmLease, DrmMaster, + DrmModeInfo, DrmObject, DrmPlane, DrmProperty, DrmPropertyDefinition, + DrmPropertyType, DrmVersion, PropBlob, DRM_CLIENT_CAP_ATOMIC, + DRM_MODE_ATOMIC_ALLOW_MODESET, DRM_MODE_ATOMIC_NONBLOCK, DRM_MODE_PAGE_FLIP_EVENT, }, gbm::{GbmDevice, GBM_BO_USE_LINEAR, GBM_BO_USE_RENDERING, GBM_BO_USE_SCANOUT}, Modifier, INVALID_MODIFIER, @@ -42,17 +43,22 @@ use { arrayvec::ArrayVec, bstr::{BString, ByteSlice}, indexmap::{indexset, IndexSet}, + isnt::std_1::collections::IsntHashMap2Ext, jay_config::video::GfxApi, std::{ any::Any, cell::{Cell, RefCell}, + collections::hash_map::Entry, ffi::CString, fmt::{Debug, Formatter}, mem, ops::DerefMut, rc::{Rc, Weak}, }, - uapi::c::{self, dev_t}, + uapi::{ + c::{self, dev_t}, + OwnedFd, + }, }; pub struct PendingDrmDevice { @@ -88,6 +94,10 @@ pub struct MetalDrmDevice { pub on_change: OnChange, pub direct_scanout_enabled: Cell>, pub is_nvidia: bool, + pub lease_ids: MetalLeaseIds, + pub leases: CopyHashMap, + pub leases_to_break: CopyHashMap, + pub paused: Cell, } impl Debug for MetalDrmDevice { @@ -145,6 +155,120 @@ impl BackendDrmDevice for MetalDrmDevice { fn is_render_device(&self) -> bool { Some(self.id) == self.backend.ctx.get().map(|c| c.dev_id) } + + fn create_lease( + self: Rc, + lessee: Rc, + connector_ids: &[ConnectorId], + ) { + let Some(data) = self.backend.device_holder.drm_devices.get(&self.devnum) else { + log::error!("Tried to create a lease for a DRM device that no longer exists"); + return; + }; + let mut connectors = vec![]; + let mut crtcs = AHashMap::new(); + let mut planes = AHashMap::new(); + let mut ids = vec![]; + for id in connector_ids { + let Some(connector) = data + .connectors + .lock() + .values() + .find(|c| c.connector_id == *id) + .cloned() + else { + log::error!("Tried to lease connector {id} but no such connector exists"); + return; + }; + let fe_state = connector.frontend_state.get(); + match fe_state { + FrontState::Connected { non_desktop: true } => {} + FrontState::Connected { non_desktop: false } + | FrontState::Removed + | FrontState::Disconnected + | FrontState::Unavailable => { + log::error!( + "Tried to lease connector {id} but it is in an invalid state: {fe_state:?}" + ); + return; + } + } + if let Some(lease_id) = connector.lease.get() { + match data.dev.leases_to_break.lock().entry(lease_id) { + Entry::Occupied(oe) => { + if oe.get().try_revoke() { + oe.remove(); + } + } + _ => { + log::error!("Connector is logically available for leasing, has a lease ID, and has no entry in leases_to_break"); + } + } + } + if connector.lease.is_some() { + log::error!("Tried to lease connector {id} but it is already leased"); + return; + } + let dd = &*connector.display.borrow(); + let crtc = dd.crtcs.values().find(|c| { + c.connector.is_none() && c.lease.is_none() && crtcs.not_contains_key(&c.id) + }); + let Some(crtc) = crtc else { + log::error!("Tried to lease connector {id} but it has no matching unused CRTC"); + return; + }; + let plane = crtc.possible_planes.values().find(|p| { + !p.assigned.get() + && p.lease.is_none() + && planes.not_contains_key(&p.id) + && p.ty == PlaneType::Primary + }); + let Some(plane) = plane else { + log::error!("Tried to lease connector {id} but it has no matching unused plane"); + return; + }; + connectors.push(connector.clone()); + crtcs.insert(crtc.id, crtc.clone()); + planes.insert(plane.id, plane.clone()); + ids.push(connector.id.0); + ids.push(crtc.id.0); + ids.push(plane.id.0); + } + let drm_lease = match self.master.lease(&ids) { + Ok(l) => l, + Err(e) => { + log::error!("Could not create lease: {}", ErrorFmt(e)); + return; + } + }; + let lease_id = self.lease_ids.next(); + for c in &connectors { + c.lease.set(Some(lease_id)); + c.send_event(ConnectorEvent::Unavailable); + } + for c in crtcs.values() { + c.lease.set(Some(lease_id)); + } + for p in planes.values() { + p.lease.set(Some(lease_id)); + } + let fd = drm_lease.lessee_fd().clone(); + let lease_data = MetalLeaseData { + lease: drm_lease, + _lessee: lessee.clone(), + connectors, + crtcs: crtcs.values().cloned().collect(), + planes: planes.values().cloned().collect(), + revoked: Cell::new(false), + }; + self.leases.set(lease_id, lease_data); + let lease = Rc::new(MetalLease { + dev: self.clone(), + id: lease_id, + fd, + }); + lessee.created(lease); + } } pub struct HandleEvents { @@ -196,6 +320,81 @@ impl ConnectorDisplayData { } } +linear_ids!(MetalLeaseIds, MetalLeaseId, u64); + +pub struct MetalLeaseData { + lease: DrmLease, + _lessee: Rc, + connectors: Vec>, + crtcs: Vec>, + planes: Vec>, + revoked: Cell, +} + +impl MetalLeaseData { + fn try_revoke(&self) -> bool { + if self.revoked.get() { + return true; + } + let res = self.lease.try_revoke(); + if res { + self.revoked.set(res); + for c in &self.connectors { + c.lease.take(); + } + for c in &self.crtcs { + c.lease.take(); + } + for p in &self.planes { + p.lease.take(); + } + } + res + } +} + +pub struct MetalLease { + dev: Rc, + id: MetalLeaseId, + fd: Rc, +} + +impl Drop for MetalLease { + fn drop(&mut self) { + if let Some(lease) = self.dev.leases.remove(&self.id) { + if !self.dev.paused.get() { + for c in &lease.connectors { + match c.frontend_state.get() { + FrontState::Removed + | FrontState::Disconnected + | FrontState::Connected { .. } => {} + FrontState::Unavailable => { + c.send_event(ConnectorEvent::Available); + } + } + } + } + if !lease.try_revoke() { + self.dev.leases_to_break.set(self.id, lease); + } + } + } +} + +impl BackendDrmLease for MetalLease { + fn fd(&self) -> &Rc { + &self.fd + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum FrontState { + Removed, + Disconnected, + Connected { non_desktop: bool }, + Unavailable, +} + pub struct MetalConnector { pub id: DrmConnector, pub master: Rc, @@ -212,13 +411,15 @@ pub struct MetalConnector { pub enabled: Cell, pub non_desktop_override: Cell>, + pub lease: Cell>, + pub can_present: Cell, pub has_damage: Cell, pub cursor_changed: Cell, pub display: RefCell, - pub connect_sent: Cell, + pub frontend_state: Cell, pub primary_plane: CloneCell>>, pub cursor_plane: CloneCell>>, @@ -387,8 +588,12 @@ impl MetalConnector { } fn send_hardware_cursor(self: &Rc) { - if !self.connect_sent.get() { - return; + match self.frontend_state.get() { + FrontState::Removed + | FrontState::Disconnected + | FrontState::Unavailable + | FrontState::Connected { non_desktop: true } => return, + FrontState::Connected { non_desktop: false } => {} } let generation = self.cursor_generation.fetch_add(1) + 1; let hc = match self.cursor_buffers.get() { @@ -852,6 +1057,78 @@ impl MetalConnector { } } } + + pub fn send_event(&self, event: ConnectorEvent) { + let state = self.frontend_state.get(); + match &event { + ConnectorEvent::Connected(ty) => match state { + FrontState::Disconnected => { + let non_desktop = ty.non_desktop; + self.on_change.send_event(event); + self.frontend_state + .set(FrontState::Connected { non_desktop }); + } + FrontState::Removed | FrontState::Connected { .. } | FrontState::Unavailable => { + log::error!("Tried to send connected event in invalid state: {state:?}"); + } + }, + ConnectorEvent::HardwareCursor(_) | ConnectorEvent::ModeChanged(_) => match state { + FrontState::Connected { non_desktop: false } => { + self.on_change.send_event(event); + } + FrontState::Connected { non_desktop: true } + | FrontState::Removed + | FrontState::Disconnected + | FrontState::Unavailable => { + let name = match &event { + ConnectorEvent::HardwareCursor(_) => "hardware cursor", + _ => "mode change", + }; + log::error!("Tried to send {name} event in invalid state: {state:?}"); + } + }, + ConnectorEvent::Disconnected => match state { + FrontState::Connected { .. } | FrontState::Unavailable => { + self.on_change.send_event(event); + self.frontend_state.set(FrontState::Disconnected); + } + FrontState::Removed | FrontState::Disconnected => { + log::error!("Tried to send disconnected event in invalid state: {state:?}"); + } + }, + ConnectorEvent::Removed => match state { + FrontState::Disconnected => { + self.on_change.send_event(event); + self.frontend_state.set(FrontState::Removed); + } + FrontState::Removed | FrontState::Connected { .. } | FrontState::Unavailable => { + log::error!("Tried to send removed event in invalid state: {state:?}"); + } + }, + ConnectorEvent::Unavailable => match state { + FrontState::Connected { non_desktop: true } => { + self.on_change.send_event(event); + self.frontend_state.set(FrontState::Unavailable); + } + FrontState::Connected { non_desktop: false } + | FrontState::Removed + | FrontState::Disconnected + | FrontState::Unavailable => { + log::error!("Tried to send unavailable event in invalid state: {state:?}"); + } + }, + ConnectorEvent::Available => match state { + FrontState::Unavailable => { + self.on_change.send_event(event); + self.frontend_state + .set(FrontState::Connected { non_desktop: true }); + } + FrontState::Connected { .. } | FrontState::Removed | FrontState::Disconnected => { + log::error!("Tried to send available event in invalid state: {state:?}"); + } + }, + } + } } impl Connector for MetalConnector { @@ -908,10 +1185,14 @@ impl Connector for MetalConnector { } fn set_mode(&self, be_mode: Mode) { - let mut dd = self.display.borrow_mut(); - if dd.non_desktop_effective { - return; + match self.frontend_state.get() { + FrontState::Connected { non_desktop: false } => {} + FrontState::Connected { non_desktop: true } + | FrontState::Removed + | FrontState::Disconnected + | FrontState::Unavailable => return, } + let mut dd = self.display.borrow_mut(); let Some(mode) = dd.modes.iter().find(|m| m.to_backend() == be_mode) else { log::warn!("Connector does not support mode {:?}", be_mode); return; @@ -932,8 +1213,7 @@ impl Connector for MetalConnector { dd.mode = Some(mode.clone()); drop(dd); let Err(e) = self.backend.handle_drm_change_(&dev, true) else { - self.on_change - .send_event(ConnectorEvent::ModeChanged(be_mode)); + self.send_event(ConnectorEvent::ModeChanged(be_mode)); return; }; log::warn!("Could not change mode: {}", ErrorFmt(&e)); @@ -973,6 +1253,8 @@ pub struct MetalCrtc { pub idx: usize, pub master: Rc, + pub lease: Cell>, + pub possible_planes: AHashMap>, pub connector: CloneCell>>, @@ -1018,6 +1300,7 @@ pub struct MetalPlane { pub possible_crtcs: u32, pub formats: AHashMap, + pub lease: Cell>, pub assigned: Cell, pub mode_w: Cell, @@ -1085,6 +1368,7 @@ fn create_connector( next_buffer: Default::default(), enabled: Cell::new(true), non_desktop_override: Default::default(), + lease: Cell::new(None), can_present: Cell::new(true), has_damage: Cell::new(true), primary_plane: Default::default(), @@ -1099,7 +1383,7 @@ fn create_connector( cursor_enabled: Cell::new(false), cursor_buffers: Default::default(), display: RefCell::new(display), - connect_sent: Cell::new(false), + frontend_state: Cell::new(FrontState::Disconnected), cursor_changed: Cell::new(false), cursor_front_buffer: Default::default(), cursor_swap_buffer: Cell::new(false), @@ -1266,6 +1550,7 @@ fn create_crtc( id: crtc, idx, master: master.clone(), + lease: Cell::new(None), possible_planes, connector: Default::default(), active: props.get("ACTIVE")?.map(|v| v == 1), @@ -1350,6 +1635,7 @@ fn create_plane(plane: DrmPlane, master: &Rc) -> Result {} + FrontState::Connected { .. } | FrontState::Unavailable => { + c.send_event(ConnectorEvent::Disconnected); + } + } + c.send_event(ConnectorEvent::Removed); } } let mut preserve = Preserve::default(); @@ -1531,16 +1827,38 @@ impl MetalBackend { } } mem::swap(old.deref_mut(), &mut dd); - if c.connect_sent.get() { - if !c.enabled.get() - || old.connection != ConnectorStatus::Connected - || !old.is_same_monitor(&dd) - || c.primary_plane.is_none() != old.non_desktop_effective - { - c.on_change.send_event(ConnectorEvent::Disconnected); - c.connect_sent.set(false); - } else if preserve_any { - preserve.connectors.insert(c.id); + match c.frontend_state.get() { + FrontState::Removed | FrontState::Disconnected => {} + FrontState::Connected { .. } | FrontState::Unavailable => { + let mut disconnect = false; + // Disconnect if the connector has been disabled. + disconnect |= !c.enabled.get(); + // If the connector is connected and switched between being a non-desktop + // and desktop device, break leases and disconnect. + disconnect |= old.connection == ConnectorStatus::Connected + && (c.primary_plane.is_none() != old.non_desktop_effective); + if c.lease.is_none() { + // If the connector is leased, we have to be careful because DRM is + // fickle with sending intermittent disconnected states while the + // client performs his setup. Otherwise apply the following rules. + + // Disconnect if the connector is no longer connected. + disconnect |= old.connection != ConnectorStatus::Connected; + // Disconnect if the connected monitor changed. + disconnect |= !old.is_same_monitor(&dd); + } + if disconnect { + if let Some(lease_id) = c.lease.get() { + if let Some(lease) = dev.dev.leases.remove(&lease_id) { + if !lease.try_revoke() { + dev.dev.leases_to_break.set(lease_id, lease); + } + } + } + c.send_event(ConnectorEvent::Disconnected); + } else if preserve_any { + preserve.connectors.insert(c.id); + } } } } @@ -1572,6 +1890,12 @@ impl MetalBackend { } fn send_connected(&self, connector: &Rc, dd: &ConnectorDisplayData) { + match connector.frontend_state.get() { + FrontState::Removed | FrontState::Connected { .. } | FrontState::Unavailable => { + return; + } + FrontState::Disconnected => {} + } let mut prev_mode = None; let mut modes = vec![]; for mode in dd.modes.iter().map(|m| m.to_backend()) { @@ -1579,19 +1903,16 @@ impl MetalBackend { modes.push(mode); } } - connector - .on_change - .send_event(ConnectorEvent::Connected(MonitorInfo { - modes, - manufacturer: dd.monitor_manufacturer.clone(), - product: dd.monitor_name.clone(), - serial_number: dd.monitor_serial_number.clone(), - initial_mode: dd.mode.clone().unwrap().to_backend(), - width_mm: dd.mm_width as _, - height_mm: dd.mm_height as _, - non_desktop: dd.non_desktop_effective, - })); - connector.connect_sent.set(true); + connector.send_event(ConnectorEvent::Connected(MonitorInfo { + modes, + manufacturer: dd.monitor_manufacturer.clone(), + product: dd.monitor_name.clone(), + serial_number: dd.monitor_serial_number.clone(), + initial_mode: dd.mode.clone().unwrap().to_backend(), + width_mm: dd.mm_width as _, + height_mm: dd.mm_height as _, + non_desktop: dd.non_desktop_effective, + })); connector.send_hardware_cursor(); } @@ -1696,6 +2017,10 @@ impl MetalBackend { on_change: Default::default(), direct_scanout_enabled: Default::default(), is_nvidia, + lease_ids: Default::default(), + leases: Default::default(), + leases_to_break: Default::default(), + paused: Cell::new(false), }); let (connectors, futures) = get_connectors(self, &dev, &resources.connectors)?; @@ -2053,11 +2378,19 @@ impl MetalBackend { } } + pub fn break_leases(&self, dev: &Rc) { + dev.dev + .leases_to_break + .lock() + .retain(|_, lease| !lease.try_revoke()); + } + fn init_drm_device( &self, dev: &Rc, preserve: &mut Preserve, ) -> Result<(), MetalError> { + self.break_leases(dev); let ctx = match self.ctx.get() { Some(ctx) => ctx, _ => return Ok(()), @@ -2342,7 +2675,7 @@ impl MetalBackend { } let crtc = 'crtc: { for crtc in dd.crtcs.values() { - if crtc.connector.is_none() { + if crtc.connector.is_none() && crtc.lease.is_none() { break 'crtc crtc.clone(); } } @@ -2390,7 +2723,8 @@ impl MetalBackend { }; let (primary_plane, primary_modifiers) = 'primary_plane: { for plane in crtc.possible_planes.values() { - if plane.ty == PlaneType::Primary && !plane.assigned.get() { + if plane.ty == PlaneType::Primary && !plane.assigned.get() && plane.lease.is_none() + { if let Some(format) = plane.formats.get(&XRGB8888.drm) { break 'primary_plane (plane.clone(), &format.modifiers); } @@ -2412,6 +2746,7 @@ impl MetalBackend { for plane in crtc.possible_planes.values() { if plane.ty == PlaneType::Cursor && !plane.assigned.get() + && plane.lease.is_none() && plane.formats.contains_key(&ARGB8888.drm) { if let Some(format) = plane.formats.get(&ARGB8888.drm) { @@ -2485,11 +2820,13 @@ impl MetalBackend { fn start_connector(&self, connector: &Rc, log_mode: bool) { let dd = connector.display.borrow_mut(); - if !connector.connect_sent.get() { - self.send_connected(connector, &dd); - } - if connector.primary_plane.is_none() { - return; + self.send_connected(connector, &dd); + match connector.frontend_state.get() { + FrontState::Connected { non_desktop: false } => {} + FrontState::Connected { non_desktop: true } + | FrontState::Removed + | FrontState::Disconnected + | FrontState::Unavailable => return, } if log_mode { log::info!( diff --git a/src/tasks/connector.rs b/src/tasks/connector.rs index 67f87ade..c5fb2382 100644 --- a/src/tasks/connector.rs +++ b/src/tasks/connector.rs @@ -317,7 +317,8 @@ impl ConnectorHandler { while let Some(event) = self.data.connector.event() { match event { ConnectorEvent::Disconnected => break 'outer, - ConnectorEvent::HardwareCursor(None) => {} + ConnectorEvent::Available => {} + ConnectorEvent::Unavailable => {} ev => unreachable!("received unexpected event {:?}", ev), } } diff --git a/src/video/drm.rs b/src/video/drm.rs index 78e57536..051181ba 100644 --- a/src/video/drm.rs +++ b/src/video/drm.rs @@ -21,7 +21,7 @@ use { bstr::{BString, ByteSlice}, indexmap::IndexSet, std::{ - cell::RefCell, + cell::{Cell, RefCell}, ffi::CString, fmt::{Debug, Display, Formatter}, mem::{self, MaybeUninit}, @@ -40,8 +40,8 @@ use crate::{ video::{ dmabuf::DmaBuf, drm::sys::{ - drm_format_modifier, drm_format_modifier_blob, get_version, DRM_CAP_CURSOR_HEIGHT, - DRM_CAP_CURSOR_WIDTH, FORMAT_BLOB_CURRENT, + drm_format_modifier, drm_format_modifier_blob, get_version, revoke_lease, + DRM_CAP_CURSOR_HEIGHT, DRM_CAP_CURSOR_WIDTH, FORMAT_BLOB_CURRENT, }, Modifier, INVALID_MODIFIER, }, @@ -137,6 +137,8 @@ pub enum DrmError { Merge(#[source] OsError), #[error("Could not import a sync file into a sync obj")] ImportSyncFile(#[source] OsError), + #[error("Could not create a lease")] + CreateLease(#[source] OsError), } fn render_node_name(fd: c::c_int) -> Result { @@ -242,6 +244,36 @@ impl Deref for DrmMaster { } } +pub struct DrmLease { + drm_fd: Rc, + lessee_id: u32, + lessee_fd: Rc, + revoked: Cell, +} + +impl DrmLease { + pub fn lessee_fd(&self) -> &Rc { + &self.lessee_fd + } + + pub fn try_revoke(&self) -> bool { + if self.revoked.get() { + return true; + } + match revoke_lease(self.drm_fd.raw(), self.lessee_id) { + Ok(_) => { + log::info!("Revoked lease {}/{}", self.drm_fd.raw(), self.lessee_id); + self.revoked.set(true); + true + } + Err(e) => { + log::error!("Could not revoke lease: {}", ErrorFmt(e)); + false + } + } + } +} + impl DrmMaster { pub fn new(ring: &Rc, fd: Rc) -> Self { Self { @@ -516,6 +548,18 @@ impl DrmMaster { } Ok(self.events.pop()) } + + pub fn lease(&self, objs: &[u32]) -> Result { + let (fd, lessee_id) = + create_lease(self.raw(), objs, c::O_CLOEXEC as _).map_err(DrmError::CreateLease)?; + log::info!("Created lease {}/{}", self.fd.raw(), lessee_id); + Ok(DrmLease { + drm_fd: self.fd.clone(), + lessee_id, + lessee_fd: Rc::new(fd), + revoked: Cell::new(false), + }) + } } pub enum DrmEvent { diff --git a/src/video/drm/sys.rs b/src/video/drm/sys.rs index 195c1029..8ad7f5e3 100644 --- a/src/video/drm/sys.rs +++ b/src/video/drm/sys.rs @@ -70,6 +70,21 @@ pub fn create_lease(fd: c::c_int, objects: &[u32], flags: u32) -> Result<(OwnedF Ok((OwnedFd::new(create.fd as _), create.lessee_id)) } +const DRM_IOCTL_MODE_REVOKE_LEASE: u64 = drm_iowr::(0xc9); + +#[repr(C)] +struct drm_mode_revoke_lease { + lessee_id: u32, +} + +pub fn revoke_lease(fd: c::c_int, lessee_id: u32) -> Result<(), OsError> { + let mut revoke = drm_mode_revoke_lease { lessee_id }; + unsafe { + ioctl(fd, DRM_IOCTL_MODE_REVOKE_LEASE, &mut revoke)?; + } + Ok(()) +} + pub fn get_node_type_from_fd(fd: c::c_int) -> Result { let (_, _, min) = drm_stat(fd)?; get_minor_type(min) From abbc8471443134b92b0d8eda8c23cffccada9724 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Fri, 26 Apr 2024 02:13:48 +0200 Subject: [PATCH 7/7] wayland: implement wp-drm-lease-v1 --- deploy-notes.md | 2 + docs/features.md | 5 + release-notes.md | 1 + src/acceptor.rs | 4 +- src/backend.rs | 5 +- src/backends/metal/video.rs | 4 + src/client.rs | 4 + src/client/objects.rs | 9 +- src/ifs.rs | 4 + src/ifs/wp_drm_lease_connector_v1.rs | 93 +++++++ src/ifs/wp_drm_lease_device_v1.rs | 228 ++++++++++++++++++ .../wp_drm_lease_device_v1/removed_device.rs | 80 ++++++ src/ifs/wp_drm_lease_request_v1.rs | 84 +++++++ src/ifs/wp_drm_lease_v1.rs | 92 +++++++ src/ifs/wp_security_context_v1.rs | 4 +- src/state.rs | 8 +- src/tasks/connector.rs | 29 ++- src/tasks/drmdev.rs | 10 + src/utils.rs | 1 + src/utils/bindings.rs | 39 +++ src/utils/oserror.rs | 2 +- src/video/drm.rs | 15 +- src/video/drm/sys.rs | 29 +++ wire/wp_drm_lease_connector_v1.txt | 23 ++ wire/wp_drm_lease_device_v1.txt | 23 ++ wire/wp_drm_lease_request_v1.txt | 7 + wire/wp_drm_lease_v1.txt | 11 + 27 files changed, 797 insertions(+), 19 deletions(-) create mode 100644 src/ifs/wp_drm_lease_connector_v1.rs create mode 100644 src/ifs/wp_drm_lease_device_v1.rs create mode 100644 src/ifs/wp_drm_lease_device_v1/removed_device.rs create mode 100644 src/ifs/wp_drm_lease_request_v1.rs create mode 100644 src/ifs/wp_drm_lease_v1.rs create mode 100644 src/utils/bindings.rs create mode 100644 wire/wp_drm_lease_connector_v1.txt create mode 100644 wire/wp_drm_lease_device_v1.txt create mode 100644 wire/wp_drm_lease_request_v1.txt create mode 100644 wire/wp_drm_lease_v1.txt diff --git a/deploy-notes.md b/deploy-notes.md index 7ca63887..5a287532 100644 --- a/deploy-notes.md +++ b/deploy-notes.md @@ -1,5 +1,7 @@ # Unreleased +- Needs jay-compositor release. + # 1.1.0 - Needs jay-config release. diff --git a/docs/features.md b/docs/features.md index dc0cb816..7942b959 100644 --- a/docs/features.md +++ b/docs/features.md @@ -118,6 +118,10 @@ You can explicitly opt into giving applications access to privileged protocols v Jay's shortcut system allows you to execute an action when a key is pressed and to execute a different action when the key is released. +## VR + +Jay's supports leasing VR headsets to applications. + ## Protocol Support Jay supports the following wayland protocols: @@ -139,6 +143,7 @@ Jay supports the following wayland protocols: | wp_alpha_modifier_v1 | 1 | | | wp_content_type_manager_v1 | 1 | | | wp_cursor_shape_manager_v1 | 1 | | +| wp_drm_lease_device_v1 | 1 | | | wp_fractional_scale_manager_v1 | 1 | | | wp_linux_drm_syncobj_manager_v1 | 1 | | | wp_presentation | 1 | | diff --git a/release-notes.md b/release-notes.md index 81957005..7e377eee 100644 --- a/release-notes.md +++ b/release-notes.md @@ -3,6 +3,7 @@ - Add support for wp-security-manager-v1. - Add support for xdg-dialog-v1. - Add support for ext-transient-seat-v1. +- Add support for wp-drm-lease-v1. # 1.1.0 (2024-04-22) diff --git a/src/acceptor.rs b/src/acceptor.rs index b8d584d0..5f5ce8ff 100644 --- a/src/acceptor.rs +++ b/src/acceptor.rs @@ -1,7 +1,7 @@ use { crate::{ async_engine::SpawnedFuture, - client::{ClientCaps, CAP_LAYER_SHELL}, + client::{ClientCaps, CAPS_DEFAULT}, state::State, utils::{errorfmt::ErrorFmt, oserror::OsError, xrd::xrd}, }, @@ -154,7 +154,7 @@ impl Acceptor { state.eng.spawn(accept( acc.socket.insecure.clone(), state.clone(), - CAP_LAYER_SHELL, + CAPS_DEFAULT, )), ]; state.acceptor.set(Some(acc.clone())); diff --git a/src/backend.rs b/src/backend.rs index fbe976d6..1bee5779 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -6,7 +6,7 @@ use { gfx_api::{GfxFramebuffer, SyncFile}, ifs::wl_seat::wl_pointer::{CONTINUOUS, FINGER, HORIZONTAL_SCROLL, VERTICAL_SCROLL, WHEEL}, libinput::consts::DeviceCapability, - video::drm::{ConnectorType, DrmError, DrmVersion}, + video::drm::{ConnectorType, DrmConnector, DrmError, DrmVersion}, }, jay_config::video::GfxApi, std::{ @@ -94,6 +94,9 @@ pub trait Connector { fn set_non_desktop_override(&self, non_desktop: Option) { let _ = non_desktop; } + fn drm_object_id(&self) -> Option { + None + } } #[derive(Debug)] diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 1f156dbd..edf4a611 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -1246,6 +1246,10 @@ impl Connector for MetalConnector { } } } + + fn drm_object_id(&self) -> Option { + Some(self.id) + } } pub struct MetalCrtc { diff --git a/src/client.rs b/src/client.rs index 4b170cc8..ed64086d 100644 --- a/src/client.rs +++ b/src/client.rs @@ -54,8 +54,12 @@ bitflags! { CAP_LAYER_SHELL = 1 << 6, CAP_SCREENCOPY_MANAGER = 1 << 7, CAP_SEAT_MANAGER = 1 << 8, + CAP_DRM_LEASE = 1 << 9, } +pub const CAPS_DEFAULT: ClientCaps = ClientCaps(CAP_LAYER_SHELL.0 | CAP_DRM_LEASE.0); +pub const CAPS_DEFAULT_SANDBOXED: ClientCaps = ClientCaps(CAP_DRM_LEASE.0); + #[derive(Debug, Copy, Clone, Hash, Ord, PartialOrd, Eq, PartialEq)] pub struct ClientId(u64); diff --git a/src/client/objects.rs b/src/client/objects.rs index def03614..7309ee1f 100644 --- a/src/client/objects.rs +++ b/src/client/objects.rs @@ -20,6 +20,7 @@ use { xdg_surface::{xdg_toplevel::XdgToplevel, XdgSurface}, WlSurface, }, + wp_drm_lease_connector_v1::WpDrmLeaseConnectorV1, wp_linux_drm_syncobj_timeline_v1::WpLinuxDrmSyncobjTimelineV1, xdg_positioner::XdgPositioner, xdg_wm_base::XdgWmBase, @@ -32,8 +33,9 @@ use { wire::{ JayOutputId, JayScreencastId, JayToplevelId, JayWorkspaceId, WlBufferId, WlDataSourceId, WlOutputId, WlPointerId, WlRegionId, WlRegistryId, WlSeatId, - WlSurfaceId, WpLinuxDrmSyncobjTimelineV1Id, XdgPositionerId, XdgSurfaceId, - XdgToplevelId, XdgWmBaseId, ZwlrDataControlSourceV1Id, ZwpPrimarySelectionSourceV1Id, + WlSurfaceId, WpDrmLeaseConnectorV1Id, WpLinuxDrmSyncobjTimelineV1Id, XdgPositionerId, + XdgSurfaceId, XdgToplevelId, XdgWmBaseId, ZwlrDataControlSourceV1Id, + ZwpPrimarySelectionSourceV1Id, }, }, std::{cell::RefCell, mem, rc::Rc}, @@ -62,6 +64,7 @@ pub struct Objects { pub timelines: CopyHashMap>, pub zwlr_data_sources: CopyHashMap>, pub jay_toplevels: CopyHashMap>, + pub drm_lease_outputs: CopyHashMap>, ids: RefCell>, } @@ -92,6 +95,7 @@ impl Objects { timelines: Default::default(), zwlr_data_sources: Default::default(), jay_toplevels: Default::default(), + drm_lease_outputs: Default::default(), ids: RefCell::new(vec![]), } } @@ -126,6 +130,7 @@ impl Objects { self.timelines.clear(); self.zwlr_data_sources.clear(); self.jay_toplevels.clear(); + self.drm_lease_outputs.clear(); } pub fn id(&self, client_data: &Client) -> Result diff --git a/src/ifs.rs b/src/ifs.rs index bb9c496b..9efdf7b0 100644 --- a/src/ifs.rs +++ b/src/ifs.rs @@ -41,6 +41,10 @@ pub mod wp_content_type_manager_v1; pub mod wp_content_type_v1; pub mod wp_cursor_shape_device_v1; pub mod wp_cursor_shape_manager_v1; +pub mod wp_drm_lease_connector_v1; +pub mod wp_drm_lease_device_v1; +pub mod wp_drm_lease_request_v1; +pub mod wp_drm_lease_v1; pub mod wp_fractional_scale_manager_v1; pub mod wp_linux_drm_syncobj_manager_v1; pub mod wp_linux_drm_syncobj_timeline_v1; diff --git a/src/ifs/wp_drm_lease_connector_v1.rs b/src/ifs/wp_drm_lease_connector_v1.rs new file mode 100644 index 00000000..868a8412 --- /dev/null +++ b/src/ifs/wp_drm_lease_connector_v1.rs @@ -0,0 +1,93 @@ +use { + crate::{ + backend::ConnectorId as BackendConnectorId, + client::{Client, ClientError}, + ifs::wp_drm_lease_device_v1::WpDrmLeaseDeviceV1, + leaks::Tracker, + object::{Object, Version}, + utils::bindings::Bindings, + wire::{wp_drm_lease_connector_v1::*, WpDrmLeaseConnectorV1Id}, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct WpDrmLeaseConnectorV1 { + pub id: WpDrmLeaseConnectorV1Id, + pub client: Rc, + pub tracker: Tracker, + pub version: Version, + pub device: Rc, + pub connector_id: BackendConnectorId, + pub bindings: Rc>, +} + +impl WpDrmLeaseConnectorV1 { + fn detach(&self) { + self.bindings.remove(&self.client, self); + } + + pub fn send_name(&self, name: &str) { + self.client.event(Name { + self_id: self.id, + name, + }); + } + + #[allow(dead_code)] + pub fn send_description(&self, description: &str) { + self.client.event(Description { + self_id: self.id, + description, + }); + } + + pub fn send_connector_id(&self, connector_id: u32) { + self.client.event(ConnectorId { + self_id: self.id, + connector_id, + }); + } + + pub fn send_done(&self) { + self.client.event(Done { self_id: self.id }); + } + + pub fn send_withdrawn(&self) { + self.client.event(Withdrawn { self_id: self.id }); + } +} + +impl WpDrmLeaseConnectorV1RequestHandler for WpDrmLeaseConnectorV1 { + type Error = WpDrmLeaseConnectorV1Error; + + fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + self.detach(); + self.client.remove_obj(self)?; + Ok(()) + } +} + +object_base! { + self = WpDrmLeaseConnectorV1; + version = self.version; +} + +impl Object for WpDrmLeaseConnectorV1 { + fn break_loops(&self) { + self.detach(); + } +} + +dedicated_add_obj!( + WpDrmLeaseConnectorV1, + WpDrmLeaseConnectorV1Id, + drm_lease_outputs +); + +#[derive(Debug, Error)] +pub enum WpDrmLeaseConnectorV1Error { + #[error(transparent)] + ClientError(Box), +} +efrom!(WpDrmLeaseConnectorV1Error, ClientError); diff --git a/src/ifs/wp_drm_lease_device_v1.rs b/src/ifs/wp_drm_lease_device_v1.rs new file mode 100644 index 00000000..c3bf68a5 --- /dev/null +++ b/src/ifs/wp_drm_lease_device_v1.rs @@ -0,0 +1,228 @@ +use { + crate::{ + backend::DrmDeviceId, + client::{Client, ClientCaps, ClientError, CAP_DRM_LEASE}, + globals::{Global, GlobalName}, + ifs::{ + wp_drm_lease_connector_v1::WpDrmLeaseConnectorV1, + wp_drm_lease_request_v1::WpDrmLeaseRequestV1, + }, + leaks::Tracker, + object::{Object, Version}, + state::OutputData, + utils::{bindings::Bindings, errorfmt::ErrorFmt, oserror::OsError}, + video::drm::{Drm, DrmError}, + wire::{wp_drm_lease_device_v1::*, WpDrmLeaseDeviceV1Id}, + }, + std::{cell::Cell, rc::Rc}, + thiserror::Error, + uapi::{c, OwnedFd}, +}; + +mod removed_device; + +pub struct WpDrmLeaseDeviceV1Global { + pub name: GlobalName, + pub device: DrmDeviceId, + pub bindings: Rc>, +} + +impl WpDrmLeaseDeviceV1Global { + fn bind_( + self: Rc, + id: WpDrmLeaseDeviceV1Id, + client: &Rc, + version: Version, + ) -> Result<(), WpDrmLeaseDeviceV1Error> { + let obj = Rc::new(WpDrmLeaseDeviceV1 { + id, + client: client.clone(), + tracker: Default::default(), + version, + bindings: self.bindings.clone(), + device: self.device, + destroyed: Cell::new(false), + }); + track!(client, obj); + client.add_client_obj(&obj)?; + if let Some(dev) = client.state.drm_devs.get(&self.device) { + if let Some(node) = &dev.devnode { + match reopen_card(node) { + Ok(f) => obj.send_drm_fd(&f), + Err(e) => { + log::error!("Could not open master device: {}", ErrorFmt(e)); + } + } + } + for c in dev.connectors.lock().keys() { + if let Some(o) = client.state.outputs.get(c) { + if o.monitor_info.non_desktop { + obj.create_connector(&o); + } + } + } + } + obj.send_done(); + self.bindings.add(client, &obj); + Ok(()) + } +} + +global_base!( + WpDrmLeaseDeviceV1Global, + WpDrmLeaseDeviceV1, + WpDrmLeaseDeviceV1Error +); + +simple_add_global!(WpDrmLeaseDeviceV1Global); + +impl Global for WpDrmLeaseDeviceV1Global { + fn singleton(&self) -> bool { + false + } + + fn version(&self) -> u32 { + 1 + } + + fn break_loops(&self) { + self.bindings.clear(); + } + + fn required_caps(&self) -> ClientCaps { + CAP_DRM_LEASE + } +} + +pub struct WpDrmLeaseDeviceV1 { + pub id: WpDrmLeaseDeviceV1Id, + pub client: Rc, + pub tracker: Tracker, + pub version: Version, + pub bindings: Rc>, + pub device: DrmDeviceId, + pub destroyed: Cell, +} + +impl WpDrmLeaseDeviceV1 { + fn detach(&self) { + self.destroyed.set(true); + self.bindings.remove(&self.client, self); + } + + pub fn create_connector(self: &Rc, output: &Rc) { + let id = match self.client.new_id() { + Ok(i) => i, + Err(e) => { + self.client.error(e); + return; + } + }; + let obj = Rc::new(WpDrmLeaseConnectorV1 { + id, + client: self.client.clone(), + tracker: Default::default(), + version: self.version, + device: self.clone(), + connector_id: output.connector.connector.id(), + bindings: output.lease_connectors.clone(), + }); + self.client.add_server_obj(&obj); + self.send_connector(&obj); + obj.send_name(&output.connector.name); + if let Some(id) = output.connector.connector.drm_object_id() { + obj.send_connector_id(id.0); + } + obj.send_done(); + output.lease_connectors.add(&self.client, &obj); + } + + fn send_drm_fd(&self, fd: &Rc) { + self.client.event(DrmFd { + self_id: self.id, + fd: fd.clone(), + }); + } + + fn send_connector(&self, c: &Rc) { + self.client.event(Connector { + self_id: self.id, + id: c.id, + }); + } + + pub fn send_done(&self) { + self.client.event(Done { self_id: self.id }); + } + + fn send_released(&self) { + self.client.event(Released { self_id: self.id }); + } +} + +impl WpDrmLeaseDeviceV1RequestHandler for WpDrmLeaseDeviceV1 { + type Error = WpDrmLeaseDeviceV1Error; + + fn create_lease_request( + &self, + req: CreateLeaseRequest, + _slf: &Rc, + ) -> Result<(), Self::Error> { + let obj = Rc::new(WpDrmLeaseRequestV1 { + id: req.id, + client: self.client.clone(), + tracker: Default::default(), + version: self.version, + device: self.device, + connectors: Default::default(), + }); + self.client.add_client_obj(&obj)?; + Ok(()) + } + + fn release(&self, _req: Release, _slf: &Rc) -> Result<(), Self::Error> { + self.detach(); + self.send_released(); + self.client.remove_obj(self)?; + Ok(()) + } +} + +object_base! { + self = WpDrmLeaseDeviceV1; + version = self.version; +} + +impl Object for WpDrmLeaseDeviceV1 { + fn break_loops(&self) { + self.detach(); + } +} + +simple_add_obj!(WpDrmLeaseDeviceV1); + +#[derive(Debug, Error)] +pub enum WpDrmLeaseDeviceV1Error { + #[error(transparent)] + ClientError(Box), +} +efrom!(WpDrmLeaseDeviceV1Error, ClientError); + +#[derive(Debug, Error)] +enum ReopenError { + #[error("Could not open the dev node")] + OpenNode(#[source] OsError), + #[error("Could not drop DRM master")] + DropMaster(#[source] DrmError), +} + +fn reopen_card(devnode: &str) -> Result, ReopenError> { + let fd = uapi::open(devnode, c::O_RDWR | c::O_CLOEXEC, 0) + .map_err(|e| ReopenError::OpenNode(e.into()))?; + let fd = Rc::new(fd); + let drm = Drm::open_existing(fd.clone()); + if drm.is_master() { + drm.drop_master().map_err(ReopenError::DropMaster)?; + } + Ok(fd) +} diff --git a/src/ifs/wp_drm_lease_device_v1/removed_device.rs b/src/ifs/wp_drm_lease_device_v1/removed_device.rs new file mode 100644 index 00000000..ff4f4425 --- /dev/null +++ b/src/ifs/wp_drm_lease_device_v1/removed_device.rs @@ -0,0 +1,80 @@ +use { + crate::{ + backend::DrmDeviceId, + client::{Client, ClientCaps, ClientError, CAP_DRM_LEASE}, + globals::{Global, GlobalName, RemovableWaylandGlobal}, + ifs::wp_drm_lease_device_v1::{WpDrmLeaseDeviceV1, WpDrmLeaseDeviceV1Global}, + object::Version, + utils::bindings::Bindings, + wire::WpDrmLeaseDeviceV1Id, + }, + std::{cell::Cell, rc::Rc}, + thiserror::Error, +}; + +struct RemovedWpDrmLeaseDeviceV1Global { + name: GlobalName, + bindings: Rc>, +} + +impl RemovedWpDrmLeaseDeviceV1Global { + fn bind_( + self: Rc, + id: WpDrmLeaseDeviceV1Id, + client: &Rc, + version: Version, + ) -> Result<(), RemovedWpDrmLeaseDeviceV1Error> { + let dev = Rc::new(WpDrmLeaseDeviceV1 { + id, + client: client.clone(), + tracker: Default::default(), + version, + bindings: self.bindings.clone(), + device: DrmDeviceId::from_raw(0), + destroyed: Cell::new(false), + }); + track!(client, dev); + client.add_client_obj(&dev)?; + dev.send_done(); + dev.bindings.add(client, &dev); + Ok(()) + } +} + +global_base!( + RemovedWpDrmLeaseDeviceV1Global, + WpDrmLeaseDeviceV1, + RemovedWpDrmLeaseDeviceV1Error +); + +simple_add_global!(RemovedWpDrmLeaseDeviceV1Global); + +impl Global for RemovedWpDrmLeaseDeviceV1Global { + fn singleton(&self) -> bool { + false + } + + fn version(&self) -> u32 { + 1 + } + + fn required_caps(&self) -> ClientCaps { + CAP_DRM_LEASE + } +} + +impl RemovableWaylandGlobal for WpDrmLeaseDeviceV1Global { + fn create_replacement(&self) -> Rc { + Rc::new(RemovedWpDrmLeaseDeviceV1Global { + name: self.name, + bindings: Default::default(), + }) + } +} + +#[derive(Debug, Error)] +pub enum RemovedWpDrmLeaseDeviceV1Error { + #[error(transparent)] + ClientError(Box), +} +efrom!(RemovedWpDrmLeaseDeviceV1Error, ClientError); diff --git a/src/ifs/wp_drm_lease_request_v1.rs b/src/ifs/wp_drm_lease_request_v1.rs new file mode 100644 index 00000000..70c95c18 --- /dev/null +++ b/src/ifs/wp_drm_lease_request_v1.rs @@ -0,0 +1,84 @@ +use { + crate::{ + backend::{ConnectorId, DrmDeviceId}, + client::{Client, ClientError}, + ifs::wp_drm_lease_v1::{WpDrmLeaseV1, WpDrmLeaseV1Lessee}, + leaks::Tracker, + object::{Object, Version}, + utils::copyhashmap::CopyHashMap, + wire::{wp_drm_lease_request_v1::*, WpDrmLeaseConnectorV1Id, WpDrmLeaseRequestV1Id}, + }, + std::{cell::Cell, rc::Rc}, + thiserror::Error, +}; + +pub struct WpDrmLeaseRequestV1 { + pub id: WpDrmLeaseRequestV1Id, + pub client: Rc, + pub tracker: Tracker, + pub version: Version, + pub device: DrmDeviceId, + pub connectors: CopyHashMap, +} + +impl WpDrmLeaseRequestV1RequestHandler for WpDrmLeaseRequestV1 { + type Error = WpDrmLeaseRequestV1Error; + + fn request_connector(&self, req: RequestConnector, _slf: &Rc) -> Result<(), Self::Error> { + let c = self.client.lookup(req.connector)?; + if self.device != c.device.device { + return Err(WpDrmLeaseRequestV1Error::MismatchedDevice(c.id)); + } + if self.connectors.contains(&c.id) { + return Err(WpDrmLeaseRequestV1Error::RepeatedDevice(c.id)); + } + self.connectors.set(c.id, c.connector_id); + Ok(()) + } + + fn submit(&self, req: Submit, _slf: &Rc) -> Result<(), Self::Error> { + self.client.remove_obj(self)?; + let obj = Rc::new(WpDrmLeaseV1 { + id: req.id, + client: self.client.clone(), + tracker: Default::default(), + version: self.version, + finished: Cell::new(false), + lease: Default::default(), + }); + self.client.add_client_obj(&obj)?; + if self.connectors.is_empty() { + return Err(WpDrmLeaseRequestV1Error::EmptyLease); + } + let Some(dev) = self.client.state.drm_devs.get(&self.device) else { + obj.send_finished(); + return Ok(()); + }; + let lessee = Rc::new(WpDrmLeaseV1Lessee { obj }); + let connectors: Vec<_> = self.connectors.lock().values().copied().collect(); + dev.dev.clone().create_lease(lessee, &connectors); + Ok(()) + } +} + +object_base! { + self = WpDrmLeaseRequestV1; + version = self.version; +} + +impl Object for WpDrmLeaseRequestV1 {} + +simple_add_obj!(WpDrmLeaseRequestV1); + +#[derive(Debug, Error)] +pub enum WpDrmLeaseRequestV1Error { + #[error(transparent)] + ClientError(Box), + #[error("Connector {0} does not belong to this device")] + MismatchedDevice(WpDrmLeaseConnectorV1Id), + #[error("Connector {0} is already part of this request")] + RepeatedDevice(WpDrmLeaseConnectorV1Id), + #[error("Lease request is empty")] + EmptyLease, +} +efrom!(WpDrmLeaseRequestV1Error, ClientError); diff --git a/src/ifs/wp_drm_lease_v1.rs b/src/ifs/wp_drm_lease_v1.rs new file mode 100644 index 00000000..3613670e --- /dev/null +++ b/src/ifs/wp_drm_lease_v1.rs @@ -0,0 +1,92 @@ +use { + crate::{ + backend::{BackendDrmLease, BackendDrmLessee}, + client::{Client, ClientError}, + leaks::Tracker, + object::{Object, Version}, + utils::clonecell::CloneCell, + wire::{wp_drm_lease_v1::*, WpDrmLeaseV1Id}, + }, + std::{cell::Cell, rc::Rc}, + thiserror::Error, + uapi::OwnedFd, +}; + +pub struct WpDrmLeaseV1Lessee { + pub obj: Rc, +} + +impl BackendDrmLessee for WpDrmLeaseV1Lessee { + fn created(&self, lease: Rc) { + if !self.obj.finished.get() { + self.obj.send_lease_fd(lease.fd()); + self.obj.lease.set(Some(lease)); + } + } +} + +impl Drop for WpDrmLeaseV1Lessee { + fn drop(&mut self) { + if !self.obj.finished.get() { + self.obj.detach(); + self.obj.send_finished(); + } + } +} + +pub struct WpDrmLeaseV1 { + pub id: WpDrmLeaseV1Id, + pub client: Rc, + pub tracker: Tracker, + pub version: Version, + pub finished: Cell, + pub lease: CloneCell>>, +} + +impl WpDrmLeaseV1 { + fn detach(&self) { + self.finished.set(true); + self.lease.take(); + } + + fn send_lease_fd(&self, fd: &Rc) { + self.client.event(LeaseFd { + self_id: self.id, + leased_fd: fd.clone(), + }); + } + + pub fn send_finished(&self) { + self.client.event(Finished { self_id: self.id }); + } +} + +impl WpDrmLeaseV1RequestHandler for WpDrmLeaseV1 { + type Error = WpDrmLeaseV1Error; + + fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + self.detach(); + self.client.remove_obj(self)?; + Ok(()) + } +} + +object_base! { + self = WpDrmLeaseV1; + version = self.version; +} + +impl Object for WpDrmLeaseV1 { + fn break_loops(&self) { + self.detach(); + } +} + +simple_add_obj!(WpDrmLeaseV1); + +#[derive(Debug, Error)] +pub enum WpDrmLeaseV1Error { + #[error(transparent)] + ClientError(Box), +} +efrom!(WpDrmLeaseV1Error, ClientError); diff --git a/src/ifs/wp_security_context_v1.rs b/src/ifs/wp_security_context_v1.rs index 670a70d3..84ce3041 100644 --- a/src/ifs/wp_security_context_v1.rs +++ b/src/ifs/wp_security_context_v1.rs @@ -1,6 +1,6 @@ use { crate::{ - client::{Client, ClientCaps, ClientError}, + client::{Client, ClientError, CAPS_DEFAULT_SANDBOXED}, leaks::Tracker, object::{Object, Version}, wire::{wp_security_context_v1::*, WpSecurityContextV1Id}, @@ -80,7 +80,7 @@ impl WpSecurityContextV1RequestHandler for WpSecurityContextV1 { fn commit(&self, _req: Commit, _slf: &Rc) -> Result<(), Self::Error> { self.check_committed()?; self.committed.set(true); - let caps = ClientCaps::none() & self.client.bounding_caps; + let caps = CAPS_DEFAULT_SANDBOXED & self.client.bounding_caps; self.client.state.security_context_acceptors.spawn( &self.client.state, self.sandbox_engine.take(), diff --git a/src/state.rs b/src/state.rs index a051a165..48cc9e16 100644 --- a/src/state.rs +++ b/src/state.rs @@ -40,6 +40,8 @@ use { zwp_input_popup_surface_v2::ZwpInputPopupSurfaceV2, NoneSurfaceExt, WlSurface, }, + wp_drm_lease_connector_v1::WpDrmLeaseConnectorV1, + wp_drm_lease_device_v1::WpDrmLeaseDeviceV1Global, wp_linux_drm_syncobj_manager_v1::WpLinuxDrmSyncobjManagerV1Global, zwlr_screencopy_frame_v1::ZwlrScreencopyFrameV1, zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1, @@ -59,8 +61,8 @@ use { WorkspaceNode, }, utils::{ - activation_token::ActivationToken, asyncevent::AsyncEvent, clonecell::CloneCell, - copyhashmap::CopyHashMap, errorfmt::ErrorFmt, fdcloser::FdCloser, + activation_token::ActivationToken, asyncevent::AsyncEvent, bindings::Bindings, + clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, fdcloser::FdCloser, linkedlist::LinkedList, numcell::NumCell, queue::AsyncQueue, refcounted::RefCounted, run_toplevel::RunToplevel, }, @@ -269,6 +271,7 @@ pub struct OutputData { pub connector: Rc, pub monitor_info: MonitorInfo, pub node: Option>, + pub lease_connectors: Rc>, } pub struct DrmDevData { @@ -280,6 +283,7 @@ pub struct DrmDevData { pub vendor: Option, pub model: Option, pub pci_id: Option, + pub lease_global: Rc, } impl DrmDevData { diff --git a/src/tasks/connector.rs b/src/tasks/connector.rs index c5fb2382..bd61ea59 100644 --- a/src/tasks/connector.rs +++ b/src/tasks/connector.rs @@ -175,6 +175,7 @@ impl ConnectorHandler { connector: self.data.clone(), monitor_info: info, node: Some(on.clone()), + lease_connectors: Default::default(), }); self.state.outputs.set(self.id, output_data); on.schedule_update_render_data(); @@ -293,9 +294,6 @@ impl ConnectorHandler { seat.set_position((tpos.x1() + tpos.x2()) / 2, (tpos.y1() + tpos.y2()) / 2); } } - if let Some(dev) = &self.data.drm_dev { - dev.connectors.remove(&self.id); - } self.state .remove_output_scale(on.global.persistent.scale.get()); let _ = self.state.remove_global(&*global); @@ -308,8 +306,26 @@ impl ConnectorHandler { connector: self.data.clone(), monitor_info, node: None, + lease_connectors: Default::default(), }); - self.state.outputs.set(self.id, output_data); + self.state.outputs.set(self.id, output_data.clone()); + let advertise = || { + if let Some(dev) = &self.data.drm_dev { + for binding in dev.lease_global.bindings.lock().values() { + binding.create_connector(&output_data); + binding.send_done(); + } + } + }; + let withdraw = || { + for (_, con) in output_data.lease_connectors.lock().drain() { + con.send_withdrawn(); + if !con.device.destroyed.get() { + con.device.send_done(); + } + } + }; + advertise(); if let Some(config) = self.state.config.get() { config.connector_connected(self.id); } @@ -317,13 +333,14 @@ impl ConnectorHandler { while let Some(event) = self.data.connector.event() { match event { ConnectorEvent::Disconnected => break 'outer, - ConnectorEvent::Available => {} - ConnectorEvent::Unavailable => {} + ConnectorEvent::Available => advertise(), + ConnectorEvent::Unavailable => withdraw(), ev => unreachable!("received unexpected event {:?}", ev), } } self.data.async_event.triggered().await; } + withdraw(); self.state.outputs.remove(&self.id); if let Some(config) = self.state.config.get() { config.connector_disconnected(self.id); diff --git a/src/tasks/drmdev.rs b/src/tasks/drmdev.rs index 993c4aee..38d6c95b 100644 --- a/src/tasks/drmdev.rs +++ b/src/tasks/drmdev.rs @@ -1,6 +1,7 @@ use { crate::{ backend::{BackendDrmDevice, DrmDeviceId, DrmEvent}, + ifs::wp_drm_lease_device_v1::WpDrmLeaseDeviceV1Global, state::{DrmDevData, State}, tasks::udev_utils::udev_props, utils::asyncevent::AsyncEvent, @@ -11,6 +12,12 @@ use { pub fn handle(state: &Rc, dev: Rc) { let id = dev.id(); let props = udev_props(dev.dev_t(), 1); + let lease_global = Rc::new(WpDrmLeaseDeviceV1Global { + name: state.globals.name(), + device: id, + bindings: Default::default(), + }); + state.add_global(&lease_global); let data = Rc::new(DrmDevData { dev: dev.clone(), handler: Cell::new(None), @@ -20,6 +27,7 @@ pub fn handle(state: &Rc, dev: Rc) { vendor: props.vendor, model: props.model, pci_id: props.pci_id, + lease_global, }); let oh = DrvDevHandler { id, @@ -66,6 +74,8 @@ impl DrvDevHandler { if let Some(config) = self.state.config.get() { config.del_drm_dev(self.id); } + self.data.lease_global.bindings.clear(); + let _ = self.state.remove_global(&*self.data.lease_global); self.data.handler.set(None); self.state.drm_devs.remove(&self.id); } diff --git a/src/utils.rs b/src/utils.rs index 0ab8c1d3..2d198bb7 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,6 +1,7 @@ pub mod activation_token; pub mod array; pub mod asyncevent; +pub mod bindings; pub mod bitfield; pub mod bitflags; pub mod buf; diff --git a/src/utils/bindings.rs b/src/utils/bindings.rs new file mode 100644 index 00000000..60134b01 --- /dev/null +++ b/src/utils/bindings.rs @@ -0,0 +1,39 @@ +use { + crate::{ + client::{Client, ClientId}, + object::{Object, ObjectId}, + utils::copyhashmap::{CopyHashMap, Locked}, + }, + std::rc::Rc, +}; + +pub struct Bindings

{ + bindings: CopyHashMap<(ClientId, ObjectId), Rc

>, +} + +impl

Default for Bindings

{ + fn default() -> Self { + Self { + bindings: Default::default(), + } + } +} + +impl Bindings

{ + pub fn add(&self, client: &Client, obj: &Rc

) { + let prev = self.bindings.set((client.id, obj.id()), obj.clone()); + assert!(prev.is_none()); + } + + pub fn remove(&self, client: &Client, obj: &P) { + self.bindings.remove(&(client.id, obj.id())); + } + + pub fn clear(&self) { + self.bindings.clear(); + } + + pub fn lock(&self) -> Locked<(ClientId, ObjectId), Rc

> { + self.bindings.lock() + } +} diff --git a/src/utils/oserror.rs b/src/utils/oserror.rs index a0ea8f5f..3f6c73e0 100644 --- a/src/utils/oserror.rs +++ b/src/utils/oserror.rs @@ -163,7 +163,7 @@ static ERRORS: Lazy<&'static [Option<&'static str>]> = Lazy::new(|| { res.leak() }); -#[derive(Debug)] +#[derive(Debug, Eq, PartialEq)] pub struct OsError(pub c::c_int); impl From for OsError { diff --git a/src/video/drm.rs b/src/video/drm.rs index 051181ba..39194a49 100644 --- a/src/video/drm.rs +++ b/src/video/drm.rs @@ -40,8 +40,8 @@ use crate::{ video::{ dmabuf::DmaBuf, drm::sys::{ - drm_format_modifier, drm_format_modifier_blob, get_version, revoke_lease, - DRM_CAP_CURSOR_HEIGHT, DRM_CAP_CURSOR_WIDTH, FORMAT_BLOB_CURRENT, + auth_magic, drm_format_modifier, drm_format_modifier_blob, drop_master, get_version, + revoke_lease, DRM_CAP_CURSOR_HEIGHT, DRM_CAP_CURSOR_WIDTH, FORMAT_BLOB_CURRENT, }, Modifier, INVALID_MODIFIER, }, @@ -139,6 +139,8 @@ pub enum DrmError { ImportSyncFile(#[source] OsError), #[error("Could not create a lease")] CreateLease(#[source] OsError), + #[error("Could not drop DRM master")] + DropMaster(#[source] OsError), } fn render_node_name(fd: c::c_int) -> Result { @@ -175,7 +177,6 @@ pub struct Drm { } impl Drm { - #[cfg_attr(not(feature = "it"), allow(dead_code))] pub fn open_existing(fd: Rc) -> Self { Self { fd } } @@ -213,6 +214,14 @@ impl Drm { pub fn version(&self) -> Result { get_version(self.fd.raw()).map_err(DrmError::Version) } + + pub fn drop_master(&self) -> Result<(), DrmError> { + drop_master(self.fd.raw()).map_err(DrmError::DropMaster) + } + + pub fn is_master(&self) -> bool { + auth_magic(self.fd.raw(), 0) != Err(OsError(c::EACCES)) + } } pub struct InFormat { diff --git a/src/video/drm/sys.rs b/src/video/drm/sys.rs index 8ad7f5e3..61579ce5 100644 --- a/src/video/drm/sys.rs +++ b/src/video/drm/sys.rs @@ -37,6 +37,10 @@ pub unsafe fn ioctl(fd: c::c_int, request: c::c_ulong, t: &mut T) -> Result u64 { + uapi::_IO(DRM_IOCTL_BASE, nr) +} + pub const fn drm_iow(nr: u64) -> u64 { uapi::_IOW::(DRM_IOCTL_BASE, nr) } @@ -1369,3 +1373,28 @@ pub fn sync_ioc_merge(left: c::c_int, right: c::c_int) -> Result Result<(), OsError> { + let mut res = 0u8; + unsafe { + ioctl(fd, DRM_IOCTL_DROP_MASTER, &mut res)?; + } + Ok(()) +} + +const DRM_IOCTL_AUTH_MAGIC: u64 = drm_iow::(0x11); + +#[repr(C)] +struct drm_auth { + magic: c::c_uint, +} + +pub fn auth_magic(fd: c::c_int, magic: c::c_uint) -> Result<(), OsError> { + let mut res = drm_auth { magic }; + unsafe { + ioctl(fd, DRM_IOCTL_AUTH_MAGIC, &mut res)?; + } + Ok(()) +} diff --git a/wire/wp_drm_lease_connector_v1.txt b/wire/wp_drm_lease_connector_v1.txt new file mode 100644 index 00000000..853296f7 --- /dev/null +++ b/wire/wp_drm_lease_connector_v1.txt @@ -0,0 +1,23 @@ +request destroy { + +} + +event name { + name: str, +} + +event description { + description: str, +} + +event connector_id { + connector_id: u32, +} + +event done { + +} + +event withdrawn { + +} diff --git a/wire/wp_drm_lease_device_v1.txt b/wire/wp_drm_lease_device_v1.txt new file mode 100644 index 00000000..c0a938b9 --- /dev/null +++ b/wire/wp_drm_lease_device_v1.txt @@ -0,0 +1,23 @@ +request create_lease_request { + id: id(wp_drm_lease_request_v1), +} + +request release { + +} + +event drm_fd { + fd: fd, +} + +event connector { + id: id(wp_drm_lease_connector_v1), +} + +event done { + +} + +event released { + +} diff --git a/wire/wp_drm_lease_request_v1.txt b/wire/wp_drm_lease_request_v1.txt new file mode 100644 index 00000000..527e4a14 --- /dev/null +++ b/wire/wp_drm_lease_request_v1.txt @@ -0,0 +1,7 @@ +request request_connector { + connector: id(wp_drm_lease_connector_v1), +} + +request submit { + id: id(wp_drm_lease_v1), +} diff --git a/wire/wp_drm_lease_v1.txt b/wire/wp_drm_lease_v1.txt new file mode 100644 index 00000000..83055426 --- /dev/null +++ b/wire/wp_drm_lease_v1.txt @@ -0,0 +1,11 @@ +request destroy { + +} + +event lease_fd { + leased_fd: fd, +} + +event finished { + +}