From 98b6eba81c40af24f70dc47d8b5a4e583be91bdd Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Mon, 4 Mar 2024 17:18:44 +0100 Subject: [PATCH] metal: allow changing the connector mode --- jay-config/src/_private/client.rs | 4 +++ jay-config/src/_private/ipc.rs | 4 +++ jay-config/src/video.rs | 37 ++++++++++++++++++++++ src/backend.rs | 1 + src/backends/dummy.rs | 6 +++- src/backends/metal/video.rs | 51 +++++++++++++++++++++++++++++-- src/backends/x.rs | 4 +++ src/config/handler.rs | 17 +++++++++++ src/it/test_backend.rs | 4 +++ 9 files changed, 124 insertions(+), 4 deletions(-) diff --git a/jay-config/src/_private/client.rs b/jay-config/src/_private/client.rs index 749ad37f..64faadad 100644 --- a/jay-config/src/_private/client.rs +++ b/jay-config/src/_private/client.rs @@ -570,6 +570,10 @@ impl Client { } } + pub fn connector_set_mode(&self, connector: Connector, mode: WireMode) { + self.send(&ClientMessage::ConnectorSetMode { connector, mode }); + } + pub fn connector_modes(&self, connector: Connector) -> Vec { let res = self.send_with_response(&ClientMessage::ConnectorModes { connector }); get_response!(res, Vec::new(), ConnectorModes { modes }); diff --git a/jay-config/src/_private/ipc.rs b/jay-config/src/_private/ipc.rs index 02e3e65c..24751afe 100644 --- a/jay-config/src/_private/ipc.rs +++ b/jay-config/src/_private/ipc.rs @@ -356,6 +356,10 @@ pub enum ClientMessage<'a> { ConnectorModes { connector: Connector, }, + ConnectorSetMode { + connector: Connector, + mode: WireMode, + }, } #[derive(Serialize, Deserialize, Debug)] diff --git a/jay-config/src/video.rs b/jay-config/src/video.rs index cb4da383..a41a2f8f 100644 --- a/jay-config/src/video.rs +++ b/jay-config/src/video.rs @@ -9,6 +9,7 @@ use { CON_VIRTUAL, CON_WRITEBACK, }, PciId, + _private::WireMode, }, serde::{Deserialize, Serialize}, std::str::FromStr, @@ -112,6 +113,42 @@ impl Connector { get!(Mode::zeroed()).connector_mode(self) } + /// Tries to set the mode of the connector. + /// + /// If the refresh rate is not specified, tries to use the first mode with the given + /// width and height. + /// + /// The default mode is the first mode advertised by the connector. This is usually + /// the native mode. + pub fn set_mode(self, width: i32, height: i32, refresh_millihz: Option) { + if !self.exists() { + log::warn!("set_mode called on a connector that does not exist"); + return; + } + let refresh_millihz = match refresh_millihz { + Some(r) => r, + _ => match self + .modes() + .iter() + .find(|m| m.width == width && m.height == height) + { + Some(m) => m.refresh_millihz, + _ => { + log::warn!("Could not find any mode with width {width} and height {height}"); + return; + } + }, + }; + get!().connector_set_mode( + self, + WireMode { + width, + height, + refresh_millihz, + }, + ) + } + /// Returns the available modes of the connector. pub fn modes(self) -> Vec { if !self.exists() { diff --git a/src/backend.rs b/src/backend.rs index 45548c6a..c69c5bd1 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -83,6 +83,7 @@ pub trait Connector { fn drm_feedback(&self) -> Option> { None } + fn set_mode(&self, mode: Mode); } #[derive(Debug)] diff --git a/src/backends/dummy.rs b/src/backends/dummy.rs index 542b8e90..ed6f8cfb 100644 --- a/src/backends/dummy.rs +++ b/src/backends/dummy.rs @@ -2,7 +2,7 @@ use { crate::{ async_engine::SpawnedFuture, backend::{ - Backend, Connector, ConnectorEvent, ConnectorId, ConnectorKernelId, DrmDeviceId, + Backend, Connector, ConnectorEvent, ConnectorId, ConnectorKernelId, DrmDeviceId, Mode, }, video::drm::ConnectorType, }, @@ -56,4 +56,8 @@ impl Connector for DummyOutput { fn set_enabled(&self, _enabled: bool) { // nothing } + + fn set_mode(&self, _mode: Mode) { + // nothing + } } diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index f7d33d83..f0710b95 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -3,7 +3,7 @@ use { async_engine::{Phase, SpawnedFuture}, backend::{ BackendDrmDevice, BackendEvent, Connector, ConnectorEvent, ConnectorId, - ConnectorKernelId, DrmDeviceId, HardwareCursor, MonitorInfo, + ConnectorKernelId, DrmDeviceId, HardwareCursor, Mode, MonitorInfo, }, backends::metal::{MetalBackend, MetalError}, drm_feedback::DrmFeedback, @@ -155,7 +155,7 @@ pub struct ConnectorDisplayData { pub crtc_id: MutableProperty, pub crtcs: AHashMap>, pub modes: Vec, - pub mode: Option>, + pub mode: Option, pub refresh: u32, pub monitor_manufacturer: String, @@ -862,6 +862,44 @@ impl Connector for MetalConnector { fn drm_feedback(&self) -> Option> { self.drm_feedback.get() } + + fn set_mode(&self, be_mode: Mode) { + 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; + }; + let prev = dd.mode.clone(); + if prev.as_ref() == Some(mode) { + return; + } + if dd.connection != ConnectorStatus::Connected { + log::warn!("Cannot change mode of connector that is not connected"); + return; + } + let Some(dev) = self.backend.device_holder.drm_devices.get(&self.dev.devnum) else { + log::warn!("Cannot change mode because underlying device does not exist?"); + return; + }; + log::info!("Trying to change mode from {:?} to {:?}", prev, mode); + 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)); + return; + }; + log::warn!("Could not change mode: {}", ErrorFmt(&e)); + self.display.borrow_mut().mode = prev; + if let MetalError::Modeset(DrmError::Atomic(OsError(c::EACCES))) = e { + log::warn!("Failed due to access denied. Resetting in memory only."); + return; + } + log::warn!("Trying to re-initialize the drm device"); + if let Err(e) = self.backend.handle_drm_change_(&dev, true) { + log::warn!("Could not restore the previous mode: {}", ErrorFmt(e)); + }; + } } #[derive(Debug)] @@ -1021,7 +1059,7 @@ fn create_connector_display_data( let mut name = String::new(); let mut manufacturer = String::new(); let mut serial_number = String::new(); - let mode = info.modes.first().cloned().map(Rc::new); + let mode = info.modes.first().cloned(); let refresh = mode .as_ref() .map(|m| 1_000_000_000_000u64 / (m.refresh_rate_millihz() as u64)) @@ -1402,6 +1440,13 @@ impl MetalBackend { } }; let mut old = c.display.borrow_mut(); + if old.is_same_monitor(&dd) { + if let Some(mode) = &old.mode { + if dd.modes.contains(mode) { + dd.mode = Some(mode.clone()); + } + } + } mem::swap(old.deref_mut(), &mut dd); if c.connect_sent.get() { if !c.enabled.get() diff --git a/src/backends/x.rs b/src/backends/x.rs index dfea819d..c195ca14 100644 --- a/src/backends/x.rs +++ b/src/backends/x.rs @@ -1058,6 +1058,10 @@ impl Connector for XOutput { fn set_enabled(&self, _enabled: bool) { // nothing } + + fn set_mode(&self, _mode: Mode) { + log::warn!("X backend doesn't support changing the connector mode"); + } } struct XSeat { diff --git a/src/config/handler.rs b/src/config/handler.rs index a1612c2b..276d13c3 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -695,6 +695,20 @@ impl ConfigProxyHandler { Ok(()) } + fn handle_connector_set_mode( + &self, + connector: Connector, + mode: WireMode, + ) -> Result<(), CphError> { + let connector = self.get_output(connector)?; + connector.connector.connector.set_mode(backend::Mode { + width: mode.width, + height: mode.height, + refresh_rate_millihz: mode.refresh_millihz, + }); + Ok(()) + } + fn handle_connector_modes(&self, connector: Connector) -> Result<(), CphError> { let connector = self.get_output(connector)?; self.respond(Response::ConnectorModes { @@ -1391,6 +1405,9 @@ impl ConfigProxyHandler { ClientMessage::ConnectorModes { connector } => self .handle_connector_modes(connector) .wrn("connector_modes")?, + ClientMessage::ConnectorSetMode { connector, mode } => self + .handle_connector_set_mode(connector, mode) + .wrn("connector_set_mode")?, } Ok(()) } diff --git a/src/it/test_backend.rs b/src/it/test_backend.rs index 5391347e..2de308a3 100644 --- a/src/it/test_backend.rs +++ b/src/it/test_backend.rs @@ -246,6 +246,10 @@ impl Connector for TestConnector { fn set_enabled(&self, _enabled: bool) { // todo } + + fn set_mode(&self, _mode: Mode) { + // todo + } } pub struct TestMouseClick {