Skip to content

Commit b83e9f1

Browse files
committed
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).
1 parent cd11170 commit b83e9f1

File tree

8 files changed

+234
-93
lines changed

8 files changed

+234
-93
lines changed

src/backend/kms/device.rs

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -171,9 +171,14 @@ pub fn init_egl(gbm: &GbmDevice<DrmDeviceFd>) -> Result<EGLInternals> {
171171
}
172172

173173
impl State {
174-
pub fn device_added(&mut self, dev: dev_t, path: &Path, dh: &DisplayHandle) -> Result<()> {
174+
pub fn device_added(
175+
&mut self,
176+
dev: dev_t,
177+
path: &Path,
178+
dh: &DisplayHandle,
179+
) -> Result<Vec<Output>> {
175180
if !self.backend.kms().session.is_active() {
176-
return Ok(());
181+
return Ok(Vec::new());
177182
}
178183

179184
if let Some(allowlist) = dev_list_var("COSMIC_DRM_ALLOW_DEVICES") {
@@ -195,7 +200,7 @@ impl State {
195200
"Skipping device {} due to COSMIC_DRM_ALLOW_DEVICE list.",
196201
path.display()
197202
);
198-
return Ok(());
203+
return Ok(Vec::new());
199204
}
200205
}
201206
}
@@ -212,7 +217,7 @@ impl State {
212217
"Skipping device {} due to COSMIC_DRM_BLOCK_DEVICE list.",
213218
path.display()
214219
);
215-
return Ok(());
220+
return Ok(Vec::new());
216221
}
217222
}
218223
}
@@ -384,12 +389,12 @@ impl State {
384389
.add_heads(wl_outputs.iter());
385390

386391
self.backend.kms().refresh_used_devices()?;
387-
Ok(())
392+
Ok(wl_outputs)
388393
}
389394

