From 558bea47b7da15a6e7d017a9b4388390962fd862 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Mon, 4 Mar 2024 16:09:53 +0100 Subject: [PATCH 1/2] config: allow retrieving the modes --- jay-config/src/_private.rs | 24 +++++++++++++++++++++++- jay-config/src/_private/client.rs | 8 +++++++- jay-config/src/_private/ipc.rs | 7 +++++++ jay-config/src/video.rs | 10 +++++++++- src/compositor.rs | 1 + src/config/handler.rs | 22 ++++++++++++++++++++++ src/ifs/wl_output.rs | 3 +++ src/tasks/connector.rs | 1 + 8 files changed, 73 insertions(+), 3 deletions(-) diff --git a/jay-config/src/_private.rs b/jay-config/src/_private.rs index d85d10c5..7e7da600 100644 --- a/jay-config/src/_private.rs +++ b/jay-config/src/_private.rs @@ -2,7 +2,12 @@ pub mod client; pub mod ipc; mod logging; -use {bincode::Options, std::marker::PhantomData}; +use { + crate::video::Mode, + bincode::Options, + serde::{Deserialize, Serialize}, + std::marker::PhantomData, +}; pub const VERSION: u32 = 1; @@ -36,3 +41,20 @@ pub fn bincode_ops() -> impl Options { pub trait Config { extern "C" fn configure(); } + +#[derive(Serialize, Deserialize, Debug)] +pub struct WireMode { + pub width: i32, + pub height: i32, + pub refresh_millihz: u32, +} + +impl WireMode { + pub fn to_mode(self) -> Mode { + Mode { + width: self.width, + height: self.height, + refresh_millihz: self.refresh_millihz, + } + } +} diff --git a/jay-config/src/_private/client.rs b/jay-config/src/_private/client.rs index bf120466..749ad37f 100644 --- a/jay-config/src/_private/client.rs +++ b/jay-config/src/_private/client.rs @@ -5,7 +5,7 @@ use { _private::{ bincode_ops, ipc::{ClientMessage, InitMessage, Response, ServerMessage}, - logging, Config, ConfigEntry, ConfigEntryGen, VERSION, + logging, Config, ConfigEntry, ConfigEntryGen, WireMode, VERSION, }, exec::Command, input::{acceleration::AccelProfile, capability::Capability, InputDevice, Seat}, @@ -570,6 +570,12 @@ impl Client { } } + pub fn connector_modes(&self, connector: Connector) -> Vec { + let res = self.send_with_response(&ClientMessage::ConnectorModes { connector }); + get_response!(res, Vec::new(), ConnectorModes { modes }); + modes.into_iter().map(WireMode::to_mode).collect() + } + pub fn connector_size(&self, connector: Connector) -> (i32, i32) { let res = self.send_with_response(&ClientMessage::ConnectorSize { connector }); get_response!(res, (0, 0), ConnectorSize { width, height }); diff --git a/jay-config/src/_private/ipc.rs b/jay-config/src/_private/ipc.rs index 779288cb..02e3e65c 100644 --- a/jay-config/src/_private/ipc.rs +++ b/jay-config/src/_private/ipc.rs @@ -7,6 +7,7 @@ use { timer::Timer, video::{connector_type::ConnectorType, Connector, DrmDevice, GfxApi, Transform}, Axis, Direction, PciId, Workspace, + _private::WireMode, }, serde::{Deserialize, Serialize}, std::time::Duration, @@ -352,6 +353,9 @@ pub enum ClientMessage<'a> { SetDoubleClickDistance { dist: i32, }, + ConnectorModes { + connector: Connector, + }, } #[derive(Serialize, Deserialize, Debug)] @@ -454,6 +458,9 @@ pub enum Response { GetWorkspaceCapture { capture: bool, }, + ConnectorModes { + modes: Vec, + }, } #[derive(Serialize, Deserialize, Debug)] diff --git a/jay-config/src/video.rs b/jay-config/src/video.rs index 92c4248e..cb4da383 100644 --- a/jay-config/src/video.rs +++ b/jay-config/src/video.rs @@ -21,7 +21,7 @@ use { /// - width in pixels /// - height in pixels /// - refresh rate in mhz. -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] +#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)] pub struct Mode { pub(crate) width: i32, pub(crate) height: i32, @@ -112,6 +112,14 @@ impl Connector { get!(Mode::zeroed()).connector_mode(self) } + /// Returns the available modes of the connector. + pub fn modes(self) -> Vec { + if !self.exists() { + return Vec::new(); + } + get!(Vec::new()).connector_modes(self) + } + /// Returns the logical width of the connector. /// /// The returned value will be different from `mode().width()` if the scale is not 1. diff --git a/src/compositor.rs b/src/compositor.rs index d65b81c4..dc3681a4 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -375,6 +375,7 @@ fn create_dummy_output(state: &Rc) { async_event: Default::default(), }), 0, + Vec::new(), &backend::Mode { width: 0, height: 0, diff --git a/src/config/handler.rs b/src/config/handler.rs index 168e5786..a1612c2b 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -27,6 +27,7 @@ use { _private::{ bincode_ops, ipc::{ClientMessage, Response, ServerMessage}, + WireMode, }, input::{ acceleration::{AccelProfile, ACCEL_PROFILE_ADAPTIVE, ACCEL_PROFILE_FLAT}, @@ -694,6 +695,24 @@ impl ConfigProxyHandler { Ok(()) } + fn handle_connector_modes(&self, connector: Connector) -> Result<(), CphError> { + let connector = self.get_output(connector)?; + self.respond(Response::ConnectorModes { + modes: connector + .node + .global + .modes + .iter() + .map(|m| WireMode { + width: m.width, + height: m.height, + refresh_millihz: m.refresh_rate_millihz, + }) + .collect(), + }); + Ok(()) + } + fn handle_set_cursor_size(&self, seat: Seat, size: i32) -> Result<(), CphError> { let seat = self.get_seat(seat)?; if size < 0 { @@ -1369,6 +1388,9 @@ impl ConfigProxyHandler { ClientMessage::SetDoubleClickDistance { dist } => { self.handle_set_double_click_distance(dist) } + ClientMessage::ConnectorModes { connector } => self + .handle_connector_modes(connector) + .wrn("connector_modes")?, } Ok(()) } diff --git a/src/ifs/wl_output.rs b/src/ifs/wl_output.rs index dbe8a9e8..d93f3b50 100644 --- a/src/ifs/wl_output.rs +++ b/src/ifs/wl_output.rs @@ -66,6 +66,7 @@ pub struct WlOutputGlobal { pub pos: Cell, pub output_id: Rc, pub mode: Cell, + pub modes: Vec, pub node: CloneCell>>, pub width_mm: i32, pub height_mm: i32, @@ -96,6 +97,7 @@ impl WlOutputGlobal { state: &Rc, connector: &Rc, x1: i32, + modes: Vec, mode: &backend::Mode, manufacturer: &str, product: &str, @@ -122,6 +124,7 @@ impl WlOutputGlobal { pos: Cell::new(Rect::new_sized(x1, 0, width, height).unwrap()), output_id, mode: Cell::new(*mode), + modes, node: Default::default(), width_mm, height_mm, diff --git a/src/tasks/connector.rs b/src/tasks/connector.rs index c0bbc22f..5c1df848 100644 --- a/src/tasks/connector.rs +++ b/src/tasks/connector.rs @@ -94,6 +94,7 @@ impl ConnectorHandler { &self.state, &self.data, x1, + info.modes.clone(), &info.initial_mode, &info.manufacturer, &info.product, From 98b6eba81c40af24f70dc47d8b5a4e583be91bdd Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Mon, 4 Mar 2024 17:18:44 +0100 Subject: [PATCH 2/2] 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 {