config: Make read_outputs failable

Previously we ignored when we had no output configuration
**and** failed to apply the automatically created one.

This leads to two problems:
- If this happens on startup, we end up with no outputs being added to the shell and we quit.
- If this happens later, we might end up in an inconsistent state, where the shell thinks we have an output, when it didn't light up for similar reasons.

Thus `read_outputs` is failable and handling that very much depends on
the where is was called from, because `read_outputs` doesn't know what
configuration was active before.

Thus make it failable and provide useful mitigations everywhere
possible:
- Try to enable just one output in case we fail on startup.
- Don't enable any additional outputs, when we fail on hotplug.
- Log the error like previously in any other case (and come up with more
  mitigations, once we understand these cases better).
This commit is contained in:
Victoria Brekenfeld 2025-09-10 18:25:33 +02:00 committed by Victoria Brekenfeld
parent cd1117080c
commit b83e9f1d32
8 changed files with 232 additions and 91 deletions

View file

@ -775,7 +775,7 @@ impl State {
#[cfg(feature = "systemd")]
{
use smithay::backend::session::Session;
use tracing::{debug, error};
use tracing::{debug, error, warn};
let outputs = self.backend.lock().all_outputs();
let is_active = match &self.backend {
@ -793,17 +793,42 @@ impl State {
debug!("Inhibiting lid switch");
self.common.inhibit_lid_fd = Some(fd);
if crate::dbus::logind::lid_closed().unwrap_or(false) {
self.backend.lock().disable_internal_output(
let backend = self.backend.lock();
let output = backend
.all_outputs()
.iter()
.find(|o| o.is_internal())
.cloned();
let closed = crate::dbus::logind::lid_closed().unwrap_or(false);
if closed {
backend.disable_internal_output(
&mut self.common.output_configuration_state,
);
} else {
self.backend.lock().enable_internal_output(
backend.enable_internal_output(
&mut self.common.output_configuration_state,
);
}
std::mem::drop(backend);
self.refresh_output_config();
if let Err(err) = self.refresh_output_config() {
if !closed {
warn!(?err, "Failed to re-enable internal connector");
if let Some(output) = output {
output.config_mut().enabled = OutputState::Disabled;
if let Err(err) = self.refresh_output_config() {
error!(
"Unrecoverable output configuration error: {}",
err
);
}
}
} else {
// Disabling an output should never fail.
error!("Unrecoverable output configuration error: {}", err);
}
}
}
Err(err) => {
error!("Failed to inhibit lid switch: {}", err);
@ -814,11 +839,24 @@ impl State {
if let Some(_fd) = self.common.inhibit_lid_fd.take() {
debug!("Removing inhibitor-lock on lid switch");
self.backend
.lock()
.enable_internal_output(&mut self.common.output_configuration_state);
let backend = self.backend.lock();
let output = backend
.all_outputs()
.iter()
.find(|o| o.is_internal())
.cloned();
backend.enable_internal_output(&mut self.common.output_configuration_state);
std::mem::drop(backend);
self.refresh_output_config();
if let Err(err) = self.refresh_output_config() {
warn!(?err, "Failed to re-enable internal connector");
if let Some(output) = output {
output.config_mut().enabled = OutputState::Disabled;
if let Err(err) = self.refresh_output_config() {
error!("Unrecoverable output configuration error: {}", err);
}
}
}
// drop _fd
}
}