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, +}