Commit cac2b35598c9
tarball3 files changed+112-6
@@ -35,10 +35,11 @@ use smithay::reexports::drm::Device as _;
3535
use smithay::reexports::drm::control::{self, ModeTypeFlags, connector, crtc};3636
use smithay::reexports::input::Libinput;3737
use smithay::reexports::rustix::fs::OFlags;38
+use smithay::reexports::wayland_server::backend::GlobalId;3839
use smithay::utils::{DeviceFd, Logical, Point, Transform};3940
use smithay::wayland::drm_syncobj::{DrmSyncobjState, supports_syncobj_eventfd};4041
use smithay_drm_extras::drm_scanner::{DrmScanEvent, DrmScanner};41
-use std::collections::HashMap;42
+use std::collections::{HashMap, HashSet};4243
use std::path::{Path, PathBuf};4344
use std::time::Duration;4445
@@ -47,6 +48,10 @@ use crate::frame::{CLEAR, FrameCtx};
4748
/// mouse does not override the refresh rate a game is pacing.4849
const CURSOR_FRAME_NS: u64 = 16_666_667;4950
51
+/// How long a disabled wl_output stays bound before it is removed, so52
+/// clients that were mid-bind are not killed by the removal.53
+const GLOBAL_REMOVAL_DELAY: Duration = Duration::from_secs(5);54
+5055
type Allocator = GbmAllocator<DrmDeviceFd>;5156
type Exporter = GbmFramebufferExporter<DrmDeviceFd>;5257
type Manager = DrmOutputManager<Allocator, Exporter, (), DrmDeviceFd>;@@ -64,11 +69,15 @@ pub struct Drm {
6469
nvidia: bool,6570
frame_flags: FrameFlags,6671
surfaces: HashMap<crtc::Handle, Surface>,72
+ /// Connected connectors the scanner has already reported but that are73
+ /// not lit, retried on the next scan.74
+ failed: HashSet<connector::Handle>,6775
pub devices: Devices,6876
}6977
7078
struct Surface {7179
output: Output,80
+ global: GlobalId,7281
id: OutputId,7382
drm: Out,7483
/// A frame is on its way to the screen and its vblank has not landed.@@ -214,6 +223,7 @@ pub fn init(
214223
s.redraw = true;215224
}216225
}226
+ scan(state);217227
state.dirty.redraw = true;218228
}219229
})?;@@ -227,7 +237,7 @@ pub fn init(
227237
&& state228238
.backend229239
.drm()230
- .is_some_and(|d| d.node.dev_id() == device_id)240
+ .is_some_and(|d| d.node.dev_id() == device_id && d.session.is_active())231241
{232242
scan(state);233243
}@@ -249,6 +259,7 @@ pub fn init(
249259
FrameFlags::DEFAULT250260
},251261
surfaces: HashMap::new(),262
+ failed: HashSet::new(),252263
devices: Devices::default(),253264
}));254265
scan(state);@@ -267,21 +278,66 @@ fn scan(state: &mut State) {
267278
return;268279
}269280
};281
+ let retry = std::mem::take(&mut drm.failed);270282
for event in events {271283
match event {272284
DrmScanEvent::Connected {273285
connector,274286
crtc: Some(crtc),275287
} => connect(state, connector, crtc),288
+ DrmScanEvent::Connected {289
+ connector,290
+ crtc: None,291
+ } => {292
+ tracing::warn!(handle = ?connector.handle(), "no free crtc for the connector");293
+ if let Some(drm) = state.backend.drm_mut() {294
+ drm.failed.insert(connector.handle());295
+ }296
+ }276297
DrmScanEvent::Disconnected {277298
crtc: Some(crtc), ..278299
} => disconnect(state, crtc),300
+ DrmScanEvent::Changed {301
+ connector,302
+ crtc: Some(crtc),303
+ } => {304
+ disconnect(state, crtc);305
+ connect(state, connector, crtc);306
+ }279307
_ => {}280308
}281309
}310
+ for handle in retry {311
+ retry_connector(state, handle);312
+ }282313
state.apply_layout();283314
}284315
316
+/// Lights a connector that failed on an earlier scan, which the scanner317
+/// will not report again until it changes.318
+fn retry_connector(state: &mut State, handle: connector::Handle) {319
+ let Some(drm) = state.backend.drm_mut() else {320
+ return;321
+ };322
+ if drm.failed.contains(&handle) {323
+ return;324
+ }325
+ let Some(info) = drm.scanner.connectors().get(&handle) else {326
+ return;327
+ };328
+ if info.state() != connector::State::Connected {329
+ return;330
+ }331
+ let info = info.clone();332
+ let Some(crtc) = drm.scanner.crtc_for_connector(&handle) else {333
+ drm.failed.insert(handle);334
+ return;335
+ };336
+ if !drm.surfaces.contains_key(&crtc) {337
+ connect(state, info, crtc);338
+ }339
+}340
+285341
fn connect(state: &mut State, connector: connector::Info, crtc: crtc::Handle) {286342
let name = format!(287343
"{}-{}",@@ -312,6 +368,9 @@ fn connect(state: &mut State, connector: connector::Info, crtc: crtc::Handle) {
312368
.copied()313369
}) else {314370
tracing::warn!(name, "connector has no modes");371
+ if let Some(drm) = state.backend.drm_mut() {372
+ drm.failed.insert(connector.handle());373
+ }315374
return;316375
};317376
let transform = transform(config.transform);@@ -352,7 +411,6 @@ fn connect(state: &mut State, connector: connector::Info, crtc: crtc::Handle) {
352411
Some(position),353412
);354413
output.set_preferred(wl_mode);355
- output.create_global::<State>(&core.display);356414
let mut planes = drm.manager.device().planes(&crtc).ok();357415
if drm.nvidia358416
&& let Some(p) = planes.as_mut()@@ -372,6 +430,7 @@ fn connect(state: &mut State, connector: connector::Info, crtc: crtc::Handle) {
372430
Ok(out) => out,373431
Err(e) => {374432
tracing::warn!(name, "could not light up the output: {e}");433
+ drm.failed.insert(connector.handle());375434
return;376435
}377436
};@@ -379,6 +438,7 @@ fn connect(state: &mut State, connector: connector::Info, crtc: crtc::Handle) {
379438
out.with_compositor(|c| c.vrr_supported(connector.handle())),380439
Ok(VrrSupport::Supported)381440
);441
+ let global = output.create_global::<State>(&core.display);382442
space.map_output(&output, position);383443
let (w, h) = mode.size();384444
let size = transform.transform_size(@@ -406,6 +466,7 @@ fn connect(state: &mut State, connector: connector::Info, crtc: crtc::Handle) {
406466
crtc,407467
Surface {408468
output,469
+ global,409470
id,410471
drm: out,411472
queued: false,@@ -540,8 +601,27 @@ fn disconnect(state: &mut State, crtc: crtc::Handle) {
540601
drm.handle.remove(token);541602
}542603
tracing::info!(name = surface.output.name(), "output disconnected");604
+ state605
+ .core606
+ .display607
+ .disable_global::<State>(surface.global.clone());608
+ let global = surface.global.clone();609
+ let removal = Timer::from_duration(GLOBAL_REMOVAL_DELAY);610
+ let armed = drm.handle.insert_source(removal, move |_, _, state| {611
+ state.core.display.remove_global::<State>(global.clone());612
+ TimeoutAction::Drop613
+ });614
+ if let Err(e) = armed {615
+ tracing::warn!("could not defer removing the wl_output: {e}");616
+ state617
+ .core618
+ .display619
+ .remove_global::<State>(surface.global.clone());620
+ }543621
state.wm.space.unmap_output(&surface.output);544622
state.wm.layout.remove_output(surface.id);623
+ state.close_layers(&surface.output);624
+ state.services.lock.forget(&surface.output);545625
}546626
547627
impl Drm {@@ -769,9 +849,16 @@ fn draw(state: &mut State, crtc: crtc::Handle, target: u64) {
769849
let wait = (surface.refresh_ns.max(1_000_000) << backoff).min(1_000_000_000);770850
let wait = Duration::from_nanos(wait);771851
let preemptible = surface.failures == 0;772
- arm(&drm.handle, surface, crtc, wait, preemptible, move |state| {773
- schedule(state, crtc);774
- });852
+ arm(853
+ &drm.handle,854
+ surface,855
+ crtc,856
+ wait,857
+ preemptible,858
+ move |state| {859
+ schedule(state, crtc);860
+ },861
+ );775862
}776863
777864
/// Renders every output that has something new and no frame in flight.@@ -114,6 +114,19 @@ impl State {
114114
true115115
}116116
117
+ /// Closes and unmaps every layer surface on an output that was unplugged.118
+ pub fn close_layers(&mut self, output: &Output) {119
+ let mut map = layer_map_for_output(output);120
+ let layers: Vec<LayerSurface> = map.layers().cloned().collect();121
+ for layer in layers {122
+ layer.layer_surface().send_close();123
+ map.unmap_layer(&layer);124
+ if self.wm.layer_focus.as_ref() == Some(layer.wl_surface()) {125
+ self.wm.layer_focus = None;126
+ }127
+ }128
+ }129
+117130
/// Keeps tiled windows out of the space bars reserve.118131
pub fn reserve_layers(&mut self) {119132
let outputs: Vec<OutputId> = self.wm.layout.outputs().map(|(o, _)| o).collect();@@ -39,6 +39,12 @@ impl Lock {
3939
.map(|(_, s)| s)4040
}4141
42
+ /// Drops what belonged to an output that was unplugged.43
+ pub fn forget(&mut self, output: &Output) {44
+ self.surfaces.retain(|(o, _)| o != output);45
+ self.blanked.remove(&output.name());46
+ }47
+4248
/// Records a frame shown on `output`, which counts towards confirming a4349
/// pending lock.4450
pub fn shown(&mut self, output: &Output) {