390-
pub fn device_changed(&mut self, dev: dev_t) -> Result<()> {
395+
pub fn device_changed(&mut self, dev: dev_t) -> Result<Vec<Output>> {
391396
if !self.backend.kms().session.is_active() {
392-
return Ok(());
397+
return Ok(Vec::new());
393398
}
394399

395400
let drm_node = DrmNode::from_dev_id(dev)?;
@@ -475,7 +480,7 @@ impl State {
475480
}
476481

477482
self.backend.kms().refresh_used_devices()?;
478-
Ok(())
483+
Ok(outputs_added)
479484
}
480485

481486
pub fn device_removed(&mut self, dev: dev_t, dh: &DisplayHandle) -> Result<()> {
@@ -548,7 +553,7 @@ impl State {
548553
Ok(())
549554
}
550555

551-
pub fn refresh_output_config(&mut self) {
556+
pub fn refresh_output_config(&mut self) -> Result<()> {
552557
self.common.config.read_outputs(
553558
&mut self.common.output_configuration_state,
554559
&mut self.backend,
@@ -558,8 +563,9 @@ impl State {
558563
&self.common.xdg_activation_state,
559564
self.common.startup_done.clone(),
560565
&self.common.clock,
561-
);
566+
)?;
562567
self.common.refresh();
568+
Ok(())
563569
}
564570
}
565571

src/backend/kms/mod.rs

Lines changed: 61 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -133,16 +133,40 @@ pub fn init_backend(
133133
});
134134

135135
// manually add already present gpus
136+
let mut outputs = Vec::new();
136137
for (dev, path) in udev_dispatcher.as_source_ref().device_list() {
137-
if let Err(err) = state.device_added(dev, path.into(), dh) {
138-
warn!("Failed to add device {}: {:?}", path.display(), err);
138+
match state.device_added(dev, path.into(), dh) {
139+
Ok(added) => outputs.extend(added),
140+
Err(err) => warn!("Failed to add device {}: {:?}", path.display(), err),
139141
}
140142
}
141143

142144
if let Err(err) = state.backend.kms().select_primary_gpu(dh) {
143145
warn!("Failed to determine primary gpu: {}", err);
144146
}
145-
state.refresh_output_config();
147+
148+
if let Err(err) = state.refresh_output_config() {
149+
info!(
150+
?err,
151+
"Couldn't enable all found outputs, trying to disable outputs."
152+
);
153+
if let Some(pos) = outputs
154+
.iter()
155+
.position(|o| o.is_internal())
156+
.or((!outputs.is_empty()).then_some(0))
157+
{
158+
for (i, output) in outputs.iter().enumerate() {
159+
output.config_mut().enabled = if i == pos {
160+
OutputState::Enabled
161+
} else {
162+
OutputState::Disabled
163+
};
164+
}
165+
if let Err(err) = state.refresh_output_config() {
166+
error!("Couldn't enable any output: {}", err);
167+
}
168+
}
169+
}
146170

147171
// start x11
148172
let primary = state.backend.kms().primary_node.read().unwrap().clone();
@@ -281,9 +305,10 @@ fn init_udev(
281305
.with_context(|| format!("Failed to update drm device: {}", device_id)),
282306
UdevEvent::Removed { device_id } => state
283307
.device_removed(device_id, &dh)
284-
.with_context(|| format!("Failed to remove drm device: {}", device_id)),
308+
.with_context(|| format!("Failed to remove drm device: {}", device_id))
309+
.map(|_| Vec::new()),
285310
} {
286-
Ok(()) => {
311+
Ok(added) => {
287312
debug!("Successfully handled udev event.");
288313

289314
{
@@ -297,7 +322,17 @@ fn init_udev(
297322
}
298323
}
299324

300-
state.refresh_output_config();
325+
if let Err(err) = state.refresh_output_config() {
326+
warn!("Unable to load output config: {}", err);
327+
if !added.is_empty() {
328+
for output in added {
329+
output.config_mut().enabled = OutputState::Disabled;
330+
}
331+
if let Err(err) = state.refresh_output_config() {
332+
error!("Unrecoverable config error: {}", err);
333+
}
334+
}
335+
}
301336
}
302337
Err(err) => {
303338
error!(?err, "Error while handling udev event.")
@@ -339,6 +374,7 @@ impl State {
339374
let dispatcher = dispatcher.clone();
340375
loop_handle.insert_idle(move |state| {
341376
// add new devices, update devices now
377+
let mut added = Vec::new();
342378
for (dev, path) in dispatcher.as_source_ref().device_list() {
343379
let drm_node = match DrmNode::from_dev_id(dev) {
344380
Ok(node) => node,
@@ -348,19 +384,33 @@ impl State {
348384
}
349385
};
350386
if state.backend.kms().drm_devices.contains_key(&drm_node) {
351-
if let Err(err) = state.device_changed(dev) {
352-
error!(?err, "Failed to update drm device {}.", path.display(),);
387+
match state.device_changed(dev) {
388+
Ok(outputs) => added.extend(outputs),
389+
Err(err) => {
390+
error!(?err, "Failed to update drm device {}.", path.display(),)
391+
}
353392
}
354393
} else {
355394
let dh = state.common.display_handle.clone();
356-
if let Err(err) = state.device_added(dev, path.into(), &dh) {
357-
error!(?err, "Failed to add drm device {}.", path.display(),);
395+
match state.device_added(dev, path.into(), &dh) {
396+
Ok(outputs) => added.extend(outputs),
397+
Err(err) => error!(?err, "Failed to add drm device {}.", path.display(),),
358398
}
359399
}
360400
}
361401

362402
// update outputs
363-
state.refresh_output_config();
403+
if let Err(err) = state.refresh_output_config() {
404+
warn!("Unable to load output config: {}", err);
405+
if !added.is_empty() {
406+
for output in added {
407+
output.config_mut().enabled = OutputState::Disabled;
408+
}
409+
if let Err(err) = state.refresh_output_config() {
410+
error!("Unrecoverable config error: {}", err);
411+
}
412+
}
413+
}
364414
state.common.refresh();
365415
});
366416
loop_signal.wakeup();

src/backend/winit.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ pub fn init_backend(
227227
.add_heads(std::iter::once(&output));
228228
{
229229
state.common.add_output(&output);
230-
state.common.config.read_outputs(
230+
if let Err(err) = state.common.config.read_outputs(
231231
&mut state.common.output_configuration_state,
232232
&mut state.backend,
233233
&state.common.shell,
@@ -236,7 +236,9 @@ pub fn init_backend(
236236
&state.common.xdg_activation_state,
237237
state.common.startup_done.clone(),
238238
&state.common.clock,
239-
);
239+
) {
240+
error!("Unrecoverable output config error: {}", err);
241+
}
240242
state.common.refresh();
241243
}
242244
state.launch_xwayland(None);

src/backend/x11.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,7 @@ pub fn init_backend(
370370
.add_heads(std::iter::once(&output));
371371
{
372372
state.common.add_output(&output);
373-
state.common.config.read_outputs(
373+
if let Err(err) = state.common.config.read_outputs(
374374
&mut state.common.output_configuration_state,
375375
&mut state.backend,
376376
&state.common.shell,
@@ -379,7 +379,9 @@ pub fn init_backend(
379379
&state.common.xdg_activation_state,
380380
state.common.startup_done.clone(),
381381
&state.common.clock,
382-
);
382+
) {
383+
error!("Unrecoverable output configuration error: {}", err);
384+
}
383385
state.common.refresh();
384386
}
385387
state.launch_xwayland(None);

src/config/mod.rs

Lines changed: 48 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use crate::{
88
output_configuration::OutputConfigurationState, workspace::WorkspaceUpdateGuard,
99
},
1010
};
11+
use anyhow::Context;
1112
use cosmic_config::{ConfigGet, CosmicConfigEntry};
1213
use cosmic_settings_config::window_rules::ApplicationException;
1314
use cosmic_settings_config::{shortcuts, window_rules, Shortcuts};
@@ -386,7 +387,7 @@ impl Config {
386387
xdg_activation_state: &XdgActivationState,
387388
startup_done: Arc<AtomicBool>,
388389
clock: &Clock<Monotonic>,
389-
) {
390+
) -> anyhow::Result<()> {
390391
let outputs = output_state.outputs().collect::<Vec<_>>();
391392
let mut infos = outputs
392393
.iter()
@@ -471,24 +472,24 @@ impl Config {
471472
found_outputs.push((output.clone(), enabled));
472473
}
473474

474-
if let Err(err) = backend.apply_config_for_outputs(
475-
false,
476-
loop_handle,
477-
self.dynamic_conf.screen_filter(),
478-
shell.clone(),
479-
workspace_state,
480-
xdg_activation_state,
481-
startup_done,
482-
clock,
483-
) {
484-
error!(?err, "Failed to reset config.");
485-
} else {
486-
for (output, enabled) in found_outputs {
487-
if enabled == OutputState::Enabled {
488-
output_state.enable_head(&output);
489-
} else {
490-
output_state.disable_head(&output);
491-
}
475+
backend
476+
.apply_config_for_outputs(
477+
false,
478+
loop_handle,
479+
self.dynamic_conf.screen_filter(),
480+
shell.clone(),
481+
workspace_state,
482+
xdg_activation_state,
483+
startup_done,
484+
clock,
485+
)
486+
.context("Failed to reset config")?;
487+
488+
for (output, enabled) in found_outputs {
489+
if enabled == OutputState::Enabled {
490+
output_state.enable_head(&output);
491+
} else {
492+
output_state.disable_head(&output);
492493
}
493494
}
494495
} else {
@@ -529,37 +530,39 @@ impl Config {
529530
w += output.geometry().size.w as u32;
530531
}
531532

532-
if let Err(err) = backend.lock().apply_config_for_outputs(
533-
false,
534-
loop_handle,
535-
self.dynamic_conf.screen_filter(),
536-
shell.clone(),
537-
workspace_state,
538-
xdg_activation_state,
539-
startup_done,
540-
clock,
541-
) {
542-
warn!(?err, "Failed to set new config.",);
543-
} else {
544-
for output in outputs {
545-
if output
546-
.user_data()
547-
.get::<RefCell<OutputConfig>>()
548-
.unwrap()
549-
.borrow()
550-
.enabled
551-
== OutputState::Enabled
552-
{
553-
output_state.enable_head(&output);
554-
} else {
555-
output_state.disable_head(&output);
556-
}
533+
let mut backend = backend.lock();
534+
backend
535+
.apply_config_for_outputs(
536+
false,
537+
loop_handle,
538+
self.dynamic_conf.screen_filter(),
539+
shell.clone(),
540+
workspace_state,
541+
xdg_activation_state,
542+
startup_done.clone(),
543+
clock,
544+
)
545+
.context("Failed to set new config")?;
546+
547+
for output in outputs {
548+
if output
549+
.user_data()
550+
.get::<RefCell<OutputConfig>>()
551+
.unwrap()
552+
.borrow()
553+
.enabled
554+
== OutputState::Enabled
555+
{
556+
output_state.enable_head(&output);
557+
} else {
558+
output_state.disable_head(&output);
557559
}
558560
}
559-
560561
output_state.update();
561562
self.write_outputs(output_state.outputs());
562563
}
564+
565+
Ok(())
563566
}
564567

565568
pub fn write_outputs(

0 commit comments

Comments
 (0)