From 6fcdb9242e65ed5825e7fda2a7935b412588b63a Mon Sep 17 00:00:00 2001 From: Ivan Molodetskikh Date: Wed, 19 Nov 2025 20:49:06 +0300 Subject: tty: Avoid modeset on session resume --- src/backend/tty.rs | 57 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 43 insertions(+), 14 deletions(-) (limited to 'src/backend') diff --git a/src/backend/tty.rs b/src/backend/tty.rs index 695cd831..daf2d532 100644 --- a/src/backend/tty.rs +++ b/src/backend/tty.rs @@ -222,7 +222,7 @@ impl OutputDevice { info.name.clone() } - fn cleanup_disconnected_resources( + fn cleanup_mismatching_resources( &self, should_be_off: &dyn Fn(crtc::Handle, &connector::Info) -> bool, ) -> anyhow::Result<()> { @@ -246,7 +246,29 @@ impl OutputDevice { for (conn, info) in self.drm_scanner.connectors() { // We only keep the connector if it has a CRTC and the output isn't off in niri. if let Some(crtc) = self.drm_scanner.crtc_for_connector(conn) { - if !should_be_off(crtc, info) { + // Verify that the connector's current CRTC matches the CRTC we expect. If not, + // clear the CRTC and the connector so that all connectors can get the expected + // CRTCs afterwards. (We do this because we do not handle CRTC rotations across TTY + // switches.) + let mut has_different_crtc = false; + if let Some(enc) = info.current_encoder() { + match self.drm.get_encoder(enc) { + Ok(enc) => { + if let Some(current_crtc) = enc.crtc() { + if current_crtc != crtc { + has_different_crtc = true; + } + } + } + Err(err) => { + debug!("couldn't get encoder: {err:?}"); + // Err on the safe side. + has_different_crtc = true; + } + } + } + + if !has_different_crtc && !should_be_off(crtc, info) { // Keep the corresponding CRTC. cleanup.remove(&crtc); continue; @@ -512,7 +534,7 @@ impl Tty { return; } - self.device_changed(device_id, niri) + self.device_changed(device_id, niri, false) } UdevEvent::Removed { device_id } => { if !self.session.is_active() { @@ -599,7 +621,7 @@ impl Tty { // It hasn't been removed, update its state as usual. let device = self.devices.get_mut(&node).unwrap(); - if let Err(err) = device.drm.activate(true) { + if let Err(err) = device.drm.activate(false) { warn!("error activating DRM device: {err:?}"); } if let Some(lease_state) = &mut device.drm_lease_state { @@ -607,7 +629,7 @@ impl Tty { } // Refresh the connectors. - self.device_changed(node.dev_id(), niri); + self.device_changed(node.dev_id(), niri, true); // Apply pending gamma changes and restore our existing gamma. let device = self.devices.get_mut(&node).unwrap(); @@ -814,12 +836,12 @@ impl Tty { }; assert!(self.devices.insert(node, device).is_none()); - self.device_changed(device_id, niri); + self.device_changed(device_id, niri, true); Ok(()) } - fn device_changed(&mut self, device_id: dev_t, niri: &mut Niri) { + fn device_changed(&mut self, device_id: dev_t, niri: &mut Niri, cleanup: bool) { debug!("device changed: {device_id}"); let Ok(node) = DrmNode::from_dev_id(device_id) else { @@ -851,10 +873,6 @@ impl Tty { return; }; - // If the DrmScanner connectors is empty here, then this will be the very first scan after - // the device had just been added. - let just_created = device.drm_scanner.connectors().is_empty(); - // DrmScanner will preserve any existing connector-CRTC mapping. let scan_result = match device.drm_scanner.scan_connectors(&device.drm) { Ok(x) => x, @@ -939,8 +957,9 @@ impl Tty { device.known_crtcs.insert(crtc, info); } - // If the device was just added, we need to cleanup any disconnected connectors and planes. - if just_created { + // If the device was just added or resumed, we need to cleanup any disconnected connectors + // and planes. + if cleanup { let device = self.devices.get(&node).unwrap(); // Follow the logic in on_output_config_changed(). @@ -962,9 +981,19 @@ impl Tty { config.off || should_disable(&output_name.connector) }; - if let Err(err) = device.cleanup_disconnected_resources(&should_be_off) { + if let Err(err) = device.cleanup_mismatching_resources(&should_be_off) { warn!("error cleaning up connectors: {err:?}"); } + + let device = self.devices.get_mut(&node).unwrap(); + for surface in device.surfaces.values_mut() { + // We aren't force-clearing the CRTCs, so we need to make the surfaces read the + // updated state after a session resume. This also causes a full damage for the + // next redraw. + if let Err(err) = surface.compositor.reset_state() { + warn!("error resetting DrmCompositor state: {err:?}"); + } + } } // This will connect any new connectors if needed, and apply other changes, such as -- cgit