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

@ -1,7 +1,12 @@
use crate::state::{BackendData, Common, State};
use crate::{
state::{BackendData, Common, State},
utils::prelude::OutputExt,
};
use anyhow::{Context, Result};
use calloop::{InsertError, LoopHandle, RegistrationToken};
use cosmic_comp_config::output::comp::OutputState;
use std::collections::HashMap;
use tracing::{error, warn};
use zbus::blocking::{fdo::DBusProxy, Connection};
#[cfg(feature = "systemd")]
@ -24,12 +29,26 @@ pub fn init(evlh: &LoopHandle<'static, State>) -> Result<Vec<RegistrationToken>>
}
_ => Vec::new(),
};
let mut added = Vec::new();
for node in nodes {
if let Err(err) = state.device_changed(node.dev_id()) {
tracing::error!(?err, "Failed to update drm device {}.", node);
match state.device_changed(node.dev_id()) {
Ok(outputs) => added.extend(outputs),
Err(err) => {
tracing::error!(?err, "Failed to update drm device {}.", node)
}
}
}
if let Err(err) = state.refresh_output_config() {
warn!("Unable to load output config: {}", err);
if !added.is_empty() {
for output in added {
output.config_mut().enabled = OutputState::Disabled;
}
if let Err(err) = state.refresh_output_config() {
error!("Unrecoverable config error: {}", err);
}
}
}
state.refresh_output_config();
()
}