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

@ -8,6 +8,7 @@ use crate::{
output_configuration::OutputConfigurationState, workspace::WorkspaceUpdateGuard,
},
};
use anyhow::Context;
use cosmic_config::{ConfigGet, CosmicConfigEntry};
use cosmic_settings_config::window_rules::ApplicationException;
use cosmic_settings_config::{shortcuts, window_rules, Shortcuts};
@ -386,7 +387,7 @@ impl Config {
xdg_activation_state: &XdgActivationState,
startup_done: Arc<AtomicBool>,
clock: &Clock<Monotonic>,
) {
) -> anyhow::Result<()> {
let outputs = output_state.outputs().collect::<Vec<_>>();
let mut infos = outputs
.iter()
@ -471,24 +472,24 @@ impl Config {
found_outputs.push((output.clone(), enabled));
}
if let Err(err) = backend.apply_config_for_outputs(
false,
loop_handle,
self.dynamic_conf.screen_filter(),
shell.clone(),
workspace_state,
xdg_activation_state,
startup_done,
clock,
) {
error!(?err, "Failed to reset config.");
} else {
for (output, enabled) in found_outputs {
if enabled == OutputState::Enabled {
output_state.enable_head(&output);
} else {
output_state.disable_head(&output);
}
backend
.apply_config_for_outputs(
false,
loop_handle,
self.dynamic_conf.screen_filter(),
shell.clone(),
workspace_state,
xdg_activation_state,
startup_done,
clock,
)
.context("Failed to reset config")?;
for (output, enabled) in found_outputs {
if enabled == OutputState::Enabled {
output_state.enable_head(&output);
} else {
output_state.disable_head(&output);
}
}
} else {
@ -529,37 +530,39 @@ impl Config {
w += output.geometry().size.w as u32;
}
if let Err(err) = backend.lock().apply_config_for_outputs(
false,
loop_handle,
self.dynamic_conf.screen_filter(),
shell.clone(),
workspace_state,
xdg_activation_state,
startup_done,
clock,
) {
warn!(?err, "Failed to set new config.",);
} else {
for output in outputs {
if output
.user_data()
.get::<RefCell<OutputConfig>>()
.unwrap()
.borrow()
.enabled
== OutputState::Enabled
{
output_state.enable_head(&output);
} else {
output_state.disable_head(&output);
}
let mut backend = backend.lock();
backend
.apply_config_for_outputs(
false,
loop_handle,
self.dynamic_conf.screen_filter(),
shell.clone(),
workspace_state,
xdg_activation_state,
startup_done.clone(),
clock,
)
.context("Failed to set new config")?;
for output in outputs {
if output
.user_data()
.get::<RefCell<OutputConfig>>()
.unwrap()
.borrow()
.enabled
== OutputState::Enabled
{
output_state.enable_head(&output);
} else {
output_state.disable_head(&output);
}
}
output_state.update();
self.write_outputs(output_state.outputs());
}
Ok(())
}
pub fn write_outputs(