From 1b51ba83b441f1a2bc629943ded7f6fdf0ad8fb5 Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Thu, 22 Oct 2026 01:27:27 +0100 Subject: [PATCH] Explain runtime invariants and avoid redundant unwraps --- compositor/src/activities/publication.rs | 5 +- compositor/src/backends/direct.rs | 147 ++++++++++++++---- compositor/src/backends/direct/capture.rs | 6 +- compositor/src/backends/direct/lid.rs | 58 +++++-- compositor/src/backends/direct/redraw.rs | 9 +- .../src/backends/direct/session_device.rs | 12 +- compositor/src/backends/direct/topology.rs | 100 +++++++++--- compositor/src/backends/direct/transaction.rs | 67 +++++--- compositor/src/config.rs | 7 +- compositor/src/cursor.rs | 44 ++++-- compositor/src/dmabuf_imports.rs | 5 +- compositor/src/effects.rs | 137 ++++++++++++---- compositor/src/floating.rs | 23 ++- compositor/src/handlers/layer_shell.rs | 4 +- compositor/src/handlers/screencopy.rs | 17 +- compositor/src/handlers/window_capture.rs | 46 ++++-- compositor/src/handlers/xdg_shell.rs | 4 +- compositor/src/idle_inhibition.rs | 2 +- compositor/src/idle_inhibition/media/bus.rs | 14 +- compositor/src/input.rs | 19 ++- compositor/src/input_capture.rs | 29 +++- compositor/src/ipc.rs | 7 +- compositor/src/ipc_events.rs | 11 +- compositor/src/monitor_identity.rs | 44 +++--- compositor/src/overview.rs | 29 +++- compositor/src/portal_session.rs | 5 +- compositor/src/render/backdrop.rs | 9 +- compositor/src/render/capture.rs | 12 +- compositor/src/render/materials.rs | 2 +- compositor/src/render/mod.rs | 11 +- compositor/src/render/scene.rs | 9 +- compositor/src/resume.rs | 20 ++- compositor/src/session_lock.rs | 16 +- compositor/src/state/animation.rs | 14 +- compositor/src/state/capture_privacy.rs | 4 +- compositor/src/state/floating.rs | 5 +- compositor/src/state/layout.rs | 6 +- compositor/src/state/lifecycle.rs | 9 +- compositor/src/state/navigation.rs | 11 +- .../src/state/presentation_dependencies.rs | 20 ++- compositor/src/state/reconciliation.rs | 17 +- compositor/src/state/window_registry.rs | 14 +- compositor/src/state/windows.rs | 8 +- compositor/src/theme.rs | 32 +++- compositor/src/wallpaper.rs | 10 +- compositor/src/wallpaper/decoder.rs | 31 +++- compositor/src/winit.rs | 2 +- crates/ferese-animation/src/gesture.rs | 2 +- crates/ferese-config/src/activities.rs | 9 +- crates/ferese-config/src/document.rs | 18 ++- crates/ferese-config/src/families.rs | 6 +- crates/ferese-config/src/theme.rs | 76 ++++++--- crates/ferese-core/src/output.rs | 17 +- crates/ferese-core/src/workspace.rs | 5 +- crates/ferese-core/src/workspace_policy.rs | 5 +- crates/ferese-layout/src/scrolling.rs | 10 +- crates/ferese-panel/src/motion.rs | 6 +- crates/ferese-panel/src/outline.rs | 12 +- crates/ferese-panel/src/panel/layout.rs | 43 +++-- crates/ferese-theme-model/src/color.rs | 111 ++++++++----- crates/ferese-theme/src/gallery.rs | 19 ++- crates/ferese-theme/src/icons.rs | 11 +- crates/ferese-theme/src/palette.rs | 14 +- crates/ferese-theme/src/typography.rs | 5 +- feresectl/src/activities.rs | 4 +- feresectl/src/activities/output.rs | 4 +- feresectl/src/activities/progress.rs | 16 +- feresectl/src/activities/setup.rs | 22 ++- feresectl/src/main.rs | 2 +- portal/src/backend.rs | 27 +++- portal/src/capture.rs | 5 +- portal/src/consent.rs | 6 +- portal/src/desktop.rs | 10 +- portal/src/inhibit.rs | 8 +- portal/src/input_capture.rs | 14 +- portal/src/lockdown.rs | 7 +- portal/src/permissions.rs | 3 +- portal/src/picker.rs | 12 +- portal/src/recorder.rs | 3 +- portal/src/settings.rs | 7 +- portal/src/shortcuts.rs | 26 +++- portal/src/stream.rs | 27 ++-- shell/ferese-lock/src/runtime.rs | 5 +- shell/ferese-polkit-agent/src/main.rs | 17 +- shell/ferese-polkit-agent/src/prompt.rs | 17 +- shell/ferese-polkit-agent/src/session.rs | 4 +- .../ferese-settings/src/connections/agent.rs | 81 ++++++++-- .../src/connections/bluetooth.rs | 28 +++- .../src/connections/bluetooth_ui.rs | 4 +- .../src/connections/controller.rs | 7 +- shell/ferese-settings/src/connections/mod.rs | 7 +- .../src/connections/wifi_ui.rs | 4 +- shell/ferese-settings/src/main.rs | 13 +- shell/ferese-settings/src/panel_controls.rs | 9 +- shell/ferese-settings/src/panel_drag.rs | 10 +- shell/ferese-settings/src/panel_edit.rs | 26 +++- shell/ferese-settings/src/panel_inspector.rs | 13 +- .../ferese-settings/src/wallpaper_controls.rs | 4 +- shell/ferese-shell/src/activities.rs | 6 +- shell/ferese-shell/src/activities/compact.rs | 6 +- shell/ferese-shell/src/config.rs | 7 +- shell/ferese-shell/src/display_mode.rs | 2 +- shell/ferese-shell/src/launcher/mod.rs | 5 +- shell/ferese-shell/src/launcher/view.rs | 4 +- shell/ferese-shell/src/media/art.rs | 29 +++- shell/ferese-shell/src/motion.rs | 4 +- shell/ferese-shell/src/notification_ui.rs | 9 +- shell/ferese-shell/src/notifications.rs | 14 +- shell/ferese-shell/src/panel_hub.rs | 4 +- shell/ferese-shell/src/status.rs | 47 ++++-- .../src/status/audio_cache/signals.rs | 42 ++++- shell/ferese-shell/src/status_ui/lifecycle.rs | 8 +- shell/ferese-shell/src/status_ui/mod.rs | 9 +- shell/ferese-shell/src/surfaces.rs | 10 +- shell/ferese-shell/src/system_modal.rs | 10 +- shell/ferese-shell/src/workspace_ui.rs | 8 +- 116 files changed, 1643 insertions(+), 515 deletions(-) diff --git a/compositor/src/activities/publication.rs b/compositor/src/activities/publication.rs index b8ef0ab..ad29eb7 100644 --- a/compositor/src/activities/publication.rs +++ b/compositor/src/activities/publication.rs @@ -30,7 +30,10 @@ impl Publisher { serde_json::json!(job.snapshot.as_ref()), )); } - let value = &cached.as_ref().unwrap().1; + let value = &cached + .as_ref() + .expect("activity serialization populated the cache for this revision") + .1; // Lock acquisition revokes queued/in-flight serialization. // Hold the guard through delivery to order sends before revocation. deliver(job.epoch, &worker_epoch, job.recipients, value); diff --git a/compositor/src/backends/direct.rs b/compositor/src/backends/direct.rs index 6de252b..f35f0e1 100644 --- a/compositor/src/backends/direct.rs +++ b/compositor/src/backends/direct.rs @@ -387,7 +387,7 @@ pub fn init(event_loop: &mut EventLoop, state: &mut Ferese) -> Result<() state .direct_backend .as_mut() - .unwrap() + .expect("DRM callbacks require an installed direct backend") .topology .observe_devices(&[( primary_path.clone(), @@ -640,25 +640,43 @@ pub(crate) fn set_output_profile(state: &mut Ferese, name: &str) -> Result<(), S return Err("profile does not match the connected monitors/lid".into()); } if let Err(error) = validate_live_outputs(state, &state.output_profiles) { - state.direct_backend.as_mut().unwrap().manual_outputs = previous; + state + .direct_backend + .as_mut() + .expect("DRM callbacks require an installed direct backend") + .manual_outputs = previous; return Err(error); } reconcile_outputs(state, false); - if let Some(error) = state.direct_backend.as_ref().unwrap().output_error.clone() { - state.direct_backend.as_mut().unwrap().manual_outputs = previous; + if let Some(error) = state + .direct_backend + .as_ref() + .expect("DRM callbacks require an installed direct backend") + .output_error + .clone() + { + state + .direct_backend + .as_mut() + .expect("DRM callbacks require an installed direct backend") + .manual_outputs = previous; return Err(error); } if name != "auto" && state .direct_backend .as_ref() - .unwrap() + .expect("DRM callbacks require an installed direct backend") .desired_outputs .profile .as_deref() != Some(name) { - state.direct_backend.as_mut().unwrap().manual_outputs = previous; + state + .direct_backend + .as_mut() + .expect("DRM callbacks require an installed direct backend") + .manual_outputs = previous; return Err("profile does not match the connected monitors/lid".into()); } arm_output_confirmation(state, known_good, previous); @@ -698,12 +716,26 @@ pub(crate) fn set_output_layout( backend.manual_outputs.internal = None; backend.manual_outputs.layout = Some(layout); if let Err(error) = validate_live_outputs(state, &state.output_profiles) { - state.direct_backend.as_mut().unwrap().manual_outputs = previous; + state + .direct_backend + .as_mut() + .expect("DRM callbacks require an installed direct backend") + .manual_outputs = previous; return Err(error); } reconcile_outputs(state, false); - if let Some(error) = state.direct_backend.as_ref().unwrap().output_error.clone() { - state.direct_backend.as_mut().unwrap().manual_outputs = previous; + if let Some(error) = state + .direct_backend + .as_ref() + .expect("DRM callbacks require an installed direct backend") + .output_error + .clone() + { + state + .direct_backend + .as_mut() + .expect("DRM callbacks require an installed direct backend") + .manual_outputs = previous; return Err(error); } arm_output_confirmation(state, known_good, previous); @@ -732,8 +764,18 @@ pub(crate) fn set_internal_output(state: &mut Ferese, enabled: bool) -> Result<( backend.manual_outputs.internal = Some(enabled); backend.manual_outputs.layout = None; reconcile_outputs(state, false); - if let Some(error) = state.direct_backend.as_ref().unwrap().output_error.clone() { - state.direct_backend.as_mut().unwrap().manual_outputs = previous; + if let Some(error) = state + .direct_backend + .as_ref() + .expect("DRM callbacks require an installed direct backend") + .output_error + .clone() + { + state + .direct_backend + .as_mut() + .expect("DRM callbacks require an installed direct backend") + .manual_outputs = previous; return Err(error); } arm_output_confirmation(state, known_good, previous); @@ -750,7 +792,10 @@ fn arm_output_confirmation( previous: DesiredOutputConfiguration, manual: ManualOverride, ) { - let backend = state.direct_backend.as_mut().unwrap(); + let backend = state + .direct_backend + .as_mut() + .expect("DRM callbacks require an installed direct backend"); if let Some(token) = backend.confirmation_timer.take() { state.loop_handle.remove(token); } @@ -781,7 +826,13 @@ fn arm_output_confirmation( TimeoutAction::Drop }, ) { - Ok(token) => state.direct_backend.as_mut().unwrap().confirmation_timer = Some(token), + Ok(token) => { + state + .direct_backend + .as_mut() + .expect("DRM callbacks require an installed direct backend") + .confirmation_timer = Some(token) + } Err(error) => { tracing::warn!(%error, "could not arm monitor confirmation; reverting"); let _ = confirm_output_configuration(state, false); @@ -809,7 +860,13 @@ pub(crate) fn confirm_output_configuration( )); backend.manual_outputs = manual; reconcile_outputs(state, false); - if let Some(error) = state.direct_backend.as_ref().unwrap().output_error.clone() { + if let Some(error) = state + .direct_backend + .as_ref() + .expect("DRM callbacks require an installed direct backend") + .output_error + .clone() + { return Err(error); } } @@ -1256,7 +1313,7 @@ fn redraw( let outputs = state .direct_backend .as_ref() - .unwrap() + .expect("DRM callbacks require an installed direct backend") .devices .iter() .flat_map(|(node, device)| { @@ -1274,7 +1331,7 @@ fn redraw( let scene = state .direct_backend .as_ref() - .unwrap() + .expect("DRM callbacks require an installed direct backend") .scene_output(state, output); selected.is_none_or(|selected| selected.contains(output) || selected.contains(&scene)) || state.output_has_animations(&scene) @@ -1293,7 +1350,7 @@ fn redraw( state .direct_backend .as_mut() - .unwrap() + .expect("DRM callbacks require an installed direct backend") .scheduling_metrics .redraw(source, selected.is_none(), requested, outputs.len()); arm_animation_timer(state); @@ -1517,7 +1574,11 @@ fn render_output( let damage = damage.map_or_else( || { vec![smithay::utils::Rectangle::from_size( - output.output.current_mode().unwrap().size, + output + .output + .current_mode() + .expect("a rendered output must have a current mode") + .size, )] }, |damage| { @@ -1641,7 +1702,13 @@ fn render_output( restore_device(state, node, device); if !submitted && scene_output - == state.direct_backend.as_ref().unwrap().devices[&node].outputs[&crtc].output + == state + .direct_backend + .as_ref() + .expect("DRM callbacks require an installed direct backend") + .devices[&node] + .outputs[&crtc] + .output { schedule_frame_callbacks(state, node, crtc, plan.present_at); } @@ -1687,7 +1754,11 @@ fn rescan_device(state: &mut Ferese, node: DrmNode) -> Result { .and_then(|scan| transaction::apply_device(state, node, &mut device, scan)); if let Err(error) = &result { tracing::warn!(?node, %error, "output transaction failed; restored known-good outputs where possible"); - state.direct_backend.as_mut().unwrap().output_error = Some(error.clone()); + state + .direct_backend + .as_mut() + .expect("DRM callbacks require an installed direct backend") + .output_error = Some(error.clone()); transaction::update_applied_info(&mut device); } restore_device(state, node, device); @@ -1695,7 +1766,10 @@ fn rescan_device(state: &mut Ferese, node: DrmNode) -> Result { } fn refresh_connected_info(state: &mut Ferese) { - let backend = state.direct_backend.as_mut().unwrap(); + let backend = state + .direct_backend + .as_mut() + .expect("DRM callbacks require an installed direct backend"); for device in backend.devices.values_mut() { if let Ok(scan) = select_outputs( device, @@ -1739,7 +1813,10 @@ fn refresh_connected_info(state: &mut Ferese) { } fn remember_applied_configuration(state: &mut Ferese) { - let backend = state.direct_backend.as_mut().unwrap(); + let backend = state + .direct_backend + .as_mut() + .expect("DRM callbacks require an installed direct backend"); if backend.topology.reconciling { // Applied positions are read only after the coherent logical publish. backend.topology.remember_applied = true; @@ -1913,17 +1990,20 @@ fn animation_fallbacks(state: &mut Ferese) -> Vec<(DrmNode, crtc::Handle, Instan let scene = state .direct_backend .as_ref() - .unwrap() + .expect("DRM callbacks require an installed direct backend") .scene_output(state, &identity); let animating = enabled && state.output_has_animations(&scene); - let backend = state.direct_backend.as_mut().unwrap(); + let backend = state + .direct_backend + .as_mut() + .expect("DRM callbacks require an installed direct backend"); let output = backend .devices .get_mut(&node) - .unwrap() + .expect("the selected DRM device remains registered during this operation") .outputs .get_mut(&crtc) - .unwrap(); + .expect("the selected DRM device remains registered during this operation"); // Preserve recovery of a requested final frame if another output // advances this animation to rest before the watchdog is dispatched. let active = enabled @@ -2032,19 +2112,21 @@ fn arm_animation_timer(state: &mut Ferese) { state .direct_backend .as_mut() - .unwrap() + .expect("DRM callbacks require an installed direct backend") .scheduling_metrics .fallback_recoveries += 1; state .direct_backend .as_mut() - .unwrap() + .expect("DRM callbacks require an installed direct backend") .devices .get_mut(&node) - .unwrap() + .expect( + "the selected DRM device must remain registered during this operation", + ) .outputs .get_mut(&crtc) - .unwrap() + .expect("the selected CRTC must remain registered during this operation") .animation_fallback = AnimationFallback::default(); request_frame(state, node, crtc); } @@ -2054,7 +2136,10 @@ fn arm_animation_timer(state: &mut Ferese) { TimeoutAction::Drop }) { Ok(token) => { - let backend = state.direct_backend.as_mut().unwrap(); + let backend = state + .direct_backend + .as_mut() + .expect("DRM callbacks require an installed direct backend"); backend.animation_timer = Some(token); backend.animation_deadline = Some(deadline); backend.scheduling_metrics.fallback_arms += 1; diff --git a/compositor/src/backends/direct/capture.rs b/compositor/src/backends/direct/capture.rs index 3ae7137..170a983 100644 --- a/compositor/src/backends/direct/capture.rs +++ b/compositor/src/backends/direct/capture.rs @@ -102,7 +102,11 @@ pub(super) fn capture_mirror( continue; }; let element = super::mirror::fitted_texture( - output.mirror_capture_texture.as_ref().unwrap().clone(), + output + .mirror_capture_texture + .as_ref() + .expect("successful mirror composition populated its capture texture") + .clone(), &output.output, smithay::backend::renderer::element::Id::new(), Default::default(), diff --git a/compositor/src/backends/direct/lid.rs b/compositor/src/backends/direct/lid.rs index da96e71..baea0c5 100644 --- a/compositor/src/backends/direct/lid.rs +++ b/compositor/src/backends/direct/lid.rs @@ -148,7 +148,11 @@ fn read_acpi() -> Option { impl Reader { pub fn set_active(&mut self, active: bool) { - let generation = self.lease.lock().unwrap().activate(active); + let generation = self + .lease + .lock() + .expect("lid policy lease must remain consistent across workers") + .activate(active); self.suspend_requested = false; if active { let _ = self.suspend.send(PolicyRequest::Activate(generation)); @@ -167,7 +171,9 @@ impl Reader { let Ok(connection) = connection else { return }; while let Ok(request) = suspend_requests.recv() { if let PolicyRequest::Activate(generation) = request { - let current = worker_lease.lock().unwrap(); + let current = worker_lease + .lock() + .expect("lid policy lease must remain consistent across workers"); if !current.active || current.generation != generation { continue; } @@ -189,14 +195,17 @@ impl Reader { reply.body().deserialize::() }); match inhibitor { - Ok(descriptor) => { - worker_lease.lock().unwrap().accept(generation, descriptor) - } + Ok(descriptor) => worker_lease + .lock() + .expect("lid policy lease must remain consistent across workers") + .accept(generation, descriptor), Err(error) => tracing::warn!(%error, "could not own logind lid policy"), } continue; } - let current = worker_lease.lock().unwrap(); + let current = worker_lease + .lock() + .expect("lid policy lease must remain consistent across workers"); if !current.active || !current.suspend { continue; } @@ -221,7 +230,9 @@ impl Reader { tracing::debug!("lid suspend prevented by sleep inhibitor"); continue; } - let current = worker_lease.lock().unwrap(); + let current = worker_lease + .lock() + .expect("lid policy lease must remain consistent across workers"); let requested = current.active && current.suspend; drop(current); if !requested { @@ -237,7 +248,10 @@ impl Reader { tracing::warn!(%error, "logind rejected lid suspend"); } } - worker_lease.lock().unwrap().activate(false); + worker_lease + .lock() + .expect("lid policy lease must remain consistent across workers") + .activate(false); })?; let (requests, receiver) = mpsc::channel::(); let (replies, events) = smithay::reexports::calloop::channel::channel(); @@ -306,7 +320,12 @@ fn request(state: &mut Ferese, reactivate: bool, wake: bool) { } } backend.lid_reader.suspend_requested = false; - backend.lid_reader.lease.lock().unwrap().suspend = false; + backend + .lid_reader + .lease + .lock() + .expect("lid policy lease must remain consistent across workers") + .suspend = false; let probe = backend.lid.begin(reactivate, wake); let sent = backend.lid_reader.requests.send(probe).is_ok(); let timer = state.loop_handle.insert_source( @@ -322,7 +341,12 @@ fn request(state: &mut Ferese, reactivate: bool, wake: bool) { ); if sent && let Ok(token) = timer { - state.direct_backend.as_mut().unwrap().lid_reader.deadline = Some(token); + state + .direct_backend + .as_mut() + .expect("DRM callbacks require an installed direct backend") + .lid_reader + .deadline = Some(token); } else { if let Ok(token) = timer { state.loop_handle.remove(token); @@ -352,7 +376,12 @@ fn complete_refresh(state: &mut Ferese, probe: Probe, value: Option) { backend.topology.reconciling = true; state.reset_animation_clock(); state.lock_input_activity(); - state.direct_backend.as_mut().unwrap().topology.reconciling = false; + state + .direct_backend + .as_mut() + .expect("DRM callbacks require an installed direct backend") + .topology + .reconciling = false; } reconcile_outputs(state, reactivate); @@ -364,7 +393,12 @@ pub(super) fn apply_suspend_policy(state: &mut Ferese) { return; }; let requested = backend.active && backend.desired_outputs.suspend && backend.lid.closed(); - backend.lid_reader.lease.lock().unwrap().suspend = requested; + backend + .lid_reader + .lease + .lock() + .expect("lid policy lease must remain consistent across workers") + .suspend = requested; if backend.lid_reader.suspend_requested != requested { backend.lid_reader.suspend_requested = requested; let _ = backend.lid_reader.suspend.send(PolicyRequest::Suspend); diff --git a/compositor/src/backends/direct/redraw.rs b/compositor/src/backends/direct/redraw.rs index 96ba9b1..0f1d285 100644 --- a/compositor/src/backends/direct/redraw.rs +++ b/compositor/src/backends/direct/redraw.rs @@ -131,7 +131,14 @@ pub(super) fn cursor_on_output(state: &crate::Ferese, output: &smithay::output:: states .data_map .get::() - .map(|attributes| attributes.lock().unwrap().hotspot) + .map(|attributes| { + attributes + .lock() + .expect( + "cursor surface attributes must remain consistent across callbacks", + ) + .hotspot + }) .unwrap_or_default() }); smithay::desktop::utils::bbox_from_surface_tree( diff --git a/compositor/src/backends/direct/session_device.rs b/compositor/src/backends/direct/session_device.rs index 99c6162..51c113a 100644 --- a/compositor/src/backends/direct/session_device.rs +++ b/compositor/src/backends/direct/session_device.rs @@ -23,13 +23,21 @@ impl SessionDevice { } pub fn device_fd(&self) -> io::Result { - self.fd.as_ref().unwrap().try_clone().map(DeviceFd::from) + self.fd + .as_ref() + .expect("the session device retains its descriptor until Drop") + .try_clone() + .map(DeviceFd::from) } } impl Drop for SessionDevice { fn drop(&mut self) { - if let Err(error) = self.session.close(self.fd.take().unwrap()) { + if let Err(error) = self.session.close( + self.fd + .take() + .expect("the session device retains its descriptor until Drop"), + ) { tracing::warn!( ?error, "could not release DRM device through the session manager" diff --git a/compositor/src/backends/direct/topology.rs b/compositor/src/backends/direct/topology.rs index f646675..ec70f55 100644 --- a/compositor/src/backends/direct/topology.rs +++ b/compositor/src/backends/direct/topology.rs @@ -176,11 +176,11 @@ fn slot_mut<'a>(state: &'a mut Ferese, path: &Path) -> &'a mut DeviceState { state .direct_backend .as_mut() - .unwrap() + .expect("DRM callbacks require an installed direct backend") .topology .devices .get_mut(path) - .unwrap() + .expect("the discovery path must have a topology slot") } /// The only entry point for applying the current output configuration to DRM. @@ -250,7 +250,12 @@ pub(super) fn reconcile_outputs(state: &mut Ferese, reactivate: bool) { } for path in paths { - let slot = &state.direct_backend.as_ref().unwrap().topology.devices[&path]; + let slot = &state + .direct_backend + .as_ref() + .expect("DRM callbacks require an installed direct backend") + .topology + .devices[&path]; let current_path = observed .as_ref() .ok() @@ -261,7 +266,7 @@ pub(super) fn reconcile_outputs(state: &mut Ferese, reactivate: bool) { state .direct_backend .as_ref() - .unwrap() + .expect("DRM callbacks require an installed direct backend") .devices .contains_key(node) }); @@ -291,7 +296,10 @@ pub(super) fn reconcile_outputs(state: &mut Ferese, reactivate: bool) { } let node = match existing { Some(node) => node, - None => match open_device(state, current_path.unwrap()) { + None => match open_device( + state, + current_path.expect("device discovery found a path before attempting to open it"), + ) { Ok(node) => node, Err(error) => { tracing::warn!(%error, ?path, device_path = ?current_path, "DRM device unavailable; will retry"); @@ -306,10 +314,10 @@ pub(super) fn reconcile_outputs(state: &mut Ferese, reactivate: bool) { let device = state .direct_backend .as_mut() - .unwrap() + .expect("DRM callbacks require an installed direct backend") .devices .get_mut(&node) - .unwrap(); + .expect("the selected DRM device must remain registered during this operation"); device.drm.activate(true).map(|()| { for (crtc, output) in &mut device.outputs { cancel_output_timers(&state.loop_handle, output); @@ -325,7 +333,7 @@ pub(super) fn reconcile_outputs(state: &mut Ferese, reactivate: bool) { state .desktop_transition .as_mut() - .unwrap() + .expect("desktop transition must be started before publication or retirement") .redraw .insert(output.output.clone()); @@ -350,13 +358,13 @@ pub(super) fn reconcile_outputs(state: &mut Ferese, reactivate: bool) { let mut output = state .direct_backend .as_mut() - .unwrap() + .expect("DRM callbacks require an installed direct backend") .devices .get_mut(&node) - .unwrap() + .expect("the selected DRM device must remain registered during this operation") .outputs .remove(&crtc) - .unwrap(); + .expect("the selected CRTC must remain registered during this operation"); cancel_output_timers(&state.loop_handle, &mut output); if let Some(global) = output.global.take() { state.display_handle.disable_global::(global); @@ -365,7 +373,7 @@ pub(super) fn reconcile_outputs(state: &mut Ferese, reactivate: bool) { state .direct_backend .as_mut() - .unwrap() + .expect("DRM callbacks require an installed direct backend") .presentation .remove(&(node, crtc)); } @@ -376,7 +384,7 @@ pub(super) fn reconcile_outputs(state: &mut Ferese, reactivate: bool) { let known_good = state .direct_backend .as_ref() - .unwrap() + .expect("DRM callbacks require an installed direct backend") .applied_outputs .clone(); if let Err(error) = refresh_monitor_policy(state).and_then(|()| { @@ -384,14 +392,21 @@ pub(super) fn reconcile_outputs(state: &mut Ferese, reactivate: bool) { validate_desired_outputs(state) }) { tracing::warn!(%error, "monitor policy validation failed; retaining applied outputs"); - state.direct_backend.as_mut().unwrap().output_error = Some(error); + state + .direct_backend + .as_mut() + .expect("DRM callbacks require an installed direct backend") + .output_error = Some(error); for (path, node) in &ready { slot_mut(state, path).degraded(*node, Instant::now()); } } else { // Activate destinations before disabling former owners, including across GPUs. ready.sort_by_key(|(_, node)| { - let backend = state.direct_backend.as_ref().unwrap(); + let backend = state + .direct_backend + .as_ref() + .expect("DRM callbacks require an installed direct backend"); let prefix = format!("{}/", drm_device_key(&backend.devices[node].drm)); let desired = backend .desired_outputs @@ -416,14 +431,21 @@ pub(super) fn reconcile_outputs(state: &mut Ferese, reactivate: bool) { // Device boundaries are not cross-GPU atomic. Compensate already // applied devices using their last actual hardware settings. if !known_good.outputs.is_empty() { - let backend = state.direct_backend.as_mut().unwrap(); + let backend = state + .direct_backend + .as_mut() + .expect("DRM callbacks require an installed direct backend"); let requested = backend.desired_outputs.clone(); backend.desired_outputs = crate::output_policy::restore_configuration(&known_good, &backend.monitors); for previous in succeeded { let _ = rescan_device(state, previous); } - state.direct_backend.as_mut().unwrap().desired_outputs = requested; + state + .direct_backend + .as_mut() + .expect("DRM callbacks require an installed direct backend") + .desired_outputs = requested; } break; } @@ -431,7 +453,7 @@ pub(super) fn reconcile_outputs(state: &mut Ferese, reactivate: bool) { if state .direct_backend .as_ref() - .unwrap() + .expect("DRM callbacks require an installed direct backend") .output_error .is_none() { @@ -441,14 +463,24 @@ pub(super) fn reconcile_outputs(state: &mut Ferese, reactivate: bool) { transaction::recover_last_output(state); let mut desktop_changes = publish_runtime_desktop(state); - if std::mem::take(&mut state.direct_backend.as_mut().unwrap().topology.layout_dirty) { + if std::mem::take( + &mut state + .direct_backend + .as_mut() + .expect("DRM callbacks require an installed direct backend") + .topology + .layout_dirty, + ) { desktop_changes.layout = true; desktop_changes.shell = true; desktop_changes .outputs .extend(state.space.outputs().cloned()); } - let backend = state.direct_backend.as_mut().unwrap(); + let backend = state + .direct_backend + .as_mut() + .expect("DRM callbacks require an installed direct backend"); backend.topology.reconciling = false; let retry = backend.topology.retry_deadline(backend.active); if std::mem::take(&mut backend.topology.remember_applied) { @@ -465,7 +497,14 @@ pub(super) fn reconcile_outputs(state: &mut Ferese, reactivate: bool) { reconcile_outputs(state, false); TimeoutAction::Drop }) { - Ok(token) => state.direct_backend.as_mut().unwrap().topology.retry_timer = Some(token), + Ok(token) => { + state + .direct_backend + .as_mut() + .expect("DRM callbacks require an installed direct backend") + .topology + .retry_timer = Some(token) + } Err(error) => tracing::warn!(%error, "could not arm DRM recovery deadline"), } } @@ -481,7 +520,7 @@ fn publish_runtime_desktop(state: &mut Ferese) -> crate::state::DesktopChanges { let mut outputs = state .direct_backend .as_ref() - .unwrap() + .expect("DRM callbacks require an installed direct backend") .devices .values() .flat_map(|device| device.outputs.values()) @@ -525,7 +564,13 @@ fn publish_runtime_desktop(state: &mut Ferese) -> crate::state::DesktopChanges { let changes = state .publish_desktop(runtime) .expect("validated usable output inventory"); - for device in state.direct_backend.as_mut().unwrap().devices.values_mut() { + for device in state + .direct_backend + .as_mut() + .expect("DRM callbacks require an installed direct backend") + .devices + .values_mut() + { for output in device.outputs.values_mut() { if output.mirror_source.is_none() { if output.global.is_none() { @@ -579,7 +624,14 @@ pub(super) fn hotplug(state: &mut Ferese) { reconcile_outputs(state, false); TimeoutAction::Drop }) { - Ok(token) => state.direct_backend.as_mut().unwrap().topology.settle_timer = Some(token), + Ok(token) => { + state + .direct_backend + .as_mut() + .expect("DRM callbacks require an installed direct backend") + .topology + .settle_timer = Some(token) + } Err(error) => { tracing::warn!(%error, "could not debounce output events"); reconcile_outputs(state, false); diff --git a/compositor/src/backends/direct/transaction.rs b/compositor/src/backends/direct/transaction.rs index e19ab08..759fdc2 100644 --- a/compositor/src/backends/direct/transaction.rs +++ b/compositor/src/backends/direct/transaction.rs @@ -257,7 +257,7 @@ fn retire(state: &mut Ferese, output: &mut DirectOutput, node: DrmNode, crtc: cr state .direct_backend .as_mut() - .unwrap() + .expect("DRM callbacks require an installed direct backend") .presentation .remove(&(node, crtc)); } @@ -296,7 +296,7 @@ impl crate::output_transaction::HardwareTransaction for DeviceTransaction<'_> { .staged .get_mut(&crtc) .or_else(|| self.device.outputs.get_mut(&crtc)) - .unwrap(); + .expect("the selected CRTC has a live or staged output in this transaction"); output .surface .set_connectors(&[selection.connector.handle()]) @@ -317,8 +317,15 @@ impl crate::output_transaction::HardwareTransaction for DeviceTransaction<'_> { // set_connectors: each tests against the other's old value. Stage a // replacement surface, retire the old one *before* its first commit // (Smithay clears the CRTC on surface drop), then publish metadata. - let replacement = self.staged.remove(&crtc).unwrap(); - let previous = self.device.outputs.get_mut(&crtc).unwrap(); + let replacement = self + .staged + .remove(&crtc) + .expect("the selected CRTC has a live or staged output in this transaction"); + let previous = self + .device + .outputs + .get_mut(&crtc) + .expect("the selected CRTC has a live or staged output in this transaction"); previous.surface = replacement.surface; previous.dmabuf_feedback = replacement.dmabuf_feedback; self.replaced.insert(crtc); @@ -326,7 +333,7 @@ impl crate::output_transaction::HardwareTransaction for DeviceTransaction<'_> { self.staged .get_mut(&crtc) .or_else(|| self.device.outputs.get_mut(&crtc)) - .unwrap() + .expect("the selected CRTC has a live or staged output in this transaction") .surface .commit_frame() .map_err(|error| error.to_string()) @@ -336,7 +343,7 @@ impl crate::output_transaction::HardwareTransaction for DeviceTransaction<'_> { self.device .outputs .get_mut(&crtc) - .unwrap() + .expect("the selected CRTC has a live or staged output in this transaction") .surface .clear() .map_err(|error| error.to_string()) @@ -384,7 +391,11 @@ impl crate::output_transaction::HardwareTransaction for DeviceTransaction<'_> { &replacement.settings, ) .map_err(|error| error.to_string())?; - let previous = self.device.outputs.get_mut(&crtc).unwrap(); + let previous = self + .device + .outputs + .get_mut(&crtc) + .expect("the selected CRTC has a live or staged output in this transaction"); previous.surface = replacement.surface; previous.dmabuf_feedback = replacement.dmabuf_feedback; previous @@ -426,7 +437,7 @@ impl crate::output_transaction::HardwareTransaction for DeviceTransaction<'_> { self.state .desktop_transition .as_mut() - .unwrap() + .expect("desktop transition must be started before publication or retirement") .redraw .insert(output.output.clone()); output.lock_frame_pending = false; @@ -504,7 +515,7 @@ pub(super) fn apply_device( let mut output = staged .remove(crtc) .or_else(|| device.outputs.remove(crtc)) - .unwrap(); + .expect("the selected CRTC has a live or staged output in this transaction"); if output.connector != selection.connector.handle() || output.identity != selection.identity { retire(state, &mut output, node, *crtc); @@ -551,7 +562,7 @@ pub(super) fn apply_device( let clock = state .direct_backend .as_mut() - .unwrap() + .expect("DRM callbacks require an installed direct backend") .presentation .entry((node, *crtc)) .or_default(); @@ -561,7 +572,10 @@ pub(super) fn apply_device( } // Retire resources now; the final inventory determines evacuation once. for crtc in removals { - let mut output = device.outputs.remove(&crtc).unwrap(); + let mut output = device + .outputs + .remove(&crtc) + .expect("the selected CRTC has a live or staged output in this transaction"); retire(state, &mut output, node, crtc); } device.connected_outputs = scan.connected_outputs; @@ -592,7 +606,7 @@ pub(super) fn retire_disconnected(state: &mut Ferese) { let missing = state .direct_backend .as_ref() - .unwrap() + .expect("DRM callbacks require an installed direct backend") .devices .iter() .flat_map(|(node, device)| { @@ -610,10 +624,10 @@ pub(super) fn retire_disconnected(state: &mut Ferese) { if let Some(mut output) = state .direct_backend .as_mut() - .unwrap() + .expect("DRM callbacks require an installed direct backend") .devices .get_mut(&node) - .unwrap() + .expect("the selected DRM device must remain registered during this operation") .outputs .remove(&crtc) { @@ -629,7 +643,7 @@ pub(super) fn recover_last_output(state: &mut Ferese) { if state .direct_backend .as_ref() - .unwrap() + .expect("DRM callbacks require an installed direct backend") .devices .values() .any(|device| { @@ -641,7 +655,10 @@ pub(super) fn recover_last_output(state: &mut Ferese) { { return; } - let backend = state.direct_backend.as_ref().unwrap(); + let backend = state + .direct_backend + .as_ref() + .expect("DRM callbacks require an installed direct backend"); let requested = backend.desired_outputs.clone(); let original_error = backend.output_error.clone(); let mut candidates = backend @@ -652,7 +669,10 @@ pub(super) fn recover_last_output(state: &mut Ferese) { .collect::>(); candidates.sort_by_key(|monitor| (!monitor.internal, monitor.key.clone())); for candidate in candidates { - let backend = state.direct_backend.as_mut().unwrap(); + let backend = state + .direct_backend + .as_mut() + .expect("DRM callbacks require an installed direct backend"); backend.desired_outputs = DesiredOutputConfiguration { profile: None, suspend: requested.suspend, @@ -687,7 +707,7 @@ pub(super) fn recover_last_output(state: &mut Ferese) { if state .direct_backend .as_ref() - .unwrap() + .expect("DRM callbacks require an installed direct backend") .devices .values() .any(|device| { @@ -698,13 +718,20 @@ pub(super) fn recover_last_output(state: &mut Ferese) { }) { remember_applied_configuration(state); - state.direct_backend.as_mut().unwrap().output_error = original_error; + state + .direct_backend + .as_mut() + .expect("DRM callbacks require an installed direct backend") + .output_error = original_error; tracing::warn!(identity = %candidate.identity, "recovered last usable output with preferred mode"); return; } } } - let backend = state.direct_backend.as_mut().unwrap(); + let backend = state + .direct_backend + .as_mut() + .expect("DRM callbacks require an installed direct backend"); backend.desired_outputs = requested; backend.output_error = original_error; } diff --git a/compositor/src/config.rs b/compositor/src/config.rs index cbcd8b1..16ca765 100644 --- a/compositor/src/config.rs +++ b/compositor/src/config.rs @@ -115,8 +115,11 @@ impl Config { theme: &ferese_config::theme::ResolvedTheme, ) -> Result { let config = Self { - theme: serde_json::from_value(serde_json::to_value(&theme.tokens).unwrap()) - .expect("resolved theme matches runtime schema"), + theme: serde_json::from_value( + serde_json::to_value(&theme.tokens) + .expect("typed configuration and IPC values must serialize to JSON"), + ) + .expect("resolved theme matches runtime schema"), ..Self::default() }; let mut settings = config.theme_settings()?; diff --git a/compositor/src/cursor.rs b/compositor/src/cursor.rs index 855aeed..62a7151 100644 --- a/compositor/src/cursor.rs +++ b/compositor/src/cursor.rs @@ -132,14 +132,20 @@ impl Loader { loop { let (requests, ready) = &*worker; let mut requests = ready - .wait_while(requests.lock().unwrap(), |requests| { - !requests.closed && requests.pending.is_none() - }) - .unwrap(); + .wait_while( + requests.lock().expect( + "cursor decode queue must remain consistent across workers", + ), + |requests| !requests.closed && requests.pending.is_none(), + ) + .expect("cursor decode queue must remain consistent across workers"); if requests.closed { break; } - let request = requests.pending.take().unwrap(); + let request = requests + .pending + .take() + .expect("the decode wait completed with a pending cursor request"); requests.in_flight = Some(request); drop(requests); let cursor = load(request); @@ -155,7 +161,11 @@ impl Loader { break; } signal.wakeup(); - worker.0.lock().unwrap().in_flight = None; + worker + .0 + .lock() + .expect("cursor decode queue must remain consistent across workers") + .in_flight = None; } })?; Ok(Self { shared, results }) @@ -163,7 +173,11 @@ impl Loader { pub(crate) fn request(&self, icon: CursorIcon, scale: i32) { let request = (icon, scale); - let mut requests = self.shared.0.lock().unwrap(); + let mut requests = self + .shared + .0 + .lock() + .expect("cursor decode queue must remain consistent across workers"); if requests.in_flight == Some(request) || requests.pending == Some(request) { return; } @@ -174,7 +188,11 @@ impl Loader { impl Drop for Loader { fn drop(&mut self) { - let mut requests = self.shared.0.lock().unwrap(); + let mut requests = self + .shared + .0 + .lock() + .expect("cursor decode queue must remain consistent across workers"); requests.closed = true; requests.pending = None; self.shared.1.notify_one(); @@ -256,7 +274,11 @@ impl crate::Ferese { }) else { break; }; - bytes -= self.named_cursors.remove(&evict).unwrap().bytes; + bytes -= self + .named_cursors + .remove(&evict) + .expect("cursor selection retains the requested or default cursor") + .bytes; } self.named_cursors.insert(icon, cursor); if affects_current { @@ -299,12 +321,12 @@ impl crate::Ferese { if !visible { return; } - let icon = icon.unwrap(); + let icon = icon.expect("the hidden-cursor branch returned before image selection"); let cursor = self .named_cursors .get(&icon) .or_else(|| self.named_cursors.get(&CursorIcon::Default)) - .unwrap(); + .expect("cursor selection retains the requested or default cursor"); let (frame, remaining) = cursor.sample(now.saturating_duration_since(self.cursor_animation.epoch)); if self.cursor_animation.frame != frame { diff --git a/compositor/src/dmabuf_imports.rs b/compositor/src/dmabuf_imports.rs index d8d21bc..987aee2 100644 --- a/compositor/src/dmabuf_imports.rs +++ b/compositor/src/dmabuf_imports.rs @@ -66,7 +66,10 @@ impl Queue { mut complete: impl FnMut(Pending), ) { for _ in 0..self.pending.len() { - let entry = self.pending.pop_front().unwrap(); + let entry = self + .pending + .pop_front() + .expect("the bounded drain removes at most the original queue length"); if remove(&entry) { complete(entry); } else { diff --git a/compositor/src/effects.rs b/compositor/src/effects.rs index 06a9c01..d899318 100644 --- a/compositor/src/effects.rs +++ b/compositor/src/effects.rs @@ -109,22 +109,52 @@ impl SurfaceEffectsState { } fn commit_presentation(&self) { - let Some(pending) = self.pending.lock().unwrap().take() else { + let Some(pending) = self + .pending + .lock() + .expect("surface effect state must not be used after a mutator panics") + .take() + else { return; }; - *self.role.lock().unwrap() = Some(pending.role); - *self.panel.lock().unwrap() = pending.panel; - *self.regions.lock().unwrap() = Some(pending.regions); - *self.region_opacities.lock().unwrap() = pending.region_opacities; + *self + .role + .lock() + .expect("surface effect state must not be used after a mutator panics") = + Some(pending.role); + *self + .panel + .lock() + .expect("surface effect state must not be used after a mutator panics") = pending.panel; + *self + .regions + .lock() + .expect("surface effect state must not be used after a mutator panics") = + Some(pending.regions); + *self + .region_opacities + .lock() + .expect("surface effect state must not be used after a mutator panics") = + pending.region_opacities; if !self.dismissing.load(Ordering::Acquire) { - *self.opacity.lock().unwrap() = pending.opacity; + *self + .opacity + .lock() + .expect("surface effect state must not be used after a mutator panics") = + pending.opacity; } self.generation.fetch_add(1, Ordering::Release); } fn set_role(&self, role: Option) -> bool { - self.pending.lock().unwrap().take(); - let mut current = self.role.lock().unwrap(); + self.pending + .lock() + .expect("surface effect state must not be used after a mutator panics") + .take(); + let mut current = self + .role + .lock() + .expect("surface effect state must not be used after a mutator panics"); if *current != role { *current = role; self.generation.fetch_add(1, Ordering::Release); @@ -144,7 +174,11 @@ impl SurfaceEffectsUserData { } fn surface(&self) -> Option { - self.0.lock().unwrap().upgrade().ok() + self.0 + .lock() + .expect("surface effect state must not be used after a mutator panics") + .upgrade() + .ok() } } @@ -182,7 +216,10 @@ impl Dispatch for Ferese { states .data_map .insert_if_missing_threadsafe(SurfaceEffectsState::new); - let effects = states.data_map.get::().unwrap(); + let effects = states + .data_map + .get::() + .expect("effects state was inserted before this access"); effects.attached.swap(true, Ordering::AcqRel) }); if attached { @@ -248,7 +285,7 @@ pub(crate) fn surface_regions(surface: &WlSurface) -> Option> { .get::()? .regions .lock() - .unwrap() + .expect("surface effect state must not be used after a mutator panics") .clone() }) } @@ -260,7 +297,7 @@ pub(crate) fn surface_panel_presentation(surface: &WlSurface) -> Option()? .panel .lock() - .unwrap() + .expect("surface effect state must not be used after a mutator panics") .clone() }) } @@ -296,7 +333,13 @@ pub(crate) fn surface_region_opacities(surface: &WlSurface) -> Vec { states .data_map .get::() - .map(|effects| effects.region_opacities.lock().unwrap().clone()) + .map(|effects| { + effects + .region_opacities + .lock() + .expect("surface effect state must not be used after a mutator panics") + .clone() + }) .unwrap_or_default() }) } @@ -345,7 +388,10 @@ fn decode_regions(bytes: &[u8]) -> Option> { pub(crate) fn surface_role(surface: &WlSurface) -> Option<(SemanticRole, u64)> { with_states(surface, |states| { let effects = states.data_map.get::()?; - let role = *effects.role.lock().unwrap(); + let role = *effects + .role + .lock() + .expect("surface effect state must not be used after a mutator panics"); role.map(|role| (role, effects.generation.load(Ordering::Acquire))) }) } @@ -388,7 +434,7 @@ impl Dispatch for Ferese { states .data_map .get::() - .unwrap() + .expect("effects state was inserted before this access") .attached .swap(true, Ordering::AcqRel) }); @@ -404,7 +450,7 @@ impl Dispatch for Ferese { states .data_map .get::() - .unwrap() + .expect("effects state was inserted before this access") .presentation_supported .store(effects.version() >= 3, Ordering::Release) }); @@ -448,7 +494,9 @@ impl Dispatch for Ferese { } with_states(&surface, |states| { if let Some(effects) = states.data_map.get::() { - *effects.pending.lock().unwrap() = Some(PendingPresentation { + *effects.pending.lock().expect( + "surface effect state must not be used after a mutator panics", + ) = Some(PendingPresentation { panel: Some(panel), role: SemanticRole::Panel, region_opacities: vec![1000; regions.len()], @@ -473,7 +521,9 @@ impl Dispatch for Ferese { }; with_states(&surface, |states| { if let Some(effects) = states.data_map.get::() { - *effects.pending.lock().unwrap() = Some(PendingPresentation { + *effects.pending.lock().expect( + "surface effect state must not be used after a mutator panics", + ) = Some(PendingPresentation { panel: None, role, regions, @@ -491,7 +541,10 @@ impl Dispatch for Ferese { if effects.dismissing.load(Ordering::Acquire) { return false; } - let mut current = effects.opacity.lock().unwrap(); + let mut current = effects + .opacity + .lock() + .expect("surface effect state must not be used after a mutator panics"); let opacity = opacity.min(1000); if *current == opacity { return false; @@ -520,7 +573,10 @@ impl Dispatch for Ferese { let Some(effects) = states.data_map.get::() else { return false; }; - let mut old = effects.region_opacities.lock().unwrap(); + let mut old = effects + .region_opacities + .lock() + .expect("surface effect state must not be used after a mutator panics"); if *old == opacities { return false; } @@ -540,7 +596,10 @@ impl Dispatch for Ferese { let Some(effects) = states.data_map.get::() else { return false; }; - let mut old = effects.regions.lock().unwrap(); + let mut old = effects + .regions + .lock() + .expect("surface effect state must not be used after a mutator panics"); if old.as_ref() == Some(®ions) { return false; } @@ -606,11 +665,26 @@ fn detach(surface: &WlSurface) -> bool { return false; }; let role_changed = effects.set_role(None); - effects.region_opacities.lock().unwrap().clear(); - effects.pending.lock().unwrap().take(); - effects.panel.lock().unwrap().take(); + effects + .region_opacities + .lock() + .expect("surface effect state must not be used after a mutator panics") + .clear(); + effects + .pending + .lock() + .expect("surface effect state must not be used after a mutator panics") + .take(); + effects + .panel + .lock() + .expect("surface effect state must not be used after a mutator panics") + .take(); effects.dismissing.store(false, Ordering::Release); - let mut opacity = effects.opacity.lock().unwrap(); + let mut opacity = effects + .opacity + .lock() + .expect("surface effect state must not be used after a mutator panics"); let opacity_changed = *opacity != 1000; *opacity = 1000; let was_attached = effects.attached.swap(false, Ordering::AcqRel); @@ -624,7 +698,12 @@ pub(crate) fn surface_opacity(surface: &WlSurface) -> f32 { .data_map .get::() .map_or(1.0, |effects| { - *effects.opacity.lock().unwrap() as f32 / 1000.0 + *effects + .opacity + .lock() + .expect("surface effect state must not be used after a mutator panics") + as f32 + / 1000.0 }) }) } @@ -645,7 +724,11 @@ pub(crate) fn begin_surface_dismiss(surface: &WlSurface) -> bool { pub(crate) fn fade_dismissed_surface(surface: &WlSurface, opacity: f64) { with_states(surface, |states| { if let Some(effects) = states.data_map.get::() { - *effects.opacity.lock().unwrap() = (opacity.clamp(0.0, 1.0) * 1000.0).round() as u32; + *effects + .opacity + .lock() + .expect("surface effect state must not be used after a mutator panics") = + (opacity.clamp(0.0, 1.0) * 1000.0).round() as u32; effects.generation.fetch_add(1, Ordering::Release); } }); diff --git a/compositor/src/floating.rs b/compositor/src/floating.rs index 0ebe343..d3977be 100644 --- a/compositor/src/floating.rs +++ b/compositor/src/floating.rs @@ -213,9 +213,13 @@ impl SaveThread { loop { let job = { let (mutex, ready) = &*worker; - let mut state = mutex.lock().unwrap(); + let mut state = mutex.lock().expect( + "screenshot writer queue must remain consistent across workers", + ); while state.pending.is_none() && !state.closed { - state = ready.wait(state).unwrap(); + state = ready.wait(state).expect( + "screenshot writer queue must remain consistent across workers", + ); } match state.pending.take() { Some(job) => job, @@ -237,7 +241,10 @@ impl SaveThread { fn queue(&self, path: PathBuf, memory: Memory) { let (mutex, ready) = &*self.shared; - mutex.lock().unwrap().pending = Some((path, memory)); + mutex + .lock() + .expect("screenshot writer queue must remain consistent across workers") + .pending = Some((path, memory)); ready.notify_one(); } } @@ -245,7 +252,10 @@ impl SaveThread { impl Drop for SaveThread { fn drop(&mut self) { let (mutex, ready) = &*self.shared; - mutex.lock().unwrap().closed = true; + mutex + .lock() + .expect("screenshot writer queue must remain consistent across workers") + .closed = true; ready.notify_one(); // Flush the latest snapshot only during compositor shutdown, after // the event loop has stopped. Drag completion never joins this thread. @@ -265,7 +275,10 @@ impl SaveWorker { if self.worker.is_none() { self.worker = Some(SaveThread::start(|path, memory| memory.persist(path))?); } - self.worker.as_ref().unwrap().queue(path, memory.clone()); + self.worker + .as_ref() + .expect("screenshot submission initialized the writer before queueing work") + .queue(path, memory.clone()); Ok(()) } } diff --git a/compositor/src/handlers/layer_shell.rs b/compositor/src/handlers/layer_shell.rs index 7179720..e7d170a 100644 --- a/compositor/src/handlers/layer_shell.rs +++ b/compositor/src/handlers/layer_shell.rs @@ -177,7 +177,9 @@ pub fn handle_commit(state: &mut Ferese, surface: &WlSurface) { std::sync::Mutex::new(None::<(LayoutState, KeyboardInteractivity)>) }); let (changed, requested_focus) = { - let mut previous = previous.lock().unwrap(); + let mut previous = previous + .lock() + .expect("layer configure state must remain consistent across callbacks"); let changed = previous.as_ref().map(|(layout, _)| layout) != Some(¤t); let requested_focus = policy.keyboard_interactivity == KeyboardInteractivity::OnDemand && previous diff --git a/compositor/src/handlers/screencopy.rs b/compositor/src/handlers/screencopy.rs index b2e49f4..4dacd34 100644 --- a/compositor/src/handlers/screencopy.rs +++ b/compositor/src/handlers/screencopy.rs @@ -250,7 +250,10 @@ impl Dispatch> for Ferese { state.queue_screencopy(frame, data, buffer, true); } zwlr_screencopy_frame_v1::Request::Destroy => { - data.snapshot.lock().unwrap().take(); + data.snapshot + .lock() + .expect("surface rendering state must remain consistent across callbacks") + .take(); } _ => unreachable!(), } @@ -265,11 +268,19 @@ impl Ferese { buffer: WlBuffer, with_damage: bool, ) { - if data.snapshot.lock().unwrap().is_some() { + if data + .snapshot + .lock() + .expect("surface rendering state must remain consistent across callbacks") + .is_some() + { super::window_capture::copy_snapshot(self, frame, data, &buffer); return; } - let mut used = data.used.lock().unwrap(); + let mut used = data + .used + .lock() + .expect("surface rendering state must remain consistent across callbacks"); if self.session_lock.active() { frame.failed(); return; diff --git a/compositor/src/handlers/window_capture.rs b/compositor/src/handlers/window_capture.rs index 4c1736a..e1ac0ee 100644 --- a/compositor/src/handlers/window_capture.rs +++ b/compositor/src/handlers/window_capture.rs @@ -35,7 +35,9 @@ struct Permit { impl Permit { fn reserve(budget: &Arc>, bytes: usize) -> Option { - let mut available = budget.lock().unwrap(); + let mut available = budget + .lock() + .expect("surface rendering state must remain consistent across callbacks"); if bytes == 0 || bytes > MAX_BYTES.saturating_sub(available.bytes) || available.frames >= MAX_FRAMES @@ -53,7 +55,10 @@ impl Permit { impl Drop for Permit { fn drop(&mut self) { - let mut budget = self.budget.lock().unwrap(); + let mut budget = self + .budget + .lock() + .expect("surface rendering state must remain consistent across callbacks"); budget.bytes -= self.bytes; budget.frames -= 1; } @@ -167,21 +172,32 @@ fn finish_conversion(state: &Ferese, result: Conversion) { let Some(data) = frame.data::>() else { return; }; - if !frame.is_alive() || *data.used.lock().unwrap() { + if !frame.is_alive() + || *data + .used + .lock() + .expect("surface rendering state must remain consistent across callbacks") + { return; } if state.session_lock.active() || !super::screencopy::capture_allowed() || !state.capture_window_allowed(snapshot.id, snapshot.epoch) { - *data.used.lock().unwrap() = true; + *data + .used + .lock() + .expect("surface rendering state must remain consistent across callbacks") = true; frame.failed(); return; } let size = (snapshot.pixels.width as u32, snapshot.pixels.height as u32); let logical_size = snapshot.logical_size; - *data.snapshot.lock().unwrap() = Some(snapshot); + *data + .snapshot + .lock() + .expect("surface rendering state must remain consistent across callbacks") = Some(snapshot); if manager.is_alive() { manager.geometry(&frame, logical_size.0, logical_size.1); } @@ -256,7 +272,8 @@ impl Dispatch for Ferese { } else if global .worker .try_send(Conversion { - snapshot: snapshot.unwrap(), + snapshot: snapshot + .expect("conversion is queued only after obtaining a capture snapshot"), frame: resource.clone(), manager: manager.clone(), }) @@ -264,10 +281,11 @@ impl Dispatch for Ferese { { *resource .data::>() - .unwrap() + .expect("capture frame resources are initialized with their FrameData") .used .lock() - .unwrap() = true; + .expect("surface rendering state must remain consistent across callbacks") = + true; manager.busy(&resource); resource.failed(); } @@ -386,7 +404,10 @@ pub(super) fn copy_snapshot( data: &FrameData, buffer: &WlBuffer, ) { - let mut used = data.used.lock().unwrap(); + let mut used = data + .used + .lock() + .expect("surface rendering state must remain consistent across callbacks"); if *used { resource.post_error( frame::Error::AlreadyUsed, @@ -395,7 +416,12 @@ pub(super) fn copy_snapshot( return; } *used = true; - let Some(snapshot) = data.snapshot.lock().unwrap().take() else { + let Some(snapshot) = data + .snapshot + .lock() + .expect("surface rendering state must remain consistent across callbacks") + .take() + else { resource.failed(); return; }; diff --git a/compositor/src/handlers/xdg_shell.rs b/compositor/src/handlers/xdg_shell.rs index 954809a..78b5887 100644 --- a/compositor/src/handlers/xdg_shell.rs +++ b/compositor/src/handlers/xdg_shell.rs @@ -329,9 +329,9 @@ pub(crate) fn initial_configure_sent(toplevel: &ToplevelSurface) -> bool { states .data_map .get::() - .unwrap() + .expect("an XDG toplevel must retain its Smithay surface data") .lock() - .unwrap() + .expect("Smithay surface state must remain consistent across callbacks") .initial_configure_sent }) } diff --git a/compositor/src/idle_inhibition.rs b/compositor/src/idle_inhibition.rs index cd14221..9253d83 100644 --- a/compositor/src/idle_inhibition.rs +++ b/compositor/src/idle_inhibition.rs @@ -142,7 +142,7 @@ impl Ferese { states .data_map .get::() - .is_some_and(|data| data.lock().unwrap().parent.is_some()) + .is_some_and(|data| data.lock().expect("Smithay surface metadata must remain consistent across callbacks").parent.is_some()) }); crate::window_rules::resolve( &self.window_rules, diff --git a/compositor/src/idle_inhibition/media/bus.rs b/compositor/src/idle_inhibition/media/bus.rs index e9ebea8..1e55237 100644 --- a/compositor/src/idle_inhibition/media/bus.rs +++ b/compositor/src/idle_inhibition/media/bus.rs @@ -245,8 +245,12 @@ async fn action( .length_us .map_or(position, |length| position.min(length)) .min(i64::MAX as u64) as i64; - let track = OwnedObjectPath::try_from(view.track_id.clone().unwrap()) - .map_err(|error| error.to_string())?; + let track = OwnedObjectPath::try_from( + view.track_id + .clone() + .expect("media action validation requires an active track"), + ) + .map_err(|error| error.to_string())?; connection .call_method( Some(player.owner.as_str()), @@ -262,7 +266,11 @@ async fn action( .as_f64() .filter(|delta| delta.is_finite() && delta.abs() <= 1.0) .ok_or("Invalid volume change")?; - let volume = (view.volume.unwrap() + delta).clamp(0.0, 1.0); + let volume = (view + .volume + .expect("relative volume validation requires the current volume") + + delta) + .clamp(0.0, 1.0); connection .call_method( Some(player.owner.as_str()), diff --git a/compositor/src/input.rs b/compositor/src/input.rs index 2c4db02..dff97e1 100644 --- a/compositor/src/input.rs +++ b/compositor/src/input.rs @@ -1048,7 +1048,10 @@ impl Ferese { let keyboard = self.seat.get_keyboard().expect("seat has a keyboard"); let modifiers = keyboard.modifier_state(); let raw_symbols = keyboard.with_xkb_state(self, |context| { - let xkb = context.xkb().lock().unwrap(); + let xkb = context + .xkb() + .lock() + .expect("keyboard keymap state must remain consistent across callbacks"); xkb.raw_syms_for_key_in_layout(keycode, xkb.active_layout()) .iter() .map(|symbol| symbol.raw()) @@ -1484,7 +1487,11 @@ fn surface_tree_offset(root: &WlSurface, target: &WlSurface) -> Option() - .and_then(|data| data.lock().unwrap().view()) + .and_then(|data| { + data.lock() + .expect("Smithay surface metadata must remain consistent across callbacks") + .view() + }) .map_or(TraversalAction::SkipChildren, |view| { TraversalAction::DoChildren(*location + view.offset) }) @@ -1494,7 +1501,13 @@ fn surface_tree_offset(root: &WlSurface, target: &WlSurface) -> Option() - .and_then(|data| data.lock().unwrap().view()) + .and_then(|data| { + data.lock() + .expect( + "Smithay surface metadata must remain consistent across callbacks", + ) + .view() + }) { found.set(Some(*location + view.offset)); } diff --git a/compositor/src/input_capture.rs b/compositor/src/input_capture.rs index ba61564..5afd8ef 100644 --- a/compositor/src/input_capture.rs +++ b/compositor/src/input_capture.rs @@ -174,7 +174,11 @@ impl InputCapture { self.disable_all(); let ids = self.sessions.keys().copied().collect::>(); for id in ids { - self.sessions.get_mut(&id).unwrap().barriers.clear(); + self.sessions + .get_mut(&id) + .expect("input capture operations validate their session or waiter before mutation") + .barriers + .clear(); self.enqueue(id, json!({"type":"zones", "zone_set":self.zone_set})); } self.flush(); @@ -246,7 +250,10 @@ impl InputCapture { failed.push(barrier.id); } } - self.sessions.get_mut(&id).unwrap().barriers = accepted; + self.sessions + .get_mut(&id) + .expect("input capture operations validate their session or waiter before mutation") + .barriers = accepted; Ok(failed) } @@ -303,7 +310,10 @@ impl InputCapture { self.next_activation = self.next_activation.wrapping_add(1); let activation = self.next_activation; self.active = Some(id); - let session = self.sessions.get_mut(&id).unwrap(); + let session = self + .sessions + .get_mut(&id) + .expect("input capture operations validate their session or waiter before mutation"); session.activation = Some(activation); self.enqueue( id, @@ -367,7 +377,9 @@ impl InputCapture { self.active = None; self.last_absolute = None; self.restore_focus = true; - let session = self.sessions.get_mut(&id).unwrap(); + let session = self.sessions.get_mut(&id).expect( + "input capture operations validate their session or waiter before mutation", + ); let activation = session.activation.take(); self.enqueue( id, @@ -380,7 +392,10 @@ impl InputCapture { pub(crate) fn disable(&mut self, id: u64) -> Result<(), String> { self.release(id, None)?; - let session = self.sessions.get_mut(&id).unwrap(); + let session = self + .sessions + .get_mut(&id) + .expect("input capture operations validate their session or waiter before mutation"); if session.enabled { session.enabled = false; self.enqueue(id, json!({"type":"disabled"})); @@ -436,7 +451,9 @@ impl InputCapture { }) .collect::>(); for owner in ready { - let (request, id, response) = self.waiters.remove(&owner).unwrap(); + let (request, id, response) = self.waiters.remove(&owner).expect( + "input capture operations validate their session or waiter before mutation", + ); let result = if let Some(session) = self.sessions.get_mut(&id) { json!({"events":session.events.drain(..).collect::>()}) } else { diff --git a/compositor/src/ipc.rs b/compositor/src/ipc.rs index d98ff63..db1c334 100644 --- a/compositor/src/ipc.rs +++ b/compositor/src/ipc.rs @@ -1106,7 +1106,10 @@ impl Ferese { self.refresh_input_capture_zones(); let keyboard = self.seat.get_keyboard().expect("seat has keyboard"); let keymap = keyboard.with_xkb_state(self, |state| { - let xkb = state.xkb().lock().unwrap(); + let xkb = state + .xkb() + .lock() + .expect("keyboard keymap state must remain consistent across callbacks"); // SAFETY: the ref-counted keymap does not escape the locked Xkb; // only its owned string is retained for the EIS keyboard. unsafe { xkb.keymap() } @@ -1708,7 +1711,7 @@ impl Ferese { .filter_map(|(window, id)| { let toplevel = window.toplevel()?; let (app_id, title) = with_states(toplevel.wl_surface(), |states| { - let attributes = states.data_map.get::().unwrap().lock().unwrap(); + let attributes = states.data_map.get::().expect("an XDG toplevel must retain its Smithay surface data").lock().expect("Smithay surface state must remain consistent across callbacks"); ( attributes.app_id.clone().unwrap_or_default(), attributes.title.clone().unwrap_or_default(), diff --git a/compositor/src/ipc_events.rs b/compositor/src/ipc_events.rs index fca9a49..ddddd31 100644 --- a/compositor/src/ipc_events.rs +++ b/compositor/src/ipc_events.rs @@ -78,7 +78,9 @@ impl Subscribers { }) .collect::>(); if let Some(last) = events.last_mut() { - Arc::get_mut(last).unwrap().last = true; + Arc::get_mut(last) + .expect("new initial-event records have not been shared with subscribers") + .last = true; } let initial = self .subscribers @@ -98,7 +100,12 @@ impl Subscribers { let delivered = if subscriber.initial { subscriber .sender - .try_send(initial.as_ref().unwrap().clone()) + .try_send( + initial + .as_ref() + .expect("the initial event batch is built before subscription delivery") + .clone(), + ) .is_ok() } else { events diff --git a/compositor/src/monitor_identity.rs b/compositor/src/monitor_identity.rs index a59214c..54abf69 100644 --- a/compositor/src/monitor_identity.rs +++ b/compositor/src/monitor_identity.rs @@ -114,26 +114,30 @@ impl IdentityRegistry { Some(base) => format!("{base}@{}", escaped(key)), None => format!("drm:{}", escaped(key)), }; - let preferred = self - .known - .get(key) - .filter(|known| { - base.as_ref() - .is_some_and(|base| *known == base || *known == &qualified) - || (base.is_none() && *known == &qualified) - }) - .cloned() - .unwrap_or_else(|| { - if identity - .as_ref() - .is_some_and(|identity| identity.serial.is_some()) - && !self.ambiguous.contains(base.as_ref().unwrap()) - { - base.clone().unwrap() - } else { - qualified.clone() - } - }); + let preferred = + self.known + .get(key) + .filter(|known| { + base.as_ref() + .is_some_and(|base| *known == base || *known == &qualified) + || (base.is_none() && *known == &qualified) + }) + .cloned() + .unwrap_or_else(|| { + if identity + .as_ref() + .is_some_and(|identity| identity.serial.is_some()) + && !self.ambiguous.contains(base.as_ref().expect( + "a unique serial-bearing monitor has a computed base identity", + )) + { + base.clone().expect( + "a unique serial-bearing monitor has a computed base identity", + ) + } else { + qualified.clone() + } + }); let resolved = if used.insert(preferred.clone()) { preferred } else { diff --git a/compositor/src/overview.rs b/compositor/src/overview.rs index 26a974d..6310e4f 100644 --- a/compositor/src/overview.rs +++ b/compositor/src/overview.rs @@ -30,7 +30,11 @@ struct FontRequests { impl FontRequests { fn request(&self, family: &str) { - *self.latest.lock().unwrap() = family.to_owned(); + *self + .latest + .lock() + .expect("overview font request state must remain consistent across workers") = + family.to_owned(); let _ = self.wake.try_send(()); } } @@ -59,7 +63,10 @@ pub(crate) fn init_font_loader( .name("ferese-overview-font".into()) .spawn(move || { while requests.recv().is_ok() { - let family = worker_latest.lock().unwrap().clone(); + let family = worker_latest + .lock() + .expect("overview font request state must remain consistent across workers") + .clone(); // Rescan on a font change so newly installed fonts remain usable. let mut database = fontdb::Database::new(); database.load_system_fonts(); @@ -1049,7 +1056,16 @@ impl Ferese { for (id, target) in overview_layout(window_area(bounds), &windows) { targets.insert( id, - (self.windows.geometry(&id).unwrap().visual.current, target), + ( + self.windows + .geometry(&id) + .expect( + "overview layout includes only windows with resolved geometry", + ) + .visual + .current, + target, + ), ); } continue; @@ -1074,7 +1090,12 @@ impl Ferese { .collect::>(); for (id, target) in overview_layout(window_area(bounds), &windows) { - let normal = self.windows.geometry(&id).unwrap().visual.current; + let normal = self + .windows + .geometry(&id) + .expect("overview layout includes only windows with resolved geometry") + .visual + .current; // Incoming grids start at their overview size. Translation // belongs to the workspace slide, not a second desktop zoom. let source = if !sliding.is_empty() { target } else { normal }; diff --git a/compositor/src/portal_session.rs b/compositor/src/portal_session.rs index ed42f06..07d5c2a 100644 --- a/compositor/src/portal_session.rs +++ b/compositor/src/portal_session.rs @@ -146,7 +146,10 @@ impl PortalSession { pub(crate) fn begin_owned_query(&mut self, owner: u64) -> u32 { let token = self.begin_query(); - self.query.as_mut().unwrap().owner = Some(owner); + self.query + .as_mut() + .expect("begin_query installs the query before setting its owner") + .owner = Some(owner); token } diff --git a/compositor/src/render/backdrop.rs b/compositor/src/render/backdrop.rs index e3ca0ba..575aae1 100644 --- a/compositor/src/render/backdrop.rs +++ b/compositor/src/render/backdrop.rs @@ -108,10 +108,15 @@ fn update_shared( ) -> bool { // Element queries can read this same backdrop's commit. Snapshot the state, // release its lock, then inspect sources. Rendering is the sole writer. - let mut backdrop = shared.lock().unwrap().clone(); + let mut backdrop = shared + .lock() + .expect("surface rendering state must remain consistent across callbacks") + .clone(); let sources = elements.iter().filter(|element| element.id() != owner); let changed = backdrop.update(sources, region, scale); - *shared.lock().unwrap() = backdrop; + *shared + .lock() + .expect("surface rendering state must remain consistent across callbacks") = backdrop; changed } diff --git a/compositor/src/render/capture.rs b/compositor/src/render/capture.rs index b8fa23d..4290b12 100644 --- a/compositor/src/render/capture.rs +++ b/compositor/src/render/capture.rs @@ -106,7 +106,11 @@ pub(crate) fn capture_window_frame( .user_data() .get_or_insert_threadsafe(|| Arc::new(CaptureTarget::default())) .clone(); - let reusable = cache.0.lock().unwrap().take(); + let reusable = cache + .0 + .lock() + .expect("the reusable capture texture cache must remain consistent across callbacks") + .take(); let mut snapshot = render_window_texture( renderer, window, @@ -159,7 +163,11 @@ pub(crate) fn capture_window_frame( return Err("Incomplete window readback".into()); } let pixels = source[..bytes].to_vec(); - *cache.0.lock().unwrap() = Some(snapshot.texture); + *cache + .0 + .lock() + .expect("the reusable capture texture cache must remain consistent across callbacks") = + Some(snapshot.texture); Ok(crate::handlers::screenshot::CaptureBuffer { width: size.w, diff --git a/compositor/src/render/materials.rs b/compositor/src/render/materials.rs index b897ee4..471bb4b 100644 --- a/compositor/src/render/materials.rs +++ b/compositor/src/render/materials.rs @@ -730,7 +730,7 @@ fn panel_matrices( match permutation .iter() .position(|i| *i == old_corner as f32) - .unwrap() + .expect("the corner transform is a permutation containing every source corner") { 0 => 5, 1 => 4, diff --git a/compositor/src/render/mod.rs b/compositor/src/render/mod.rs index 5d03a32..e3f7f0d 100644 --- a/compositor/src/render/mod.rs +++ b/compositor/src/render/mod.rs @@ -222,7 +222,11 @@ impl BlurRenderElement { self.geometry = parameters.sample_geometry; self.capture_rect = framebuffer_capture_rect(parameters.sample_framebuffer); self.uniforms = blur_uniforms(parameters).into(); - self.backdrop.lock().unwrap().commit.increment(); + self.backdrop + .lock() + .expect("surface rendering state must remain consistent across callbacks") + .commit + .increment(); } } @@ -232,7 +236,10 @@ impl Element for BlurRenderElement { } fn current_commit(&self) -> CommitCounter { - self.backdrop.lock().unwrap().commit + self.backdrop + .lock() + .expect("surface rendering state must remain consistent across callbacks") + .commit } fn src(&self) -> Rectangle { diff --git a/compositor/src/render/scene.rs b/compositor/src/render/scene.rs index afe1cff..45e8df5 100644 --- a/compositor/src/render/scene.rs +++ b/compositor/src/render/scene.rs @@ -561,7 +561,14 @@ pub(super) fn cursor_elements( states .data_map .get::() - .map(|attributes| attributes.lock().unwrap().hotspot) + .map(|attributes| { + attributes + .lock() + .expect( + "cursor surface attributes must remain consistent across callbacks", + ) + .hotspot + }) .unwrap_or_default() }); let physical_location = diff --git a/compositor/src/resume.rs b/compositor/src/resume.rs index 0448ea5..bb5a714 100644 --- a/compositor/src/resume.rs +++ b/compositor/src/resume.rs @@ -39,7 +39,10 @@ struct Cancellation { impl Cancellation { fn cancel(&self) { - let mut state = self.state.lock().unwrap(); + let mut state = self + .state + .lock() + .expect("resume cancellation state must remain consistent across workers"); state.requested = true; let waker = state.waker.take(); self.changed.notify_all(); @@ -52,7 +55,10 @@ impl Cancellation { async fn cancelled(&self) { future::poll_fn(|context| { - let mut state = self.state.lock().unwrap(); + let mut state = self + .state + .lock() + .expect("resume cancellation state must remain consistent across workers"); if state.requested { return Poll::Ready(()); } @@ -66,8 +72,14 @@ impl Cancellation { fn retry(&self, delay: Duration) -> bool { let (state, _) = self .changed - .wait_timeout_while(self.state.lock().unwrap(), delay, |state| !state.requested) - .unwrap(); + .wait_timeout_while( + self.state + .lock() + .expect("resume cancellation state must remain consistent across workers"), + delay, + |state| !state.requested, + ) + .expect("resume cancellation state must remain consistent across workers"); !state.requested } } diff --git a/compositor/src/session_lock.rs b/compositor/src/session_lock.rs index 62e5fe6..c8e0ace 100644 --- a/compositor/src/session_lock.rs +++ b/compositor/src/session_lock.rs @@ -289,7 +289,11 @@ impl Ferese { surface.alive().then(|| { ( surface.wl_surface().clone(), - self.space.output_geometry(output).unwrap().loc.to_f64(), + self.space + .output_geometry(output) + .expect("a lock surface is assigned to a mapped output with geometry") + .loc + .to_f64(), ) }) } @@ -366,7 +370,10 @@ impl Ferese { .map(|(_, surface)| surface) }) .map(|surface| surface.wl_surface().clone()); - let keyboard = self.seat.get_keyboard().unwrap(); + let keyboard = self + .seat + .get_keyboard() + .expect("the compositor seat is initialized with a keyboard"); if keyboard.current_focus() != surface { keyboard.set_focus(self, surface, SERIAL_COUNTER.next_serial()); } @@ -493,7 +500,10 @@ impl SessionLockHandler for Ferese { touch.unset_grab(self); touch.cancel(self); } - let keyboard = self.seat.get_keyboard().unwrap(); + let keyboard = self + .seat + .get_keyboard() + .expect("the compositor seat is initialized with a keyboard"); keyboard.unset_grab(self); keyboard.set_focus(self, None, serial); self.lock_input_activity(); diff --git a/compositor/src/state/animation.rs b/compositor/src/state/animation.rs index bb262c0..e587e27 100644 --- a/compositor/src/state/animation.rs +++ b/compositor/src/state/animation.rs @@ -200,9 +200,9 @@ impl Ferese { let swipe = self .viewports .get_mut(&workspace) - .unwrap() + .expect("an active workspace gesture retains its viewport state") .end_gesture(None) - .unwrap(); + .expect("an active workspace gesture retains its viewport state"); self.presentation_dependencies .restore_viewport(swipe.workspace, swipe.dependencies); self.release_presentation_dependencies(); @@ -360,10 +360,12 @@ impl Ferese { } } } - let expired = self - .windows - .resizing() - .any(|id| self.windows.transaction(id).unwrap().expired(now)); + let expired = self.windows.resizing().any(|id| { + self.windows + .transaction(id) + .expect("the resize candidate retains its active presentation transaction") + .expired(now) + }); self.windows.expire_transactions(now); if expired { self.release_presentation_dependencies(); diff --git a/compositor/src/state/capture_privacy.rs b/compositor/src/state/capture_privacy.rs index 8916d12..28f70c8 100644 --- a/compositor/src/state/capture_privacy.rs +++ b/compositor/src/state/capture_privacy.rs @@ -30,7 +30,9 @@ impl Ferese { let Some(data) = states.data_map.get::() else { return (true, None); }; - let data = data.lock().unwrap(); + let data = data + .lock() + .expect("window metadata must remain consistent during privacy checks"); let rule = crate::window_rules::resolve( &self.window_rules, data.app_id.as_deref(), diff --git a/compositor/src/state/floating.rs b/compositor/src/state/floating.rs index d8d0b94..b4388bf 100644 --- a/compositor/src/state/floating.rs +++ b/compositor/src/state/floating.rs @@ -156,7 +156,10 @@ impl Ferese { self.windows.update(window, |w| w.maximized = false); } if self.workspaces.placement(window) == Some(WindowPlacement::Tiled) { - let workspace_id = self.workspaces.workspace_for_window(window).unwrap(); + let workspace_id = self + .workspaces + .workspace_for_window(window) + .expect("a tiled window belongs to a workspace"); if let Some(workspace) = self.workspaces.workspace_mut(workspace_id) && let WorkspaceLayout::Scrolling(layout) = &mut workspace.layout { diff --git a/compositor/src/state/layout.rs b/compositor/src/state/layout.rs index c5eea39..4d109ca 100644 --- a/compositor/src/state/layout.rs +++ b/compositor/src/state/layout.rs @@ -657,7 +657,11 @@ impl Ferese { self.resize_metrics.begin( id, self.workspaces.workspace_for_window(id), - &self.windows.record(id).unwrap().app_id, + &self + .windows + .record(id) + .expect("the selected window has a registered lifecycle record") + .app_id, now, ); self.windows.set_transaction( diff --git a/compositor/src/state/lifecycle.rs b/compositor/src/state/lifecycle.rs index 7d59a62..3db6801 100644 --- a/compositor/src/state/lifecycle.rs +++ b/compositor/src/state/lifecycle.rs @@ -57,8 +57,13 @@ impl Ferese { let Some(mut presentation) = self.current_window_presentation(id) else { return; }; - let record = self.windows.record(id).unwrap(); - let geometry = record.geometry.unwrap(); + let record = self + .windows + .record(id) + .expect("the selected window has a registered lifecycle record"); + let geometry = record + .geometry + .expect("presented windows retain their resolved geometry"); let decorations = geometry.decorations.clamp(0.0, 1.0); let handoff_size = (!presentation.scale_content).then(|| { presentation diff --git a/compositor/src/state/navigation.rs b/compositor/src/state/navigation.rs index e0b033c..e0911ef 100644 --- a/compositor/src/state/navigation.rs +++ b/compositor/src/state/navigation.rs @@ -112,7 +112,7 @@ impl Ferese { if let Some(workspace) = self.focus_swipe().map(|swipe| swipe.workspace) { self.viewports .get_mut(&workspace) - .unwrap() + .expect("an active workspace gesture retains its viewport state") .update_gesture(progress); } @@ -145,9 +145,9 @@ impl Ferese { let swipe = self .viewports .get_mut(&workspace) - .unwrap() + .expect("an active workspace gesture retains its viewport state") .end_gesture(velocity) - .unwrap(); + .expect("an active workspace gesture retains its viewport state"); self.presentation_dependencies .restore_viewport(workspace, swipe.dependencies.clone()); let neighbor = self.output_bounds().and_then(|bounds| { @@ -653,7 +653,10 @@ impl Ferese { { slide.release_with_velocity(true, self.swipe.release_velocity / self.animation_speed); self.last_animation_tick = Instant::now(); - self.workspace_slides.insert(owner, previous_slide.unwrap()); + self.workspace_slides.insert( + owner, + previous_slide.expect("the reversal branch requires an existing workspace slide"), + ); return; } let slide_direction = slide_direction.or_else(|| { diff --git a/compositor/src/state/presentation_dependencies.rs b/compositor/src/state/presentation_dependencies.rs index 634854b..d6ba5ba 100644 --- a/compositor/src/state/presentation_dependencies.rs +++ b/compositor/src/state/presentation_dependencies.rs @@ -119,7 +119,11 @@ fn tree_reflow_groups( groups.insert(*id, group); let mut frontier = vec![*id]; while let Some(id) = frontier.pop() { - let bounds = moving.iter().find(|(window, _)| *window == id).unwrap().1; + let bounds = moving + .iter() + .find(|(window, _)| *window == id) + .expect("the dependency frontier contains only moving window IDs") + .1; for (other, candidate) in &moving { if !groups.contains_key(other) && bounds[0].max(candidate[0]) + 0.001 < bounds[2].min(candidate[2]) @@ -448,7 +452,10 @@ impl Ferese { if self.workspaces.workspace_for_window(*slow) != Some(workspace_id) { continue; } - let transaction = *self.windows.transaction(slow).unwrap(); + let transaction = *self + .windows + .transaction(slow) + .expect("the resize candidate retains its active presentation transaction"); let commit = ClientCommit { window: *slow, serial: transaction.serial(), @@ -504,7 +511,14 @@ impl Ferese { self.workspaces.workspace_for_window(**id) == Some(workspace.id) && layout.contains(**id) }) - .map(|id| (*id, *self.windows.transaction(id).unwrap())) + .map(|id| { + ( + *id, + *self.windows.transaction(id).expect( + "the resize candidate retains its active presentation transaction", + ), + ) + }) .collect::>(), previous_viewport.remove(&workspace.id).unwrap_or_default(), ); diff --git a/compositor/src/state/reconciliation.rs b/compositor/src/state/reconciliation.rs index 487a17e..0b70c1c 100644 --- a/compositor/src/state/reconciliation.rs +++ b/compositor/src/state/reconciliation.rs @@ -101,7 +101,12 @@ impl Ferese { layout: !plan.affected_outputs.is_empty(), focus: plan.focus_changed, shell: !plan.affected_outputs.is_empty(), - outputs: self.desktop_transition.as_ref().unwrap().redraw.clone(), + outputs: self + .desktop_transition + .as_ref() + .expect("desktop transition must be started before publication or retirement") + .redraw + .clone(), }; let floats = self .windows @@ -213,7 +218,10 @@ impl Ferese { } pub(crate) fn finish_desktop_transition(&mut self, mut changes: DesktopChanges) { - self.desktop_transition.as_mut().unwrap().publishing = true; + self.desktop_transition + .as_mut() + .expect("desktop transition must be started before publication or retirement") + .publishing = true; let activity_output = self.space.outputs().next().map(|output| output.name()); if let Some(output) = activity_output && (self.activities.store.attach_output(&output) @@ -230,7 +238,10 @@ impl Ferese { self.reconcile_layer_keyboard_focus(); self.refresh_lock_outputs(); self.refresh_idle_inhibition(); - let batch = self.desktop_transition.take().unwrap(); + let batch = self + .desktop_transition + .take() + .expect("desktop transition must be started before publication or retirement"); changes.outputs.extend(batch.redraw); // Animation/layout helpers may request snapshots, but only this point // can emit the complete topology generation. diff --git a/compositor/src/state/window_registry.rs b/compositor/src/state/window_registry.rs index 9847628..9c12e66 100644 --- a/compositor/src/state/window_registry.rs +++ b/compositor/src/state/window_registry.rs @@ -236,7 +236,10 @@ impl WindowRegistry { if let Some(surface) = surface { self.surface_ids.insert(surface.clone(), id); - self.records.get_mut(&id).unwrap().surface = Some(surface.clone()); + self.records + .get_mut(&id) + .expect("the selected window has a registered lifecycle record") + .surface = Some(surface.clone()); self.refresh_metadata(&surface); } } @@ -245,14 +248,17 @@ impl WindowRegistry { let Some(id) = self.id_for_surface(surface) else { return false; }; - let record = self.records.get_mut(&id).unwrap(); + let record = self + .records + .get_mut(&id) + .expect("the selected window has a registered lifecycle record"); with_states(surface, |states| { let attributes = states .data_map .get::() - .unwrap() + .expect("an XDG toplevel must retain its Smithay surface data") .lock() - .unwrap(); + .expect("Smithay surface state must remain consistent across callbacks"); let app_id = attributes.app_id.as_deref().unwrap_or_default(); let title = attributes.title.as_deref().unwrap_or_default(); let changed = record.app_id != app_id || record.title != title; diff --git a/compositor/src/state/windows.rs b/compositor/src/state/windows.rs index d9bdb68..f5b3d0c 100644 --- a/compositor/src/state/windows.rs +++ b/compositor/src/state/windows.rs @@ -28,9 +28,9 @@ impl Ferese { let attributes = states .data_map .get::() - .unwrap() + .expect("an XDG toplevel must retain its Smithay surface data") .lock() - .unwrap(); + .expect("Smithay surface state must remain consistent across callbacks"); ( attributes.app_id.clone(), attributes.title.clone(), @@ -350,9 +350,9 @@ impl Ferese { let attributes = states .data_map .get::() - .unwrap() + .expect("an XDG toplevel must retain its Smithay surface data") .lock() - .unwrap(); + .expect("Smithay surface state must remain consistent across callbacks"); ( attributes.app_id.clone(), attributes.title.clone(), diff --git a/compositor/src/theme.rs b/compositor/src/theme.rs index eb67f3f..4032c64 100644 --- a/compositor/src/theme.rs +++ b/compositor/src/theme.rs @@ -90,7 +90,9 @@ fn auto_context() -> ferese_config::theme::AutoContext { Mutex::new(value) }); ferese_config::theme::AutoContext { - system: *appearance.lock().unwrap(), + system: *appearance + .lock() + .expect("theme watcher state must remain consistent across workers"), } } @@ -264,7 +266,10 @@ impl Engine { directory.to_owned() }) .collect(); - *self.paths.lock().unwrap() = targets; + *self + .paths + .lock() + .expect("theme watcher state must remain consistent across workers") = targets; if let Some(watcher) = &mut self.watcher { for directory in self.watched_directories.difference(&directories) { let _ = watcher.unwatch(directory); @@ -349,11 +354,14 @@ pub(crate) fn init( let (sender, events) = mpsc::sync_channel(1); let paths = state.theme_engine.paths.clone(); if let Some(config) = crate::config::config_path() { - paths.lock().unwrap().insert(config); + paths + .lock() + .expect("theme watcher state must remain consistent across workers") + .insert(config); } paths .lock() - .unwrap() + .expect("theme watcher state must remain consistent across workers") .insert(PathBuf::from("/etc/localtime")); let wakeup = state.loop_signal.clone(); let system_sender = sender.clone(); @@ -385,7 +393,9 @@ pub(crate) fn init( .output() { if let Some(state) = SYSTEM_APPEARANCE.get() { - *state.lock().unwrap() = + *state + .lock() + .expect("theme watcher state must remain consistent across workers") = system_appearance_value(&String::from_utf8_lossy(&output.stdout)); } let _ = system_sender.try_send(()); @@ -398,7 +408,9 @@ pub(crate) fn init( { let value = system_appearance_value(&line); if let Some(state) = SYSTEM_APPEARANCE.get() { - *state.lock().unwrap() = value; + *state.lock().expect( + "theme watcher state must remain consistent across workers", + ) = value; } let _ = system_sender.try_send(()); system_wakeup.wakeup(); @@ -434,7 +446,7 @@ fn file_watcher( && event.paths.iter().any(|path| { paths .lock() - .unwrap() + .expect("theme watcher state must remain consistent across workers") .iter() .any(|target| target == path || target.starts_with(path)) }) @@ -575,7 +587,11 @@ impl Ferese { } } if start { - let pending = self.theme_engine.pending.take().unwrap(); + let pending = self + .theme_engine + .pending + .take() + .expect("the transition starts only when a theme candidate is pending"); let from = self.theme_engine.live.presented.clone(); let previous_wallpaper = pending .wallpaper diff --git a/compositor/src/wallpaper.rs b/compositor/src/wallpaper.rs index 911ebbd..63ecdc5 100644 --- a/compositor/src/wallpaper.rs +++ b/compositor/src/wallpaper.rs @@ -114,7 +114,15 @@ impl WallpaperState { let decode_failed = config.path.is_some() && !owned; let receiver = if owned { - Some(decoder.submit(config.path.clone().unwrap(), wakeup.clone())) + Some( + decoder.submit( + config + .path + .clone() + .expect("owned wallpaper decoding requires a configured image path"), + wakeup.clone(), + ), + ) } else { decoder.cancel_pending(); None diff --git a/compositor/src/wallpaper/decoder.rs b/compositor/src/wallpaper/decoder.rs index b6b3395..65e05bb 100644 --- a/compositor/src/wallpaper/decoder.rs +++ b/compositor/src/wallpaper/decoder.rs @@ -30,15 +30,21 @@ impl Decoder { loop { let (queue, ready) = &*worker; let mut queue = ready - .wait_while(queue.lock().unwrap(), |queue| { - !queue.closed && queue.pending.is_none() - }) - .unwrap(); + .wait_while( + queue.lock().expect( + "wallpaper decode queue must remain consistent across workers", + ), + |queue| !queue.closed && queue.pending.is_none(), + ) + .expect("wallpaper decode queue must remain consistent across workers"); if queue.closed { break; } - let request = queue.pending.take().unwrap(); + let request = queue + .pending + .take() + .expect("the decode wait completed with a pending wallpaper request"); drop(queue); let _ = request.result.send(load(&request.path)); if let Some(wakeup) = request.wakeup { @@ -57,7 +63,10 @@ impl Decoder { ) -> mpsc::Receiver> { let (result, receiver) = mpsc::channel(); let (queue, ready) = &*self.shared; - queue.lock().unwrap().pending = Some(Request { + queue + .lock() + .expect("wallpaper decode queue must remain consistent across workers") + .pending = Some(Request { path, result, wakeup, @@ -67,14 +76,20 @@ impl Decoder { } pub fn cancel_pending(&self) { - self.shared.0.lock().unwrap().pending = None; + self.shared + .0 + .lock() + .expect("wallpaper decode queue must remain consistent across workers") + .pending = None; } } impl Drop for Decoder { fn drop(&mut self) { let (queue, ready) = &*self.shared; - let mut queue = queue.lock().unwrap(); + let mut queue = queue + .lock() + .expect("wallpaper decode queue must remain consistent across workers"); queue.closed = true; queue.pending = None; ready.notify_one(); diff --git a/compositor/src/winit.rs b/compositor/src/winit.rs index b934118..00e4a8e 100644 --- a/compositor/src/winit.rs +++ b/compositor/src/winit.rs @@ -185,7 +185,7 @@ pub fn init(event_loop: &mut EventLoop, state: &mut Ferese) -> Result<() let _ = renderer .render( framebuffer, - output.current_mode().unwrap().size, + output.current_mode().expect("a rendered output must have a current mode").size, output.current_transform(), )? .finish()?; diff --git a/crates/ferese-animation/src/gesture.rs b/crates/ferese-animation/src/gesture.rs index 8c57199..c76c536 100644 --- a/crates/ferese-animation/src/gesture.rs +++ b/crates/ferese-animation/src/gesture.rs @@ -55,7 +55,7 @@ impl VelocityTracker { for col in 0..=degree { let pivot = (col..=degree) .max_by(|&a, &b| matrix[a][col].abs().total_cmp(&matrix[b][col].abs())) - .unwrap(); + .expect("the inclusive pivot range contains at least the current column"); matrix.swap(col, pivot); let divisor = matrix[col][col]; if divisor.abs() < 1e-12 { diff --git a/crates/ferese-config/src/activities.rs b/crates/ferese-config/src/activities.rs index 0a0cbc0..62c3ad8 100644 --- a/crates/ferese-config/src/activities.rs +++ b/crates/ferese-config/src/activities.rs @@ -310,13 +310,18 @@ pub(crate) fn parse(node: &KdlNode) -> Result { .into_iter() .filter(|rule| !names.contains(&rule.name)), ); - values.insert("rules".into(), serde_json::to_value(rules).unwrap()); + values.insert( + "rules".into(), + serde_json::to_value(rules) + .expect("typed configuration and IPC values must serialize to JSON"), + ); let settings: Settings = serde_json::from_value(Value::Object(values)).map_err(|error| diagnostic(node, error))?; settings .validate() .map_err(|error| diagnostic(node, error))?; - Ok(serde_json::to_value(settings).unwrap()) + Ok(serde_json::to_value(settings) + .expect("typed configuration and IPC values must serialize to JSON")) } fn builtins() -> Vec { [ diff --git a/crates/ferese-config/src/document.rs b/crates/ferese-config/src/document.rs index 551ca1a..e132fa5 100644 --- a/crates/ferese-config/src/document.rs +++ b/crates/ferese-config/src/document.rs @@ -166,7 +166,10 @@ fn object(doc: &KdlDocument, parent: &str) -> Result, Error> let records = result .entry(key) .or_insert_with(|| Value::Array(Vec::new())); - records.as_array_mut().unwrap().push(value); + records + .as_array_mut() + .expect("repeated KDL records are stored as arrays") + .push(value); } else { insert(&mut result, key, value)?; } @@ -255,7 +258,10 @@ pub(crate) fn node_value(node: &KdlNode, key: &str, parent: &str) -> Result Result { } else if let Some(i) = n.as_u64() { KdlValue::Integer(i.into()) } else { - KdlValue::Float(n.as_f64().unwrap()) + KdlValue::Float( + n.as_f64() + .expect("JSON numbers fit the KDL numeric representation"), + ) } } _ => return Err(Error("Expected a scalar value".into())), diff --git a/crates/ferese-config/src/families.rs b/crates/ferese-config/src/families.rs index 21ac54f..c6ed147 100644 --- a/crates/ferese-config/src/families.rs +++ b/crates/ferese-config/src/families.rs @@ -64,10 +64,12 @@ pub fn builtins() -> Vec { id: (*id).into(), name: (*name).into(), light: Some(Palette::from( - &crate::theme::preset(light, Appearance::Light).unwrap(), + &crate::theme::preset(light, Appearance::Light) + .expect("built-in theme families must reference registered presets"), )), dark: Some(Palette::from( - &crate::theme::preset(dark, Appearance::Dark).unwrap(), + &crate::theme::preset(dark, Appearance::Dark) + .expect("built-in theme families must reference registered presets"), )), }) .collect() diff --git a/crates/ferese-config/src/theme.rs b/crates/ferese-config/src/theme.rs index 66dff31..21f820e 100644 --- a/crates/ferese-config/src/theme.rs +++ b/crates/ferese-config/src/theme.rs @@ -552,8 +552,10 @@ fn token_value(tokens: &Tokens) -> Result { let mut value = serde_json::to_value(tokens).map_err(|error| error.to_string())?; // Keep authoring defaults available for merging. The wire format omits // automatic styles so older snapshots still round-trip unchanged. - value["focus_ring"]["style"] = serde_json::to_value(tokens.focus_ring.style).unwrap(); - value["border"]["style"] = serde_json::to_value(tokens.border.style).unwrap(); + value["focus_ring"]["style"] = serde_json::to_value(tokens.focus_ring.style) + .expect("typed configuration and IPC values must serialize to JSON"); + value["border"]["style"] = serde_json::to_value(tokens.border.style) + .expect("typed configuration and IPC values must serialize to JSON"); Ok(value) } @@ -673,8 +675,16 @@ pub fn preset(name: &str, appearance: Appearance) -> Result { } else { [1.; 4] }; - t.colors.surface_raised = hex(blend(rgba(base).unwrap(), shade, 0.055)); - t.colors.application_background = hex(blend(rgba(base).unwrap(), shade, 0.025)); + t.colors.surface_raised = hex(blend( + rgba(base).expect("resolved theme colors must be valid hexadecimal RGBA"), + shade, + 0.055, + )); + t.colors.application_background = hex(blend( + rgba(base).expect("resolved theme colors must be valid hexadecimal RGBA"), + shade, + 0.025, + )); t.colors.text_primary = text.into(); t.colors.text_muted = muted.into(); t.colors.accent = accent.into(); @@ -708,8 +718,10 @@ pub fn authored_warnings(tokens: &Tokens) -> Vec { ), ]; let check = |foreground: &str, background: &str, minimum: f64| { - let foreground = rgba(foreground).unwrap(); - let background = rgba(background).unwrap(); + let foreground = + rgba(foreground).expect("resolved theme colors must be valid hexadecimal RGBA"); + let background = + rgba(background).expect("resolved theme colors must be valid hexadecimal RGBA"); contrast(composite(foreground, background), background) >= minimum }; for (name, background) in surfaces { @@ -813,7 +825,8 @@ fn transform(t: &mut Tokens, accessibility: &Accessibility, warnings: &mut Vec) { }; let backgrounds = [ - rgba(&tokens.colors.surface_base).unwrap(), - rgba(&tokens.colors.surface_raised).unwrap(), - rgba(&tokens.colors.application_background).unwrap(), - rgba(&tokens.surface.bar.background).unwrap(), + rgba(&tokens.colors.surface_base) + .expect("resolved theme colors must be valid hexadecimal RGBA"), + rgba(&tokens.colors.surface_raised) + .expect("resolved theme colors must be valid hexadecimal RGBA"), + rgba(&tokens.colors.application_background) + .expect("resolved theme colors must be valid hexadecimal RGBA"), + rgba(&tokens.surface.bar.background) + .expect("resolved theme colors must be valid hexadecimal RGBA"), ]; for (paint, color, endpoint, focus) in [ ( @@ -915,7 +935,11 @@ fn resolve_gradients(tokens: &mut Tokens, preset_end: Option<&str>) { let to = endpoint.map_or_else(|| shade(color), str::to_owned); let to = if focus { - hex(readable_across(rgba(&to).unwrap(), &backgrounds, 3.)) + hex(readable_across( + rgba(&to).expect("resolved theme colors must be valid hexadecimal RGBA"), + &backgrounds, + 3., + )) } else { to }; @@ -931,10 +955,14 @@ fn resolve_material_contrast(tokens: &mut Tokens, minimum: f64) { if tokens.material.style == "solid" { return; } - let surface = rgba(&tokens.colors.surface_base).unwrap(); - let bar = rgba(&tokens.surface.bar.background).unwrap(); - let raised = rgba(&tokens.colors.surface_raised).unwrap(); - let app = rgba(&tokens.colors.application_background).unwrap(); + let surface = rgba(&tokens.colors.surface_base) + .expect("resolved theme colors must be valid hexadecimal RGBA"); + let bar = rgba(&tokens.surface.bar.background) + .expect("resolved theme colors must be valid hexadecimal RGBA"); + let raised = rgba(&tokens.colors.surface_raised) + .expect("resolved theme colors must be valid hexadecimal RGBA"); + let app = rgba(&tokens.colors.application_background) + .expect("resolved theme colors must be valid hexadecimal RGBA"); let initial = tokens.material.opacity * tokens.material.tint_strength; let readable = |backgrounds: &[[f64; 4]]| { [[0., 0., 0., 1.], [1.; 4]].into_iter().any(|text| { @@ -962,7 +990,11 @@ fn resolve_material_contrast(tokens: &mut Tokens, minimum: f64) { (&mut tokens.surface.bar.text_primary, bar.as_slice()), (&mut tokens.surface.bar.text_muted, bar.as_slice()), ] { - *color = hex(readable_across(rgba(color).unwrap(), backgrounds, minimum)); + *color = hex(readable_across( + rgba(color).expect("resolved theme colors must be valid hexadecimal RGBA"), + backgrounds, + minimum, + )); } } diff --git a/crates/ferese-core/src/output.rs b/crates/ferese-core/src/output.rs index 5506926..947bf49 100644 --- a/crates/ferese-core/src/output.rs +++ b/crates/ferese-core/src/output.rs @@ -184,7 +184,15 @@ impl OutputWorkspaceMap { let mut removed = self .connected_outputs() .filter(|id| !desired.contains_key(id)) - .map(|id| (id, outputs.outputs.remove(&id).unwrap())) + .map(|id| { + ( + id, + outputs + .outputs + .remove(&id) + .expect("workspace reconciliation only accesses connected outputs"), + ) + }) .collect::>(); // Preserve the original focused workspace when several outputs vanish. removed.sort_by_key(|(id, _)| (Some(*id) == self.focused, *id)); @@ -196,7 +204,10 @@ impl OutputWorkspaceMap { // against the final inventory, where that donor no longer needs it. for (id, record) in returning { let reclaimed = outputs.reclaim(id, &record); - let state = outputs.outputs.get_mut(&id).unwrap(); + let state = outputs + .outputs + .get_mut(&id) + .expect("workspace reconciliation only accesses connected outputs"); state.workspaces.extend(reclaimed.iter().copied()); if reclaimed.contains(&record.active) { state.activate(record.active); @@ -635,7 +646,7 @@ impl OutputWorkspaceMap { self.homes.insert(workspace, output); self.outputs .get_mut(&output) - .unwrap() + .expect("workspace reconciliation only accesses connected outputs") .workspaces .insert(workspace); self.bump_revision(workspace); diff --git a/crates/ferese-core/src/workspace.rs b/crates/ferese-core/src/workspace.rs index eb1a61f..4035607 100644 --- a/crates/ferese-core/src/workspace.rs +++ b/crates/ferese-core/src/workspace.rs @@ -544,7 +544,10 @@ impl WorkspaceSet { replacements.push((workspace.id, layout)); } for (id, layout) in replacements { - self.workspaces.get_mut(&id).unwrap().layout = layout; + self.workspaces + .get_mut(&id) + .expect("replacement layouts were built from existing workspace IDs") + .layout = layout; } self.default_layout_mode = mode; self.default_column_width = width; diff --git a/crates/ferese-core/src/workspace_policy.rs b/crates/ferese-core/src/workspace_policy.rs index 01a7a32..40fa328 100644 --- a/crates/ferese-core/src/workspace_policy.rs +++ b/crates/ferese-core/src/workspace_policy.rs @@ -41,7 +41,10 @@ impl OutputWorkspaceMap { id, output, index: index as u32 + 1, - window_count: workspaces.workspace(id).unwrap().window_count(), + window_count: workspaces + .workspace(id) + .expect("workspace views enumerate registered workspace IDs") + .window_count(), visible, focused: visible && self.focused_output() == Some(output), } diff --git a/crates/ferese-layout/src/scrolling.rs b/crates/ferese-layout/src/scrolling.rs index 4c93d8e..48fd59c 100644 --- a/crates/ferese-layout/src/scrolling.rs +++ b/crates/ferese-layout/src/scrolling.rs @@ -666,7 +666,10 @@ impl ScrollingLayout { let viewport_center = bounds.x + bounds.width / 2.0; let window_center = rect.x + rect.width / 2.0; self.viewport_x += window_center - viewport_center; - let active = self.window_location(window).unwrap().0; + let active = self + .window_location(window) + .expect("geometry resolution confirmed the window belongs to a column") + .0; let inner = finite_nonnegative(gaps.inner); let positions = self .columns @@ -678,7 +681,10 @@ impl ScrollingLayout { Some(position) }) .collect::>(); - let viewport_width = self.last_column_sizing.unwrap().viewport_width; + let viewport_width = self + .last_column_sizing + .expect("geometry resolution records the column sizing before centering") + .viewport_width; self.record_viewport_basis( &positions, inner, diff --git a/crates/ferese-panel/src/motion.rs b/crates/ferese-panel/src/motion.rs index 1cd5d8e..46057f3 100644 --- a/crates/ferese-panel/src/motion.rs +++ b/crates/ferese-panel/src/motion.rs @@ -49,9 +49,9 @@ impl Geometry { fn from_values(values: [f64; COUNT]) -> Self { Self { bounds: Rect::new(values[0], values[1], values[2], values[3]), - radii: values[4..8].try_into().unwrap(), - margin: values[8..12].try_into().unwrap(), - thickness: values[12..16].try_into().unwrap(), + radii: [values[4], values[5], values[6], values[7]], + margin: [values[8], values[9], values[10], values[11]], + thickness: [values[12], values[13], values[14], values[15]], gap: values[16], fusion: values[17], } diff --git a/crates/ferese-panel/src/outline.rs b/crates/ferese-panel/src/outline.rs index 6285ecc..1213295 100644 --- a/crates/ferese-panel/src/outline.rs +++ b/crates/ferese-panel/src/outline.rs @@ -211,10 +211,14 @@ pub fn notch_border( let mut paths = Vec::new(); while let Some((a, b)) = segments.pop() { let mut path = vec![a, b]; - while let Some(index) = segments - .iter() - .position(|&(a, _)| close(a, *path.last().unwrap())) - { + while let Some(index) = segments.iter().position(|&(a, _)| { + close( + a, + *path + .last() + .expect("an outline path starts with two points and only grows"), + ) + }) { path.push(segments.swap_remove(index).1); } while let Some(index) = segments.iter().position(|&(_, b)| close(b, path[0])) { diff --git a/crates/ferese-panel/src/panel/layout.rs b/crates/ferese-panel/src/panel/layout.rs index a29ca69..43ffccc 100644 --- a/crates/ferese-panel/src/panel/layout.rs +++ b/crates/ferese-panel/src/panel/layout.rs @@ -183,10 +183,10 @@ pub fn resolve_with_gap( result.minimum_width = minimum.required_width(); // Flexible content yields space without changing the stored item preference. for (_, item) in &candidates { - let Some(measured) = measurements - .get(&item.id) - .filter(|measured| measured.minimum.is_some()) - else { + let Some(measured) = measurements.get(&item.id) else { + continue; + }; + let Some(minimum) = measured.minimum else { continue; }; if fits(&result) { @@ -199,7 +199,7 @@ pub fn resolve_with_gap( else { continue; }; - let minimum = measured.minimum.unwrap().min(width); + let minimum = minimum.min(width); result.items.insert( measured.id.clone(), Placement::Visible { @@ -352,10 +352,14 @@ pub fn resolve_with_gap( let mut high = maximum; for _ in 0..20 { let width = (low + high) * 0.5; - *result.items.get_mut(&measured.id).unwrap() = Placement::Visible { - representation, - width, - }; + *result + .items + .get_mut(&measured.id) + .expect("visible items retain their allocation during width refinement") = + Placement::Visible { + representation, + width, + }; result.update_widths(panel, overflow_width); if fits(&result) { low = width @@ -363,14 +367,21 @@ pub fn resolve_with_gap( high = width } } - *result.items.get_mut(&measured.id).unwrap() = Placement::Visible { - representation, - width: low, - }; + *result + .items + .get_mut(&measured.id) + .expect("visible items retain their allocation during width refinement") = + Placement::Visible { + representation, + width: low, + }; } - result - .overflow - .sort_by_key(|id| ordered.iter().position(|item| &item.id == id).unwrap()); + result.overflow.sort_by_key(|id| { + ordered + .iter() + .position(|item| &item.id == id) + .expect("overflow contains only authored item IDs") + }); result.update_widths(panel, overflow_width); result } diff --git a/crates/ferese-theme-model/src/color.rs b/crates/ferese-theme-model/src/color.rs index 1a55a3b..1c86f8f 100644 --- a/crates/ferese-theme-model/src/color.rs +++ b/crates/ferese-theme-model/src/color.rs @@ -108,7 +108,8 @@ pub fn readable_across( white }; for step in 1..=100 { - let color = rgba(&hex(blend(preferred, target, f64::from(step) / 100.))).unwrap(); + let color = rgba(&hex(blend(preferred, target, f64::from(step) / 100.))) + .expect("hex output must be a valid RGBA color"); if score(color) >= minimum { return color; } @@ -137,9 +138,12 @@ fn material_foreground(preferred: [f64; 4], backgrounds: [[f64; 4]; 2]) -> [f64; let outside = luminance(result) < luminance(backgrounds[0]) || luminance(result) > luminance(backgrounds[1]); if outside - && backgrounds - .iter() - .all(|background| contrast(rgba(&hex(result)).unwrap(), *background) >= 4.5) + && backgrounds.iter().all(|background| { + contrast( + rgba(&hex(result)).expect("hex output must be a valid RGBA color"), + *background, + ) >= 4.5 + }) { result } else { @@ -161,8 +165,8 @@ fn transition_paint(from: &Paint, to: &Paint, from_solid: &str, to_solid: &str, let (a, b) = endpoints(from, from_solid); let (c, d) = endpoints(to, to_solid); let color = |a: &str, b: &str| { - let mut a = rgba(a).unwrap(); - let mut b = rgba(b).unwrap(); + let mut a = rgba(a).expect("resolved theme colors must be valid hexadecimal RGBA"); + let mut b = rgba(b).expect("resolved theme colors must be valid hexadecimal RGBA"); for channel in 0..3 { a[channel] *= a[3]; b[channel] *= b[3]; @@ -180,13 +184,13 @@ fn transition_paint(from: &Paint, to: &Paint, from_solid: &str, to_solid: &str, let from_angle = from .effective_gradient() .or(to.effective_gradient()) - .unwrap() + .expect("at least one transition endpoint has a gradient") .angle .rem_euclid(360.); let to_angle = to .effective_gradient() .or(from.effective_gradient()) - .unwrap() + .expect("at least one transition endpoint has a gradient") .angle .rem_euclid(360.); let delta = (to_angle - from_angle + 180.).rem_euclid(360.) - 180.; @@ -202,6 +206,10 @@ fn transition_paint(from: &Paint, to: &Paint, from_solid: &str, to_solid: &str, impl ResolvedTheme { /// Bound contrast across all backdrops instead of interpolating foregrounds. + /// + /// # Panics + /// Both themes must contain valid hexadecimal colors and gradient endpoints, + /// as supplied by configuration resolution. Invalid colors panic during blending. pub fn transition(&self, to: &Self, progress: f64) -> Self { if to.reduced_motion || self.accessibility != to.accessibility || progress >= 1.0 { return to.clone(); @@ -212,7 +220,13 @@ impl ResolvedTheme { let p = progress.clamp(0., 1.); let p = p * p * (3. - 2. * p); let mut frame = to.clone(); - let mix = |a: &str, b: &str| hex(blend(rgba(a).unwrap(), rgba(b).unwrap(), p)); + let mix = |a: &str, b: &str| { + hex(blend( + rgba(a).expect("resolved theme colors must be valid hexadecimal RGBA"), + rgba(b).expect("resolved theme colors must be valid hexadecimal RGBA"), + p, + )) + }; frame.tokens.colors.surface_base = mix( &self.tokens.colors.surface_base, &to.tokens.colors.surface_base, @@ -256,9 +270,12 @@ impl ResolvedTheme { frame.appearance = preferred.appearance; if self.appearance == to.appearance { let body = [ - rgba(&frame.tokens.colors.surface_base).unwrap(), - rgba(&frame.tokens.colors.surface_raised).unwrap(), - rgba(&frame.tokens.colors.application_background).unwrap(), + rgba(&frame.tokens.colors.surface_base) + .expect("resolved theme colors must be valid hexadecimal RGBA"), + rgba(&frame.tokens.colors.surface_raised) + .expect("resolved theme colors must be valid hexadecimal RGBA"), + rgba(&frame.tokens.colors.application_background) + .expect("resolved theme colors must be valid hexadecimal RGBA"), ]; let minimum = if to.accessibility.increase_contrast { 7.0 @@ -266,36 +283,46 @@ impl ResolvedTheme { 4.5 }; frame.tokens.colors.text_primary = hex(readable_across( - rgba(&preferred.tokens.colors.text_primary).unwrap(), + rgba(&preferred.tokens.colors.text_primary) + .expect("resolved theme colors must be valid hexadecimal RGBA"), &body, minimum, )); frame.tokens.colors.text_muted = hex(readable_across( - rgba(&preferred.tokens.colors.text_muted).unwrap(), + rgba(&preferred.tokens.colors.text_muted) + .expect("resolved theme colors must be valid hexadecimal RGBA"), &body, minimum, )); - let bar = rgba(&frame.tokens.surface.bar.background).unwrap(); + let bar = rgba(&frame.tokens.surface.bar.background) + .expect("resolved theme colors must be valid hexadecimal RGBA"); frame.tokens.surface.bar.text_primary = hex(readable( - rgba(&preferred.tokens.surface.bar.text_primary).unwrap(), + rgba(&preferred.tokens.surface.bar.text_primary) + .expect("resolved theme colors must be valid hexadecimal RGBA"), bar, minimum, )); frame.tokens.surface.bar.text_muted = hex(readable( - rgba(&preferred.tokens.surface.bar.text_muted).unwrap(), + rgba(&preferred.tokens.surface.bar.text_muted) + .expect("resolved theme colors must be valid hexadecimal RGBA"), bar, minimum, )); frame.tokens.colors.on_accent = hex(readable( - rgba(&preferred.tokens.colors.on_accent).unwrap(), - rgba(&frame.tokens.colors.accent).unwrap(), + rgba(&preferred.tokens.colors.on_accent) + .expect("resolved theme colors must be valid hexadecimal RGBA"), + rgba(&frame.tokens.colors.accent) + .expect("resolved theme colors must be valid hexadecimal RGBA"), 4.5, )); return frame; } - let base = rgba(&frame.tokens.colors.surface_base).unwrap(); - let raised = rgba(&frame.tokens.colors.surface_raised).unwrap(); - let app = rgba(&frame.tokens.colors.application_background).unwrap(); + let base = rgba(&frame.tokens.colors.surface_base) + .expect("resolved theme colors must be valid hexadecimal RGBA"); + let raised = rgba(&frame.tokens.colors.surface_raised) + .expect("resolved theme colors must be valid hexadecimal RGBA"); + let app = rgba(&frame.tokens.colors.application_background) + .expect("resolved theme colors must be valid hexadecimal RGBA"); let minimum = [base, raised, app] .into_iter() .map(luminance) @@ -308,8 +335,10 @@ impl ResolvedTheme { frame.tokens.colors.surface_raised = frame.tokens.colors.surface_base.clone(); frame.tokens.colors.application_background = frame.tokens.colors.surface_base.clone(); } - let surface = rgba(&frame.tokens.colors.surface_base).unwrap(); - let bar = rgba(&frame.tokens.surface.bar.background).unwrap(); + let surface = rgba(&frame.tokens.colors.surface_base) + .expect("resolved theme colors must be valid hexadecimal RGBA"); + let bar = rgba(&frame.tokens.surface.bar.background) + .expect("resolved theme colors must be valid hexadecimal RGBA"); let mut opacity = if frame.tokens.material.style == "solid" { 1. } else { @@ -318,8 +347,10 @@ impl ResolvedTheme { for step in 0..=100 { let candidate = opacity + (1. - opacity) * f64::from(step) / 100.; let backgrounds = material_bounds(surface, candidate); - let raised = rgba(&frame.tokens.colors.surface_raised).unwrap(); - let app = rgba(&frame.tokens.colors.application_background).unwrap(); + let raised = rgba(&frame.tokens.colors.surface_raised) + .expect("resolved theme colors must be valid hexadecimal RGBA"); + let app = rgba(&frame.tokens.colors.application_background) + .expect("resolved theme colors must be valid hexadecimal RGBA"); let all = [backgrounds[0], backgrounds[1], raised, app]; let minimum = all.into_iter().map(luminance).fold(f64::INFINITY, f64::min); let maximum = all.into_iter().map(luminance).fold(0., f64::max); @@ -339,38 +370,46 @@ impl ResolvedTheme { } let surface = material_bounds(surface, opacity); let bar = material_bounds(bar, opacity); - let raised = rgba(&frame.tokens.colors.surface_raised).unwrap(); - let app = rgba(&frame.tokens.colors.application_background).unwrap(); + let raised = rgba(&frame.tokens.colors.surface_raised) + .expect("resolved theme colors must be valid hexadecimal RGBA"); + let app = rgba(&frame.tokens.colors.application_background) + .expect("resolved theme colors must be valid hexadecimal RGBA"); let body_backgrounds = [surface[0], surface[1], raised, app]; let body_bounds = [ *body_backgrounds .iter() .min_by(|a, b| luminance(**a).total_cmp(&luminance(**b))) - .unwrap(), + .expect("material contrast checks contain four background samples"), *body_backgrounds .iter() .max_by(|a, b| luminance(**a).total_cmp(&luminance(**b))) - .unwrap(), + .expect("material contrast checks contain four background samples"), ]; frame.tokens.colors.text_primary = hex(material_foreground( - rgba(&preferred.tokens.colors.text_primary).unwrap(), + rgba(&preferred.tokens.colors.text_primary) + .expect("resolved theme colors must be valid hexadecimal RGBA"), body_bounds, )); frame.tokens.colors.text_muted = hex(material_foreground( - rgba(&preferred.tokens.colors.text_muted).unwrap(), + rgba(&preferred.tokens.colors.text_muted) + .expect("resolved theme colors must be valid hexadecimal RGBA"), body_bounds, )); frame.tokens.surface.bar.text_primary = hex(material_foreground( - rgba(&preferred.tokens.surface.bar.text_primary).unwrap(), + rgba(&preferred.tokens.surface.bar.text_primary) + .expect("resolved theme colors must be valid hexadecimal RGBA"), bar, )); frame.tokens.surface.bar.text_muted = hex(material_foreground( - rgba(&preferred.tokens.surface.bar.text_muted).unwrap(), + rgba(&preferred.tokens.surface.bar.text_muted) + .expect("resolved theme colors must be valid hexadecimal RGBA"), bar, )); frame.tokens.colors.on_accent = hex(readable( - rgba(&preferred.tokens.colors.on_accent).unwrap(), - rgba(&frame.tokens.colors.accent).unwrap(), + rgba(&preferred.tokens.colors.on_accent) + .expect("resolved theme colors must be valid hexadecimal RGBA"), + rgba(&frame.tokens.colors.accent) + .expect("resolved theme colors must be valid hexadecimal RGBA"), 4.5, )); frame diff --git a/crates/ferese-theme/src/gallery.rs b/crates/ferese-theme/src/gallery.rs index 4d4b3b1..cd9504b 100644 --- a/crates/ferese-theme/src/gallery.rs +++ b/crates/ferese-theme/src/gallery.rs @@ -17,6 +17,10 @@ pub struct TileOptions { pub id: Id, } +/// Shows a theme family using its validated appearance palettes. +/// +/// # Panics +/// The family must contain a usable palette with valid hexadecimal colors. pub fn tile( family: &Family, options: TileOptions, @@ -36,7 +40,9 @@ pub fn tile( let label_color = family .palette(variant.unwrap_or(Appearance::Light)) .or(family.dark.as_ref()) - .map(|palette| crate::parse_color(&palette.text).unwrap()) + .map(|palette| { + crate::parse_color(&palette.text).expect("theme family text is a validated color") + }) .unwrap_or(palette.text); let mut caption = row([]) .spacing(6) @@ -141,7 +147,9 @@ fn preview_with_radius( std::sync::Mutex>, > = std::sync::OnceLock::new(); let cache = CACHE.get_or_init(Default::default); - let mut cache = cache.lock().unwrap(); + let mut cache = cache + .lock() + .expect("theme preview cache must not be used after a mutator panics"); if cache.get(&key).is_none_or(|(cached, _)| cached != family) { let handle = icon::from_svg_bytes( svg_with_radius(family, variant, active, selected, radius).into_bytes(), @@ -207,7 +215,7 @@ fn svg_with_radius( result.push_str(""); for appearance in [Appearance::Light, Appearance::Dark] { @@ -263,10 +271,11 @@ fn svg_with_radius( .palette(active.unwrap_or(Appearance::Dark)) .or(family.dark.as_ref()) .or(family.light.as_ref()) - .unwrap(); + .expect("a theme family provides at least one appearance palette"); let foreground = ferese_theme_model::hex(ferese_theme_model::readable( [1.; 4], - ferese_theme_model::rgba(&p.accent).unwrap(), + ferese_theme_model::rgba(&p.accent) + .expect("resolved theme colors must be valid hexadecimal RGBA"), 4.5, )); result.push_str(&format!(r#""#, p.accent)); diff --git a/crates/ferese-theme/src/icons.rs b/crates/ferese-theme/src/icons.rs index c2f741a..79f8173 100644 --- a/crates/ferese-theme/src/icons.rs +++ b/crates/ferese-theme/src/icons.rs @@ -136,7 +136,10 @@ impl TintCache { .iter() .position(|entry| std::ptr::eq(entry.source, source) && entry.colors == colors) { - let entry = self.entries.remove(index).unwrap(); + let entry = self + .entries + .remove(index) + .expect("the cache lookup identifies an existing icon entry"); let handle = entry.handle.clone(); self.entries.push_back(entry); return handle; @@ -147,7 +150,11 @@ impl TintCache { if bytes <= TINT_CACHE_BYTES { while self.entries.len() >= TINT_CACHE_ENTRIES || self.bytes + bytes > TINT_CACHE_BYTES { - self.bytes -= self.entries.pop_front().unwrap().bytes; + self.bytes -= self + .entries + .pop_front() + .expect("cache eviction requires an entry while over budget") + .bytes; } self.bytes += bytes; self.entries.push_back(TintedIcon { diff --git a/crates/ferese-theme/src/palette.rs b/crates/ferese-theme/src/palette.rs index 73f257b..8c0a27e 100644 --- a/crates/ferese-theme/src/palette.rs +++ b/crates/ferese-theme/src/palette.rs @@ -94,8 +94,18 @@ impl Palette { stop.color.a = opacity; } - let from = crate::composite(candidate.stops[0].unwrap().color, self.sidebar); - let to = crate::composite(candidate.stops[1].unwrap().color, self.sidebar); + let from = crate::composite( + candidate.stops[0] + .expect("the constructed linear gradient contains both endpoint stops") + .color, + self.sidebar, + ); + let to = crate::composite( + candidate.stops[1] + .expect("the constructed linear gradient contains both endpoint stops") + .color, + self.sidebar, + ); let lower = Color::from_rgb(from.r.min(to.r), from.g.min(to.g), from.b.min(to.b)); let upper = Color::from_rgb(from.r.max(to.r), from.g.max(to.g), from.b.max(to.b)); let lightness = crate::luminance(foreground); diff --git a/crates/ferese-theme/src/typography.rs b/crates/ferese-theme/src/typography.rs index 4fbc2ed..2cccf35 100644 --- a/crates/ferese-theme/src/typography.rs +++ b/crates/ferese-theme/src/typography.rs @@ -6,7 +6,10 @@ pub fn font(family: Option<&str>) -> cosmic::font::Font { return cosmic::font::default(); }; static FONTS: OnceLock>> = OnceLock::new(); - let mut fonts = FONTS.get_or_init(Default::default).lock().unwrap(); + let mut fonts = FONTS + .get_or_init(Default::default) + .lock() + .expect("shared font cache must not be used after a mutator panics"); if let Some(font) = fonts.get(family) { return *font; } diff --git a/feresectl/src/activities.rs b/feresectl/src/activities.rs index 4b068ac..57b58c3 100644 --- a/feresectl/src/activities.rs +++ b/feresectl/src/activities.rs @@ -423,7 +423,9 @@ pub fn run( }); let status = child.wait()?; CHILD.store(0, std::sync::atomic::Ordering::Relaxed); - *drain_deadline.lock().unwrap() = + *drain_deadline + .lock() + .expect("command output drain deadline must remain consistent across reader and waiter") = Some(std::time::Instant::now() + std::time::Duration::from_millis(250)); if let Some(reader) = reader { // Descendants may inherit the pipe after the direct child exits. Drain diff --git a/feresectl/src/activities/output.rs b/feresectl/src/activities/output.rs index a64404b..2ccb83d 100644 --- a/feresectl/src/activities/output.rs +++ b/feresectl/src/activities/output.rs @@ -19,7 +19,9 @@ impl Read for Output { if self .deadline .lock() - .unwrap() + .expect( + "command output drain deadline must remain consistent across reader and waiter", + ) .is_some_and(|deadline| Instant::now() >= deadline) { // EOF lets BufReader return its final unterminated line too. diff --git a/feresectl/src/activities/progress.rs b/feresectl/src/activities/progress.rs index 54f26e2..47be915 100644 --- a/feresectl/src/activities/progress.rs +++ b/feresectl/src/activities/progress.rs @@ -16,7 +16,9 @@ pub(super) struct Updates(Arc<(Mutex, Condvar)>); impl Updates { pub fn progress(&self, units: u64) { let (lock, wake) = &*self.0; - let mut pending = lock.lock().unwrap(); + let mut pending = lock.lock().expect( + "the bounded progress buffer was checked before removing bytes or parsing text", + ); if !pending.closed { pending.units = Some(units); wake.notify_one(); @@ -36,9 +38,11 @@ impl Publisher { std::thread::spawn(move || { loop { let (lock, wake) = &*worker.0; - let mut pending = lock.lock().unwrap(); + let mut pending = lock.lock().expect( + "the bounded progress buffer was checked before removing bytes or parsing text", + ); while pending.units.is_none() && pending.end.is_none() { - pending = wake.wait(pending).unwrap(); + pending = wake.wait(pending).expect("the bounded progress buffer was checked before removing bytes or parsing text"); } let end = pending.end.take(); let units = pending.units.take(); @@ -54,7 +58,7 @@ impl Publisher { Publish::Update { local: local.clone(), handle: Some(handle.clone()), - value: Some(Value::Text(format!("{} units compiled", units.unwrap()))), + value: Some(Value::Text(format!("{} units compiled", units.expect("the bounded progress buffer was checked before removing bytes or parsing text")))), lease_ms: None, } }; @@ -76,7 +80,9 @@ impl Publisher { pub fn finish(self, result: State) { let (lock, wake) = &*self.updates.0; { - let mut pending = lock.lock().unwrap(); + let mut pending = lock.lock().expect( + "the bounded progress buffer was checked before removing bytes or parsing text", + ); pending.closed = true; pending.units = None; pending.end = Some(result); diff --git a/feresectl/src/activities/setup.rs b/feresectl/src/activities/setup.rs index c49961d..0d15b83 100644 --- a/feresectl/src/activities/setup.rs +++ b/feresectl/src/activities/setup.rs @@ -15,7 +15,11 @@ fn prepare(source: &str) -> Result> { serde_json::to_value(vec![panel::Panel::default()])?, )?; } - let panels: Vec = serde_json::from_value(doc.get("panels").unwrap().clone())?; + let panels: Vec = serde_json::from_value( + doc.get("panels") + .expect("setup populated the panel document before validation") + .clone(), + )?; panel::validate(&panels)?; for (index, bar) in panels.iter().enumerate() { let groups = [&bar.start, &bar.center, &bar.end] @@ -47,7 +51,11 @@ fn prepare(source: &str) -> Result> { if let Some(zone) = zone { doc.add( &format!("panels.{index}.{zone}.groups.0.items"), - item.as_object().unwrap().clone().into_iter().collect(), + item.as_object() + .expect("serialized panel items are JSON objects") + .clone() + .into_iter() + .collect(), )?; } else { doc.add( @@ -59,7 +67,11 @@ fn prepare(source: &str) -> Result> { )?; } } - let panels = serde_json::from_value::>(doc.get("panels").unwrap().clone())?; + let panels = serde_json::from_value::>( + doc.get("panels") + .expect("setup populated the panel document before validation") + .clone(), + )?; panel::validate(&panels)?; Ok(doc.to_string()) } @@ -72,7 +84,9 @@ pub fn configure(path: &Path) -> Result<(), Box> { } let backup = path.with_file_name(format!( "{}.before-activities-{}", - path.file_name().unwrap().to_string_lossy(), + path.file_name() + .expect("the configured shell hook path identifies a file") + .to_string_lossy(), ferese_ipc::activities::now_ms() )); file.replace(&desired, Some(&backup), |_| Ok(()))?; diff --git a/feresectl/src/main.rs b/feresectl/src/main.rs index a24e92c..a00b4b3 100644 --- a/feresectl/src/main.rs +++ b/feresectl/src/main.rs @@ -119,7 +119,7 @@ fn theme_command( } ThemeAction::Mode { mode } => connection.call( "theme-set-mode", - json!({"mode": mode.to_possible_value().unwrap().get_name()}), + json!({"mode": mode.to_possible_value().expect("every ThemeMode variant has a Clap value name").get_name()}), )?, ThemeAction::Preview { path } => connection.call( "theme-preview", diff --git a/portal/src/backend.rs b/portal/src/backend.rs index 3e641c8..3bca419 100644 --- a/portal/src/backend.rs +++ b/portal/src/backend.rs @@ -210,7 +210,11 @@ pub(crate) async fn authorize( .ok_or_else(|| zbus::fdo::Error::AccessDenied("Missing caller".into()))?; let dbus = zbus::fdo::DBusProxy::new(connection).await?; let owner = dbus - .get_name_owner(FRONTEND.try_into().unwrap()) + .get_name_owner( + FRONTEND + .try_into() + .expect("the portal frontend has a valid static bus name"), + ) .await .map_err(|_| zbus::fdo::Error::AccessDenied("Desktop portal is unavailable".into()))?; if sender.as_str() != owner.as_str() { @@ -608,9 +612,15 @@ async fn picker(prompt: &Prompt) -> Result { .kill_on_drop(true) .spawn() .map_err(|e| e.to_string())?; - let mut input = child.stdin.take().unwrap(); + let mut input = child + .stdin + .take() + .expect("the child was spawned with piped standard input"); input - .write_all(&serde_json::to_vec(prompt).unwrap()) + .write_all( + &serde_json::to_vec(prompt) + .expect("typed configuration and IPC values must serialize to JSON"), + ) .await .map_err(|e| e.to_string())?; drop(input); @@ -855,7 +865,10 @@ async fn start_streams( .spawn() .map_err(|e| e.to_string())?; let mut line = String::new(); - let output = child.stdout.take().unwrap(); + let output = child + .stdout + .take() + .expect("the child was spawned with piped standard output"); tokio::time::timeout( Duration::from_secs(10), BufReader::new(output).read_line(&mut line), @@ -1129,7 +1142,11 @@ pub async fn run() -> Result<(), Box> { tokio::time::sleep(Duration::from_secs(1)).await; let owner = match zbus::fdo::DBusProxy::new(&connection).await { Ok(proxy) => proxy - .get_name_owner(FRONTEND.try_into().unwrap()) + .get_name_owner( + FRONTEND + .try_into() + .expect("the portal frontend has a valid static bus name"), + ) .await .ok() .map(|name| name.to_string()), diff --git a/portal/src/capture.rs b/portal/src/capture.rs index 565d8ef..cdb9620 100644 --- a/portal/src/capture.rs +++ b/portal/src/capture.rs @@ -234,7 +234,10 @@ impl Capture { } frame.destroy(); - self.state.result.take().unwrap()?; + self.state + .result + .take() + .expect("the capture dispatch loop exits after storing its result")?; let buffer = self .state diff --git a/portal/src/consent.rs b/portal/src/consent.rs index cdd83ce..bc24472 100644 --- a/portal/src/consent.rs +++ b/portal/src/consent.rs @@ -163,7 +163,11 @@ impl cosmic::Application for Consent { if self.prompt.shortcuts.is_empty() { println!("true"); } else { - println!("{}", serde_json::to_string(&self.prompt.shortcuts).unwrap()); + println!( + "{}", + serde_json::to_string(&self.prompt.shortcuts) + .expect("typed configuration and IPC values must serialize to JSON") + ); } let _ = std::io::stdout().flush(); return cosmic::iced::exit(); diff --git a/portal/src/desktop.rs b/portal/src/desktop.rs index 5e7a53b..3189b8e 100644 --- a/portal/src/desktop.rs +++ b/portal/src/desktop.rs @@ -229,7 +229,10 @@ pub(crate) async fn consent( .spawn() .map_err(|error| error.to_string())?; let pid = child.id().ok_or("Missing consent process ID")?; - let mut input = child.stdin.take().unwrap(); + let mut input = child + .stdin + .take() + .expect("the child was spawned with piped standard input"); input .write_all(&serde_json::to_vec(&prompt).map_err(|error| error.to_string())?) @@ -578,7 +581,10 @@ async fn select_window(app: &str, parent: &str) -> Result, String> { .spawn() .map_err(|error| error.to_string())?; let pid = child.id().ok_or("Missing window selector PID")?; - let mut stdin = child.stdin.take().unwrap(); + let mut stdin = child + .stdin + .take() + .expect("the child was spawned with piped standard input"); stdin .write_all(&serde_json::to_vec(&prompt).map_err(|error| error.to_string())?) .await diff --git a/portal/src/inhibit.rs b/portal/src/inhibit.rs index 9b57ef3..43719c8 100644 --- a/portal/src/inhibit.rs +++ b/portal/src/inhibit.rs @@ -157,17 +157,19 @@ impl Inhibit { let values = HashMap::from([ ( "screensaver-active".into(), - Value::from(locked).try_into().unwrap(), + Value::from(locked).try_into().expect("the D-Bus value uses an owned supported scalar or collection type"), ), ( "session-state".into(), - Value::from(phase).try_into().unwrap(), + Value::from(phase).try_into().expect("the D-Bus value uses an owned supported scalar or collection type"), ), ]); if let Ok(emitter) = SignalEmitter::new(&connection, PATH) && Self::state_changed( &emitter, - OwnedObjectPath::try_from(path.clone()).unwrap(), + OwnedObjectPath::try_from(path.clone()).expect( + "session handles were validated as D-Bus object paths", + ), values, ) .await diff --git a/portal/src/input_capture.rs b/portal/src/input_capture.rs index 70dae08..44e76be 100644 --- a/portal/src/input_capture.rs +++ b/portal/src/input_capture.rs @@ -506,7 +506,12 @@ impl InputCapture { return Ok((1, Options::new())); } let result = self - .finish_native_call(connection, &session_handle, &bridge, result.unwrap()) + .finish_native_call( + connection, + &session_handle, + &bridge, + result.expect("the completion loop returned with a native call result"), + ) .await?; let zones: Vec<(u32, u32, i32, i32)> = serde_json::from_value(result["zones"].clone()) .map_err(|_| invalid("Invalid native capture zones"))?; @@ -582,7 +587,12 @@ impl InputCapture { return Ok((1, Options::new())); } let result = self - .finish_native_call(connection, &session_handle, &bridge, result.unwrap()) + .finish_native_call( + connection, + &session_handle, + &bridge, + result.expect("the completion loop returned with a native call result"), + ) .await?; let failed_barriers: Vec = serde_json::from_value(result["failed_barriers"].clone()) .map_err(|_| invalid("Invalid native barrier response"))?; diff --git a/portal/src/lockdown.rs b/portal/src/lockdown.rs index c00ef04..e7f8442 100644 --- a/portal/src/lockdown.rs +++ b/portal/src/lockdown.rs @@ -31,7 +31,8 @@ impl Lockdown { } pub(crate) async fn watch(self, connection: Connection) { - let emitter = SignalEmitter::new(&connection, PATH).unwrap(); + let emitter = SignalEmitter::new(&connection, PATH) + .expect("the portal exports signals at a valid static object path"); loop { tokio::time::sleep(Duration::from_secs(1)).await; let mut current = self.0.write().await; @@ -49,7 +50,9 @@ impl Lockdown { drop(current); let _ = zbus::fdo::Properties::properties_changed( &emitter, - "org.freedesktop.impl.portal.Lockdown".try_into().unwrap(), + "org.freedesktop.impl.portal.Lockdown" + .try_into() + .expect("the Lockdown interface has a valid static D-Bus name"), changed, Cow::Borrowed(&[]), ) diff --git a/portal/src/permissions.rs b/portal/src/permissions.rs index 4167497..f848af8 100644 --- a/portal/src/permissions.rs +++ b/portal/src/permissions.rs @@ -150,7 +150,8 @@ impl Background { } pub(crate) async fn watch(self, connection: Connection) { - let emitter = SignalEmitter::new(&connection, PATH).unwrap(); + let emitter = SignalEmitter::new(&connection, PATH) + .expect("the portal exports signals at a valid static object path"); loop { let result = async { let path = ferese_ipc::socket::resolve(None).map_err(|error| error.to_string())?; diff --git a/portal/src/picker.rs b/portal/src/picker.rs index 9a4c832..aa9e034 100644 --- a/portal/src/picker.rs +++ b/portal/src/picker.rs @@ -250,7 +250,11 @@ impl cosmic::Application for Picker { .map(|i| &self.prompt.sources[*i].name) .collect(); if self.prompt.window_capture { - println!("{}", serde_json::to_string(&names).unwrap()); + println!( + "{}", + serde_json::to_string(&names) + .expect("typed configuration and IPC values must serialize to JSON") + ); } else { let selection = Selection { names: names.into_iter().cloned().collect(), @@ -262,7 +266,11 @@ impl cosmic::Application for Picker { audio: self.audio, region: self.region, }; - println!("{}", serde_json::to_string(&selection).unwrap()); + println!( + "{}", + serde_json::to_string(&selection) + .expect("typed configuration and IPC values must serialize to JSON") + ); } let _ = std::io::stdout().flush(); return cosmic::iced::exit(); diff --git a/portal/src/recorder.rs b/portal/src/recorder.rs index e7d4bb9..6f3b049 100644 --- a/portal/src/recorder.rs +++ b/portal/src/recorder.rs @@ -28,7 +28,8 @@ pub fn event(state: &str, message: Option<&str>, path: Option<&Path>) { } fn option_string(value: String) -> OwnedValue { - OwnedValue::try_from(Value::from(value)).unwrap() + OwnedValue::try_from(Value::from(value)) + .expect("the D-Bus value uses an owned supported scalar or collection type") } async fn request( diff --git a/portal/src/settings.rs b/portal/src/settings.rs index dc69f50..e95ac80 100644 --- a/portal/src/settings.rs +++ b/portal/src/settings.rs @@ -44,7 +44,9 @@ impl Appearance { ("color-scheme".into(), self.scheme.into()), ( "accent-color".into(), - Value::from(self.accent).try_to_owned().unwrap(), + Value::from(self.accent) + .try_to_owned() + .expect("the D-Bus value uses an owned supported scalar or collection type"), ), ("contrast".into(), self.contrast.into()), ("reduced-motion".into(), self.reduced_motion.into()), @@ -83,7 +85,8 @@ impl Settings { } pub(crate) async fn watch(self, connection: Connection) { - let emitter = SignalEmitter::new(&connection, PATH).unwrap(); + let emitter = SignalEmitter::new(&connection, PATH) + .expect("the portal exports signals at a valid static object path"); loop { let connection = tokio::task::spawn_blocking(ferese_ipc::Connection::connect).await; if let Ok(Ok(mut connection)) = connection diff --git a/portal/src/shortcuts.rs b/portal/src/shortcuts.rs index 22d6a64..d236f5e 100644 --- a/portal/src/shortcuts.rs +++ b/portal/src/shortcuts.rs @@ -210,7 +210,8 @@ impl GlobalShortcuts { session: Arc, bridge: Bridge, ) { - let emitter = SignalEmitter::new(&connection, PATH).unwrap(); + let emitter = SignalEmitter::new(&connection, PATH) + .expect("the portal exports signals at a valid static object path"); loop { tokio::select! { _ = session.cancel.wait() => break, @@ -414,7 +415,12 @@ impl GlobalShortcuts { self.requests.end(connection, &handle).await; Ok(( 0, - HashMap::from([("shortcuts".into(), Value::from(result).try_into().unwrap())]), + HashMap::from([( + "shortcuts".into(), + Value::from(result) + .try_into() + .expect("the D-Bus value uses an owned supported scalar or collection type"), + )]), )) } @@ -453,7 +459,8 @@ impl GlobalShortcuts { .await; session.state.lock().await.busy = false; if let Ok(Some(shortcuts)) = result { - let emitter = SignalEmitter::new(&connection, PATH).unwrap(); + let emitter = SignalEmitter::new(&connection, PATH) + .expect("the portal exports signals at a valid static object path"); let _ = Self::shortcuts_changed(&emitter, session_handle, shortcuts).await; } }); @@ -597,7 +604,9 @@ fn reply(response: Result, String>) -> (u32, Options) { 0, HashMap::from([( "shortcuts".into(), - Value::from(shortcuts).try_into().unwrap(), + Value::from(shortcuts) + .try_into() + .expect("the D-Bus value uses an owned supported scalar or collection type"), )]), ), Ok(None) => (1, Options::new()), @@ -629,7 +638,10 @@ async fn dialog( .stderr(Stdio::inherit()) .spawn() .map_err(|error| error.to_string())?; - let mut input = child.stdin.take().unwrap(); + let mut input = child + .stdin + .take() + .expect("the child was spawned with piped standard input"); input .write_all(&serde_json::to_vec(&prompt).map_err(|error| error.to_string())?) .await @@ -705,7 +717,9 @@ fn save(app: &str, fields: &[ShortcutField]) -> Result<(), String> { let Some(path) = cache_path(app) else { return Ok(()); }; - let directory = path.parent().unwrap(); + let directory = path + .parent() + .expect("the portal configuration path identifies a file in a directory"); std::fs::create_dir_all(directory).map_err(|error| error.to_string())?; let mut file = tempfile::NamedTempFile::new_in(directory).map_err(|error| error.to_string())?; std::io::Write::write_all( diff --git a/portal/src/stream.rs b/portal/src/stream.rs index 55780ec..a9c80c9 100644 --- a/portal/src/stream.rs +++ b/portal/src/stream.rs @@ -106,7 +106,7 @@ fn pod(object: spa::pod::Object) -> Vec { std::io::Cursor::new(Vec::new()), &spa::pod::Value::Object(object), ) - .unwrap() + .expect("SPA pod serialization into an owned Vec cannot fail for these object types") .0 .into_inner() } @@ -230,7 +230,7 @@ pub fn run( width, height }) - .unwrap() + .expect("typed configuration and IPC values must serialize to JSON") ); let _ = std::io::stdout().flush(); } @@ -291,8 +291,10 @@ pub fn run( )); if stream .update_params(&mut [ - Pod::from_bytes(&buffers).unwrap(), - Pod::from_bytes(&header).unwrap(), + Pod::from_bytes(&buffers) + .expect("SPA pod bytes were produced by the matching pod serializer"), + Pod::from_bytes(&header) + .expect("SPA pod bytes were produced by the matching pod serializer"), ]) .is_err() { @@ -310,7 +312,9 @@ pub fn run( *target.chunk_mut().size_mut() = 0; return; } - let frame = data.latest.lock().unwrap(); + let frame = data.latest.lock().expect( + "capture frame state must remain consistent across producer and PipeWire callbacks", + ); if (frame.width, frame.height) != data.negotiated { *target.chunk_mut().size_mut() = 0; return; @@ -344,7 +348,8 @@ pub fn run( spa::utils::Direction::Output, None, pw::stream::StreamFlags::MAP_BUFFERS | pw::stream::StreamFlags::DRIVER, - &mut [Pod::from_bytes(&format).unwrap()], + &mut [Pod::from_bytes(&format) + .expect("SPA pod bytes were produced by the matching pod serializer")], )?; let interval = Duration::from_nanos(1_000_000_000 / 30); @@ -362,7 +367,7 @@ pub fn run( checked = Instant::now(); match capture.frame(&name, cursor, &capture_stop, std::mem::take(&mut spare)) { Ok(frame) if resizable || (frame.width == width && frame.height == height) => { - spare = std::mem::replace(&mut *latest.lock().unwrap(), frame).pixels; + spare = std::mem::replace(&mut *latest.lock().expect("capture frame state must remain consistent across producer and PipeWire callbacks"), frame).pixels; fresh.store(capture_streaming.load(Ordering::Relaxed), Ordering::SeqCst); } Ok(_) => { @@ -385,12 +390,16 @@ pub fn run( while !stop.load(Ordering::Relaxed) { let size = { - let frame = format_frames.lock().unwrap(); + let frame = format_frames.lock().expect( + "capture frame state must remain consistent across producer and PipeWire callbacks", + ); (frame.width, frame.height) }; if resizable && size != requested_size { let format = video_format(size.0, size.1); - if let Err(error) = stream.update_params(&mut [Pod::from_bytes(&format).unwrap()]) { + if let Err(error) = stream.update_params(&mut [Pod::from_bytes(&format) + .expect("SPA pod bytes were produced by the matching pod serializer")]) + { renegotiation_error = Some(error); stop.store(true, Ordering::Relaxed); break; diff --git a/shell/ferese-lock/src/runtime.rs b/shell/ferese-lock/src/runtime.rs index 7197c26..ca2c1b1 100644 --- a/shell/ferese-lock/src/runtime.rs +++ b/shell/ferese-lock/src/runtime.rs @@ -47,7 +47,10 @@ pub fn daemonize() -> Result<(), String> { } fn confirm(child: &mut Child) -> Result<(), String> { - let stdout = child.stdout.take().unwrap(); + let stdout = child + .stdout + .take() + .expect("the child was spawned with piped standard output"); let (tx, rx) = std::sync::mpsc::sync_channel(1); std::thread::spawn(move || { let mut reader = BufReader::new(stdout); diff --git a/shell/ferese-polkit-agent/src/main.rs b/shell/ferese-polkit-agent/src/main.rs index 212863e..b64c0ac 100644 --- a/shell/ferese-polkit-agent/src/main.rs +++ b/shell/ferese-polkit-agent/src/main.rs @@ -136,7 +136,10 @@ impl Agent { let cancelled = Arc::new(AtomicBool::new(false)); { - let mut active = self.active.lock().unwrap(); + let mut active = self + .active + .lock() + .expect("authentication session ownership must remain consistent across callbacks"); if active.contains_key(&cookie) { return Err(zbus::fdo::Error::Failed("Duplicate request".into())); } @@ -166,7 +169,10 @@ impl Agent { }) .await; - self.active.lock().unwrap().remove(&cookie); + self.active + .lock() + .expect("authentication session ownership must remain consistent across callbacks") + .remove(&cookie); match result.map_err(|error| zbus::fdo::Error::Failed(error.to_string()))? { Ok(true) => Ok(()), Ok(false) => Err(zbus::fdo::Error::Failed("Authentication cancelled".into())), @@ -175,7 +181,12 @@ impl Agent { } fn cancel_authentication(&self, cookie: String) { - if let Some(cancelled) = self.active.lock().unwrap().get(&cookie) { + if let Some(cancelled) = self + .active + .lock() + .expect("authentication session ownership must remain consistent across callbacks") + .get(&cookie) + { cancelled.store(true, Ordering::Release); } } diff --git a/shell/ferese-polkit-agent/src/prompt.rs b/shell/ferese-polkit-agent/src/prompt.rs index a1019c0..3353384 100644 --- a/shell/ferese-polkit-agent/src/prompt.rs +++ b/shell/ferese-polkit-agent/src/prompt.rs @@ -153,11 +153,17 @@ fn read_events( for line in lines { let Ok(line) = line else { break }; if let Ok(event) = serde_json::from_str(&line) { - queue.lock().unwrap().push_back(event); + queue + .lock() + .expect("authentication prompt queue must remain consistent across workers") + .push_back(event); } } // The agent owns this pipe. EOF (including agent death) closes the prompt. - queue.lock().unwrap().push_back(PromptEvent::Cancel); + queue + .lock() + .expect("authentication prompt queue must remain consistent across workers") + .push_back(PromptEvent::Cancel); } pub fn run() -> Result<(), Box> { @@ -348,7 +354,12 @@ impl cosmic::Application for Prompt { self.material = result.ok(); } Message::Tick => { - let events: Vec<_> = self.incoming.lock().unwrap().drain(..).collect(); + let events: Vec<_> = self + .incoming + .lock() + .expect("authentication prompt queue must remain consistent across workers") + .drain(..) + .collect(); let mut refocus = false; for event in events { match event { diff --git a/shell/ferese-polkit-agent/src/session.rs b/shell/ferese-polkit-agent/src/session.rs index 2dd310d..cb4b9b2 100644 --- a/shell/ferese-polkit-agent/src/session.rs +++ b/shell/ferese-polkit-agent/src/session.rs @@ -469,7 +469,9 @@ fn run_session( } else if stopped { Ok(SessionOutcome::Cancelled) } else { - Ok(SessionOutcome::Completed(outcome.unwrap())) + Ok(SessionOutcome::Completed(outcome.expect( + "the session loop finished only after receiving a completion outcome", + ))) } } diff --git a/shell/ferese-settings/src/connections/agent.rs b/shell/ferese-settings/src/connections/agent.rs index 759296b..45747b6 100644 --- a/shell/ferese-settings/src/connections/agent.rs +++ b/shell/ferese-settings/src/connections/agent.rs @@ -68,7 +68,10 @@ enum Error { impl Pairing { fn begin(&self, target: &str) -> Result { - let mut state = self.0.lock().unwrap(); + let mut state = self + .0 + .lock() + .expect("pairing agent state must remain consistent across callbacks"); if state.target.is_some() { return Err("Another device is already pairing.".into()); } @@ -78,7 +81,10 @@ impl Pairing { } fn finish(&self, generation: u64) { - let mut state = self.0.lock().unwrap(); + let mut state = self + .0 + .lock() + .expect("pairing agent state must remain consistent across callbacks"); if state.generation == generation { state.target = None; state.prompt = None; @@ -87,7 +93,10 @@ impl Pairing { } pub fn cancel_local(&self) -> Option { - let mut state = self.0.lock().unwrap(); + let mut state = self + .0 + .lock() + .expect("pairing agent state must remain consistent across callbacks"); state.generation = state.generation.wrapping_add(1); state.prompt = None; state.reply = None; @@ -95,11 +104,18 @@ impl Pairing { } pub fn prompt(&self) -> Option { - self.0.lock().unwrap().prompt.clone() + self.0 + .lock() + .expect("pairing agent state must remain consistent across callbacks") + .prompt + .clone() } pub fn reply(&self, id: u64, value: Option>) { - let mut state = self.0.lock().unwrap(); + let mut state = self + .0 + .lock() + .expect("pairing agent state must remain consistent across callbacks"); if state.prompt.as_ref().is_some_and(|p| p.id == id) { if let Some(reply) = state.reply.take() { let _ = reply.send(value); @@ -126,7 +142,10 @@ impl Pairing { sender: Option<&str>, ) -> Result, Error> { let (id, rx) = { - let mut state = self.0.lock().unwrap(); + let mut state = self + .0 + .lock() + .expect("pairing agent state must remain consistent across callbacks"); Self::check(&state, device.as_str(), sender)?; if state.reply.is_some() { return Err(Error::Rejected( @@ -159,7 +178,10 @@ impl Pairing { text: String, sender: Option<&str>, ) -> Result<(), Error> { - let mut state = self.0.lock().unwrap(); + let mut state = self + .0 + .lock() + .expect("pairing agent state must remain consistent across callbacks"); Self::check(&state, device.as_str(), sender)?; state.serial = state.serial.wrapping_add(1); state.prompt = Some(Prompt { @@ -171,7 +193,10 @@ impl Pairing { } fn dismiss(&self, sender: Option<&str>) -> Result<(), Error> { - let mut state = self.0.lock().unwrap(); + let mut state = self + .0 + .lock() + .expect("pairing agent state must remain consistent across callbacks"); if sender.is_none() || state.owner.as_deref() != sender { return Err(Error::Rejected("Unknown sender".into())); } @@ -289,7 +314,10 @@ impl Pairing { fn release(&self, #[zbus(header)] header: Header<'_>) -> Result<(), Error> { self.dismiss(header.sender().map(|s| s.as_str()))?; self.cancel_local(); - self.0.lock().unwrap().owner = None; + self.0 + .lock() + .expect("pairing agent state must remain consistent across callbacks") + .owner = None; Ok(()) } } @@ -308,17 +336,29 @@ impl Client { .map_err(|e| e.to_string())?; let mut registered = self.registered.lock().await; if registered.as_ref() == Some(&owner) - && self.agent.0.lock().unwrap().owner.as_ref() == Some(&owner) + && self + .agent + .0 + .lock() + .expect("pairing agent state must remain consistent across callbacks") + .owner + .as_ref() + == Some(&owner) { return Ok(()); } - self.agent.0.lock().unwrap().owner = Some(owner.clone()); + self.agent + .0 + .lock() + .expect("pairing agent state must remain consistent across callbacks") + .owner = Some(owner.clone()); self.connection .object_server() .at(PATH, self.agent.clone()) .await .map_err(|e| e.to_string())?; - let path = OwnedObjectPath::try_from(PATH).unwrap(); + let path = OwnedObjectPath::try_from(PATH) + .expect("the pairing agent has a valid static object path"); proxy( &self.connection, "org.bluez", @@ -341,13 +381,26 @@ impl Client { } let result = async { self.register_agent().await?; - if self.agent.0.lock().unwrap().generation != generation { + if self + .agent + .0 + .lock() + .expect("pairing agent state must remain consistent across callbacks") + .generation + != generation + { return Err("Pairing canceled.".into()); } bluetooth::device_call(&self.connection, path, "Pair").await } .await; - let still_active = self.agent.0.lock().unwrap().generation == generation; + let still_active = self + .agent + .0 + .lock() + .expect("pairing agent state must remain consistent across callbacks") + .generation + == generation; self.agent.finish(generation); if !still_active { return Err("Pairing canceled.".into()); diff --git a/shell/ferese-settings/src/connections/bluetooth.rs b/shell/ferese-settings/src/connections/bluetooth.rs index fe87ddc..4ef49cc 100644 --- a/shell/ferese-settings/src/connections/bluetooth.rs +++ b/shell/ferese-settings/src/connections/bluetooth.rs @@ -170,7 +170,12 @@ impl Client { pub(crate) async fn discover(&self, path: String, epoch: u64) -> Result<(), String> { // BlueZ discovery is reference counted per D-Bus caller. Never stop // discovery owned by another app, and never acquire a lease twice. - if self.discovery.lock().unwrap().contains_key(&path) { + if self + .discovery + .lock() + .expect("Bluetooth discovery ownership must remain consistent across callbacks") + .contains_key(&path) + { return Ok(()); } adapter_call(&self.connection, &path, "StartDiscovery").await?; @@ -179,13 +184,18 @@ impl Client { return Err("Discovery canceled.".into()); } let lease = std::time::Instant::now(); - self.discovery.lock().unwrap().insert(path.clone(), lease); + self.discovery + .lock() + .expect("Bluetooth discovery ownership must remain consistent across callbacks") + .insert(path.clone(), lease); let leases = self.discovery.clone(); let connection = self.connection.clone(); tokio::spawn(async move { tokio::time::sleep(Duration::from_secs(30)).await; let owned = { - let mut leases = leases.lock().unwrap(); + let mut leases = leases.lock().expect( + "Bluetooth discovery ownership must remain consistent across callbacks", + ); if leases.get(&path) == Some(&lease) { leases.remove(&path); true @@ -201,7 +211,12 @@ impl Client { } pub(crate) async fn stop_discovery(&self) -> Result<(), String> { - let paths = std::mem::take(&mut *self.discovery.lock().unwrap()); + let paths = std::mem::take( + &mut *self + .discovery + .lock() + .expect("Bluetooth discovery ownership must remain consistent across callbacks"), + ); let mut result = Ok(()); for (path, _) in paths { if let Err(error) = adapter_call(&self.connection, &path, "StopDiscovery").await { @@ -212,7 +227,10 @@ impl Client { } pub(crate) fn discovering(&self, path: &str) -> bool { - self.discovery.lock().unwrap().contains_key(path) + self.discovery + .lock() + .expect("Bluetooth discovery ownership must remain consistent across callbacks") + .contains_key(path) } } diff --git a/shell/ferese-settings/src/connections/bluetooth_ui.rs b/shell/ferese-settings/src/connections/bluetooth_ui.rs index c966640..7b67e99 100644 --- a/shell/ferese-settings/src/connections/bluetooth_ui.rs +++ b/shell/ferese-settings/src/connections/bluetooth_ui.rs @@ -19,7 +19,9 @@ impl App { column([]) .spacing(8) .push(self.label("Bluetooth unavailable", 14.)) - .push(self.connection_note(result.as_ref().unwrap_err())), + .push(self.connection_note(result.as_ref().expect_err( + "the unavailable-services branch contains a connection error", + ))), ); }; let mut body = column([]).spacing(6); diff --git a/shell/ferese-settings/src/connections/controller.rs b/shell/ferese-settings/src/connections/controller.rs index 51ce6be..9182fbb 100644 --- a/shell/ferese-settings/src/connections/controller.rs +++ b/shell/ferese-settings/src/connections/controller.rs @@ -163,7 +163,12 @@ impl App { } if accepted && !super::agent::valid_answer( - &self.connections.prompt.as_ref().unwrap().kind, + &self + .connections + .prompt + .as_ref() + .expect("the pairing reply branch requires an active prompt") + .kind, &self.connections.pairing_input, ) { diff --git a/shell/ferese-settings/src/connections/mod.rs b/shell/ferese-settings/src/connections/mod.rs index 75bf982..c23da50 100644 --- a/shell/ferese-settings/src/connections/mod.rs +++ b/shell/ferese-settings/src/connections/mod.rs @@ -229,7 +229,12 @@ impl Client { fn cleanup(&self) { let target = self.agent.cancel_local(); - let paths = std::mem::take(&mut *self.discovery.lock().unwrap()); + let paths = std::mem::take( + &mut *self + .discovery + .lock() + .expect("Bluetooth discovery ownership must remain consistent across callbacks"), + ); let connection = self.connection.clone(); self.runtime.spawn(async move { if let Some(path) = target { diff --git a/shell/ferese-settings/src/connections/wifi_ui.rs b/shell/ferese-settings/src/connections/wifi_ui.rs index 4396863..af95a9b 100644 --- a/shell/ferese-settings/src/connections/wifi_ui.rs +++ b/shell/ferese-settings/src/connections/wifi_ui.rs @@ -20,7 +20,9 @@ impl App { column([]) .spacing(8) .push(self.label("Wi-Fi unavailable", 14.)) - .push(self.connection_note(result.as_ref().unwrap_err())), + .push(self.connection_note(result.as_ref().expect_err( + "the unavailable-services branch contains a connection error", + ))), ); }; let mut body = column([]).spacing(6); diff --git a/shell/ferese-settings/src/main.rs b/shell/ferese-settings/src/main.rs index 6755313..48bcddf 100644 --- a/shell/ferese-settings/src/main.rs +++ b/shell/ferese-settings/src/main.rs @@ -258,7 +258,9 @@ impl cosmic::Application for App { } .into(); let error = initial.as_ref().err().cloned(); - let current = initial.unwrap_or_else(|_| Snapshot::parse(String::new()).unwrap()); + let current = initial.unwrap_or_else(|_| { + Snapshot::parse(String::new()).expect("an empty Settings document uses valid defaults") + }); let resolved = ferese_theme_client::service::current(); let font = fonts::interface_font(&resolved.presented.tokens.typography.font_family); let native_palette = visuals::Palette::from_resolved(&resolved.presented); @@ -540,7 +542,10 @@ impl cosmic::Application for App { if let Some((id, appearance)) = &self.gallery_focus && self.gallery_position(id, *appearance) == Some((list, index)) { - let (id, appearance) = self.gallery_focus.take().unwrap(); + let (id, appearance) = self + .gallery_focus + .take() + .expect("gallery focus was requested before this focus operation"); return cosmic::iced::advanced::widget::operate( cosmic::iced::advanced::widget::operation::focusable::focus( gallery_id(&id, appearance), @@ -1100,7 +1105,9 @@ impl App { { return self.apply_wallpaper_edits(wallpaper_controls::edits( wallpaper_controls::Target::Both, - path.rsplit('.').next().unwrap(), + path.rsplit('.') + .next() + .expect("splitting a field path always yields its final component"), value.clone(), )); } diff --git a/shell/ferese-settings/src/panel_controls.rs b/shell/ferese-settings/src/panel_controls.rs index a321f16..07827fd 100644 --- a/shell/ferese-settings/src/panel_controls.rs +++ b/shell/ferese-settings/src/panel_controls.rs @@ -321,10 +321,13 @@ impl App { .map(|panel| panel.resolved_geometry()) .unwrap_or_default(), ) - .unwrap(); - *default = geometry[field.path.rsplit('.').next().unwrap()] + .expect("typed configuration and IPC values must serialize to JSON"); + *default = + geometry[field.path.rsplit('.').next().expect( + "splitting a field path always yields its final component", + )] .as_f64() - .unwrap(); + .expect("splitting a field path always yields its final component"); } let pinned = self.draft.item(&field.path).is_some(); let reset = pinned.then_some(Message::PanelEdit(Action::ClearGeometry( diff --git a/shell/ferese-settings/src/panel_drag.rs b/shell/ferese-settings/src/panel_drag.rs index b24d515..bdd6e5d 100644 --- a/shell/ferese-settings/src/panel_drag.rs +++ b/shell/ferese-settings/src/panel_drag.rs @@ -258,7 +258,7 @@ impl Widget for Editor<'_> { .find_map(|(region, bounds)| { matches!(region, Region::Item(id, _) if id == item).then_some(*bounds) }) - .unwrap(); + .expect("the active drag branch retains the pressed item and its measured bounds"); state.drag = Some(Drag { item: item.clone(), origin: cursor, @@ -272,7 +272,9 @@ impl Widget for Editor<'_> { } } Event::Mouse(mouse::Event::CursorMoved { .. }) if state.drag.is_some() => { - let drag = state.drag.as_mut().unwrap(); + let drag = state.drag.as_mut().expect( + "the active drag branch retains the pressed item and its measured bounds", + ); if let Some(cursor) = cursor.position() { drag.cursor = cursor; drag.moved |= (cursor.x - drag.origin.x).hypot(cursor.y - drag.origin.y) >= 6.; @@ -291,7 +293,9 @@ impl Widget for Editor<'_> { Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) if state.drag.is_some() => { - let drag = state.drag.take().unwrap(); + let drag = state.drag.take().expect( + "the active drag branch retains the pressed item and its measured bounds", + ); if drag.moved { if let Some(target) = cursor .position() diff --git a/shell/ferese-settings/src/panel_edit.rs b/shell/ferese-settings/src/panel_edit.rs index 3080686..3edaae5 100644 --- a/shell/ferese-settings/src/panel_edit.rs +++ b/shell/ferese-settings/src/panel_edit.rs @@ -147,7 +147,7 @@ fn locate(panel: &Panel, id: &ItemId) -> Result<(String, usize), String> { .groups .iter() .find(|group| group.id == destination.group) - .unwrap(); + .expect("the selected destination group belongs to its resolved zone"); if let Some(index) = group.items.iter().position(|item| &item.id == id) { return Ok((path, index)); } @@ -309,7 +309,7 @@ pub(super) fn plan(snapshot: &Snapshot, action: Action) -> Result, Str } let group = records(&candidate, &format!("panels.0.{}.groups", zone.key()))[0]["id"] .as_str() - .unwrap() + .expect("validated panel groups and item kinds have string identifiers") .to_owned(); edits.extend(plan( &candidate, @@ -337,7 +337,13 @@ pub(super) fn plan(snapshot: &Snapshot, action: Action) -> Result, Str } let (path, index) = locate(panel, &id)?; if field == "style" - && !matches!(panel.item(&id).unwrap().kind, ItemKind::Workspaces { .. }) + && !matches!( + panel + .item(&id) + .expect("the item edit resolved its stable ID before checking its kind") + .kind, + ItemKind::Workspaces { .. } + ) { return Err("Workspace style belongs to Workspaces items.".into()); } @@ -349,11 +355,14 @@ pub(super) fn plan(snapshot: &Snapshot, action: Action) -> Result, Str } let path = group_path(panel, &destination)?; let name = serde_json::to_value(kind).map_err(|error| error.to_string())?; - let name = name["kind"].as_str().unwrap(); + let name = name["kind"] + .as_str() + .expect("validated panel groups and item kinds have string identifiers"); let count = destinations(panel) .iter() .map(|destination| { - let path = group_path(panel, destination).unwrap(); + let path = group_path(panel, destination) + .expect("catalog destinations refer to existing panel groups"); records(snapshot, &path).len() }) .sum::(); @@ -366,7 +375,7 @@ pub(super) fn plan(snapshot: &Snapshot, action: Action) -> Result, Str } }) .find(|id| panel.item(&ItemId(id.clone())).is_none()) - .unwrap(); + .expect("one more candidate ID than existing items guarantees an unused ID"); let mut items = records(snapshot, &path); items.push( serde_json::to_value(Item::new(&id, kind)).map_err(|error| error.to_string())?, @@ -381,7 +390,7 @@ pub(super) fn plan(snapshot: &Snapshot, action: Action) -> Result, Str let id = (1..=count + 1) .map(|number| format!("{}-{number}", zone.key())) .find(|id| locate_group(panel, &GroupId(id.clone())).is_err()) - .unwrap(); + .expect("one more candidate ID than existing items guarantees an unused ID"); let path = format!("panels.0.{}.groups", zone.key()); let mut groups = records(snapshot, &path); groups.push(serde_json::json!({ @@ -432,7 +441,8 @@ pub(super) fn plan(snapshot: &Snapshot, action: Action) -> Result, Str "padding_vertical" | "padding_horizontal" => { let (zone, index) = locate_group(panel, &id)?; let mut padding = - serde_json::to_value(zone.definition(panel).groups[index].padding).unwrap(); + serde_json::to_value(zone.definition(panel).groups[index].padding) + .expect("typed configuration and IPC values must serialize to JSON"); padding[usize::from(field == "padding_horizontal")] = value; ("padding".to_owned(), padding) } diff --git a/shell/ferese-settings/src/panel_inspector.rs b/shell/ferese-settings/src/panel_inspector.rs index 5617c88..07a2d08 100644 --- a/shell/ferese-settings/src/panel_inspector.rs +++ b/shell/ferese-settings/src/panel_inspector.rs @@ -140,12 +140,12 @@ impl App { .into_iter() .flat_map(|zone| &zone.definition(panel).groups) .find(|group| group.id == destination.group) - .unwrap(); + .expect("the inspector selection refers to an item or group in the current panel"); let index = group .items .iter() .position(|candidate| candidate.id == item.id) - .unwrap(); + .expect("the inspector selection refers to an item or group in the current panel"); rows.push(self.panel_divider()) .push( self.inspector_actions( @@ -293,7 +293,7 @@ impl App { .groups .iter() .position(|candidate| candidate.id == group.id) - .unwrap(); + .expect("the inspector selection refers to an item or group in the current panel"); rows = rows.push( self.inspector_actions( (index > 0).then(|| Message::PanelEdit(Action::EarlierGroup(group.id.clone()))), @@ -335,7 +335,12 @@ impl App { fn panel_field(&self, item: &Item, field: Field) -> Element<'static, Message> { let p = crate::visuals::Palette::from_resolved(&self.resolved.presented); let id = item.id.clone(); - let name = field.path.rsplit('.').next().unwrap().to_owned(); + let name = field + .path + .rsplit('.') + .next() + .expect("splitting a field path always yields its final component") + .to_owned(); let values = serde_json::to_value(item).expect("serializable panel item"); match field.kind { Kind::Choice { default, choices } => { diff --git a/shell/ferese-settings/src/wallpaper_controls.rs b/shell/ferese-settings/src/wallpaper_controls.rs index 507f4f4..8746efb 100644 --- a/shell/ferese-settings/src/wallpaper_controls.rs +++ b/shell/ferese-settings/src/wallpaper_controls.rs @@ -328,7 +328,9 @@ impl App { return setting( &self.draft, self.wallpaper.target, - path.rsplit('.').next().unwrap(), + path.rsplit('.') + .next() + .expect("splitting a field path always yields its final component"), fallback, ); } diff --git a/shell/ferese-shell/src/activities.rs b/shell/ferese-shell/src/activities.rs index ed8a5e0..77b922f 100644 --- a/shell/ferese-shell/src/activities.rs +++ b/shell/ferese-shell/src/activities.rs @@ -336,7 +336,11 @@ impl FereseShell { presentation.rows = rows; continue; } - let runtime = output.runtime.hub.as_ref().unwrap(); + let runtime = output + .runtime + .hub + .as_ref() + .expect("this activity action belongs to an output with a Morph hub"); let name = self .output_for_bar(output.bar) .map(|output| output.name.as_str()) diff --git a/shell/ferese-shell/src/activities/compact.rs b/shell/ferese-shell/src/activities/compact.rs index c1a7af9..59b7635 100644 --- a/shell/ferese-shell/src/activities/compact.rs +++ b/shell/ferese-shell/src/activities/compact.rs @@ -67,7 +67,11 @@ impl Compact { + count as f32 * (ICON + TEXT_GAP) + count.saturating_sub(1) as f32 * (2. * ITEM_GAP + 1.) + if hidden > 0 { - ITEM_GAP + self.badge.unwrap().1 + ITEM_GAP + + self + .badge + .expect("hidden activity count is nonzero only when its badge was measured") + .1 } else { 0. }; diff --git a/shell/ferese-shell/src/config.rs b/shell/ferese-shell/src/config.rs index fddfe93..481564e 100644 --- a/shell/ferese-shell/src/config.rs +++ b/shell/ferese-shell/src/config.rs @@ -451,8 +451,11 @@ fn parse_document( impl ShellConfig { pub(crate) fn apply_theme(&mut self, theme: &ferese_config::theme::ResolvedTheme) { - let config: ThemeConfig = - serde_json::from_value(serde_json::to_value(&theme.tokens).unwrap()).unwrap(); + let config: ThemeConfig = serde_json::from_value( + serde_json::to_value(&theme.tokens) + .expect("typed configuration and IPC values must serialize to JSON"), + ) + .expect("resolved theme tokens must match their consumer schema"); self.theme = shell_theme(&config); self.theme.appearance = theme.appearance; self.theme.high_contrast = theme.accessibility.increase_contrast; diff --git a/shell/ferese-shell/src/display_mode.rs b/shell/ferese-shell/src/display_mode.rs index ba53a0d..e72f044 100644 --- a/shell/ferese-shell/src/display_mode.rs +++ b/shell/ferese-shell/src/display_mode.rs @@ -295,7 +295,7 @@ impl FereseShell { let current = Mode::ALL .iter() .position(|mode| *mode == self.display_mode.selected) - .unwrap(); + .expect("Mode::ALL contains every selectable display mode"); let mode = match key { Key::Character(character) => character .parse::() diff --git a/shell/ferese-shell/src/launcher/mod.rs b/shell/ferese-shell/src/launcher/mod.rs index 11f8a0c..c4ab38d 100644 --- a/shell/ferese-shell/src/launcher/mod.rs +++ b/shell/ferese-shell/src/launcher/mod.rs @@ -460,7 +460,10 @@ impl FereseShell { launcher.rows = rows; launcher.file_status = status; let task = self.merge_launcher_windows(true); - let launcher = self.launcher.as_mut().unwrap(); + let launcher = self + .launcher + .as_mut() + .expect("merging launcher windows keeps the active launcher alive"); launcher.selected = selected .and_then(|id| launcher.rows.iter().position(|row| row.id == id)) .unwrap_or(0); diff --git a/shell/ferese-shell/src/launcher/view.rs b/shell/ferese-shell/src/launcher/view.rs index 4392d7a..6622b93 100644 --- a/shell/ferese-shell/src/launcher/view.rs +++ b/shell/ferese-shell/src/launcher/view.rs @@ -51,7 +51,9 @@ impl FereseShell { && let Some(effects) = &launcher.effects { let _ = effects.set_presentation( - &launcher.regions.lock().unwrap(), + &launcher.regions.lock().expect( + "surface rendering state must remain consistent across callbacks", + ), launcher.progress.current as f32, [], ferese_surface_effects_v1::Role::Popover, diff --git a/shell/ferese-shell/src/media/art.rs b/shell/ferese-shell/src/media/art.rs index a9b3daf..505d948 100644 --- a/shell/ferese-shell/src/media/art.rs +++ b/shell/ferese-shell/src/media/art.rs @@ -79,7 +79,11 @@ impl Service { return; } self.requested = key.clone(); - self.queue.0.lock().unwrap().pending = key; + self.queue + .0 + .lock() + .expect("artwork decode queue must remain consistent across workers") + .pending = key; self.queue.1.notify_one(); } @@ -94,7 +98,11 @@ impl Service { impl Drop for Service { fn drop(&mut self) { - self.queue.0.lock().unwrap().stopped = true; + self.queue + .0 + .lock() + .expect("artwork decode queue must remain consistent across workers") + .stopped = true; self.queue.1.notify_one(); } } @@ -107,18 +115,27 @@ fn work( let mut cache: VecDeque = VecDeque::new(); loop { let (lock, changed) = &*queue; - let mut state = lock.lock().unwrap(); + let mut state = lock + .lock() + .expect("artwork decode queue must remain consistent across workers"); while state.pending.is_none() && !state.stopped { - state = changed.wait(state).unwrap(); + state = changed + .wait(state) + .expect("artwork decode queue must remain consistent across workers"); } if state.stopped { return; } - let key = state.pending.take().unwrap(); + let key = state + .pending + .take() + .expect("the artwork wait completed with a pending request"); drop(state); let artwork = match cache.iter().position(|art| art.key == key) { - Some(index) => cache.remove(index).unwrap(), + Some(index) => cache + .remove(index) + .expect("the cache lookup returned an existing artwork index"), None => Artwork { handle: load(&key.uri), key, diff --git a/shell/ferese-shell/src/motion.rs b/shell/ferese-shell/src/motion.rs index 5c48a6a..9b308c2 100644 --- a/shell/ferese-shell/src/motion.rs +++ b/shell/ferese-shell/src/motion.rs @@ -503,7 +503,9 @@ impl Widget for Motion<'_, M> { viewport: &Rectangle, ) { if let Some(regions) = &self.regions { - let mut collected = regions.lock().unwrap(); + let mut collected = regions + .lock() + .expect("surface rendering state must remain consistent across callbacks"); collected.clear(); self.content.as_widget_mut().operate( tree, diff --git a/shell/ferese-shell/src/notification_ui.rs b/shell/ferese-shell/src/notification_ui.rs index 1e73123..9289bb0 100644 --- a/shell/ferese-shell/src/notification_ui.rs +++ b/shell/ferese-shell/src/notification_ui.rs @@ -250,7 +250,10 @@ impl FereseShell { .as_ref() .is_some_and(|surface| !wanted || selected.as_ref() != Some(&surface.output)) { - let surface = self.notification_surface.take().unwrap(); + let surface = self + .notification_surface + .take() + .expect("notification teardown selected an existing notification surface"); tasks.push(destroy_layer_surface(surface.id)); } if !wanted { @@ -630,7 +633,9 @@ impl FereseShell { && let Some(effects) = &surface.effects { let _ = effects.set_presentation( - &surface.regions.lock().unwrap(), + &surface.regions.lock().expect( + "surface rendering state must remain consistent across callbacks", + ), 1.0, self.notifications .popup_groups_at(now) diff --git a/shell/ferese-shell/src/notifications.rs b/shell/ferese-shell/src/notifications.rs index f8a178d..e6b81f8 100644 --- a/shell/ferese-shell/src/notifications.rs +++ b/shell/ferese-shell/src/notifications.rs @@ -91,7 +91,10 @@ impl Server { hints: HashMap, expire_timeout: i32, ) -> zbus::fdo::Result { - let mut registry = self.registry.lock().unwrap(); + let mut registry = self + .registry + .lock() + .expect("notification ownership registry must remain consistent across callbacks"); let replacing = registry.live.contains_key(&replaces_id); if !replacing && registry.live.len() >= HISTORY_LIMIT { return Err(zbus::fdo::Error::LimitsExceeded( @@ -156,7 +159,10 @@ impl Server { } fn close_notification(&self, id: u32) -> zbus::fdo::Result<()> { - let mut registry = self.registry.lock().unwrap(); + let mut registry = self + .registry + .lock() + .expect("notification ownership registry must remain consistent across callbacks"); let revision = *registry .live .get(&id) @@ -249,7 +255,7 @@ impl Service { continue; } Command::Close(id, revision, reason) => { - if !registry.lock().unwrap().close(id, revision, reason) { + if !registry.lock().expect("notification ownership registry must remain consistent across callbacks").close(id, revision, reason) { continue; } connection.emit_signal( @@ -261,7 +267,7 @@ impl Service { ) } Command::Action(id, revision, action) => { - if registry.lock().unwrap().live.get(&id) != Some(&revision) { + if registry.lock().expect("notification ownership registry must remain consistent across callbacks").live.get(&id) != Some(&revision) { continue; } connection.emit_signal( diff --git a/shell/ferese-shell/src/panel_hub.rs b/shell/ferese-shell/src/panel_hub.rs index 9fbf166..8103da5 100644 --- a/shell/ferese-shell/src/panel_hub.rs +++ b/shell/ferese-shell/src/panel_hub.rs @@ -844,7 +844,9 @@ impl FereseShell { let body: Element<'_, cosmic::Action> = match mode { _ if activity.is_some() => self.hub_activity_strip( id, - activity.unwrap(), + activity.expect( + "the activity-specific hub view requires its selected activity", + ), mode == Mode::Peek, intrinsic, opacity, diff --git a/shell/ferese-shell/src/status.rs b/shell/ferese-shell/src/status.rs index 8cacd64..6e0562e 100644 --- a/shell/ferese-shell/src/status.rs +++ b/shell/ferese-shell/src/status.rs @@ -140,8 +140,14 @@ type PollState = (Mutex<(u64, bool, Option, bool, u64)>, Condvar); fn wait_for_poll(shared: &PollState) -> Option { let state = shared .1 - .wait_while(shared.0.lock().unwrap(), |state| state.1 && !state.3) - .unwrap(); + .wait_while( + shared + .0 + .lock() + .expect("status polling state must not be used after a worker panics"), + |state| state.1 && !state.3, + ) + .expect("status polling state must not be used after a worker panics"); (!state.3).then_some(state.0) } @@ -184,7 +190,11 @@ impl Service { let mut night_light_monitor = night_light::Monitor::start(polling.clone()).ok(); while let Some(before) = wait_for_poll(&polling) { - let revision = polling.0.lock().unwrap().4; + let revision = polling + .0 + .lock() + .expect("status polling state must not be used after a worker panics") + .4; if night_light_monitor.is_none() { night_light_monitor = night_light::Monitor::start(polling.clone()).ok(); } @@ -222,7 +232,10 @@ impl Service { snapshot.audio = audio.read(before); snapshot.network = network.read(before, network::read); snapshot.bluetooth = bluetooth.read(before, bluetooth::read); - let state = polling.0.lock().unwrap(); + let state = polling + .0 + .lock() + .expect("status polling state must not be used after a worker panics"); if state.3 { break; @@ -282,15 +295,24 @@ impl Service { for (id, action) in pending { { - let mut state = shared.0.lock().unwrap(); + let mut state = shared + .0 + .lock() + .expect("status polling state must not be used after a worker panics"); state.0 = id; state.1 = true; } - let settings = settings.lock().unwrap().clone(); + let settings = settings + .lock() + .expect("status polling state must not be used after a worker panics") + .clone(); let error = execute(&action, settings.as_deref()) .err() .map(|message| ActionError { action, message }); - let mut state = shared.0.lock().unwrap(); + let mut state = shared + .0 + .lock() + .expect("status polling state must not be used after a worker panics"); state.1 = false; state.2 = error; state.4 = state.4.wrapping_add(1); @@ -298,7 +320,11 @@ impl Service { } } - shared.0.lock().unwrap().3 = true; + shared + .0 + .lock() + .expect("status polling state must not be used after a worker panics") + .3 = true; shared.1.notify_one(); }); @@ -325,7 +351,10 @@ impl Service { } pub fn update_settings(&self, settings: Option>) { - *self.settings.lock().unwrap() = settings; + *self + .settings + .lock() + .expect("status polling state must not be used after a worker panics") = settings; } pub fn subscription(&self) -> cosmic::iced::Subscription { diff --git a/shell/ferese-shell/src/status/audio_cache/signals.rs b/shell/ferese-shell/src/status/audio_cache/signals.rs index f13c44a..65f0135 100644 --- a/shell/ferese-shell/src/status/audio_cache/signals.rs +++ b/shell/ferese-shell/src/status/audio_cache/signals.rs @@ -60,18 +60,26 @@ impl Signals { pw::init(); loop { - if *stop.stopped.lock().unwrap() { + if *stop.stopped.lock().expect( + "audio listener lifecycle state must remain consistent across workers", + ) { break; } let _ = monitor(state.clone(), &stop, remote.as_deref()); state.set_alive(false); - *stop.sender.lock().unwrap() = None; - let stopped = stop.stopped.lock().unwrap(); + *stop.sender.lock().expect( + "audio listener lifecycle state must remain consistent across workers", + ) = None; + let stopped = stop.stopped.lock().expect( + "audio listener lifecycle state must remain consistent across workers", + ); let (stopped, _) = stop .wake .wait_timeout_while(stopped, Duration::from_secs(5), |value| !*value) - .unwrap(); + .expect( + "audio listener lifecycle state must remain consistent across workers", + ); if *stopped { break; @@ -115,10 +123,20 @@ impl Signals { impl Drop for Signals { fn drop(&mut self) { - *self.stop.stopped.lock().unwrap() = true; + *self + .stop + .stopped + .lock() + .expect("audio listener lifecycle state must remain consistent across workers") = true; self.stop.wake.notify_one(); - if let Some(sender) = self.stop.sender.lock().unwrap().as_ref() { + if let Some(sender) = self + .stop + .sender + .lock() + .expect("audio listener lifecycle state must remain consistent across workers") + .as_ref() + { let _ = sender.send(()); } } @@ -273,9 +291,17 @@ fn monitor(state: Arc, stop: &Stop, remote: Option<&str>) -> Result<(), p mainloop.quit(); } }); - *stop.sender.lock().unwrap() = Some(sender); + *stop + .sender + .lock() + .expect("audio listener lifecycle state must remain consistent across workers") = + Some(sender); - if !*stop.stopped.lock().unwrap() { + if !*stop + .stopped + .lock() + .expect("audio listener lifecycle state must remain consistent across workers") + { mainloop.run(); } diff --git a/shell/ferese-shell/src/status_ui/lifecycle.rs b/shell/ferese-shell/src/status_ui/lifecycle.rs index ef758b4..f49097a 100644 --- a/shell/ferese-shell/src/status_ui/lifecycle.rs +++ b/shell/ferese-shell/src/status_ui/lifecycle.rs @@ -21,8 +21,12 @@ fn menu_positioner( }; SctkPositioner { anchor_rect: rectangle, - anchor: anchor.try_into().unwrap(), - gravity: gravity.try_into().unwrap(), + anchor: anchor + .try_into() + .expect("popup placement uses supported anchor and gravity combinations"), + gravity: gravity + .try_into() + .expect("popup placement uses supported anchor and gravity combinations"), offset: (rectangle.width / 2, gap), size_limits: Limits::NONE .max_width(368.0) diff --git a/shell/ferese-shell/src/status_ui/mod.rs b/shell/ferese-shell/src/status_ui/mod.rs index c35152f..bea5f0f 100644 --- a/shell/ferese-shell/src/status_ui/mod.rs +++ b/shell/ferese-shell/src/status_ui/mod.rs @@ -164,7 +164,10 @@ impl OpenMenu { } } if let Some(effects) = &self.effects { - let regions = self.regions.lock().unwrap(); + let regions = self + .regions + .lock() + .expect("surface rendering state must remain consistent across callbacks"); if let Err(error) = effects.set_regions(®ions) { eprintln!("ferese-shell: could not prepare popover material: {error}"); } @@ -355,7 +358,9 @@ impl FereseShell { |now| { if let Some(effects) = &menu.effects { let _ = effects.set_presentation( - &menu.regions.lock().unwrap(), + &menu.regions.lock().expect( + "surface rendering state must remain consistent across callbacks", + ), menu.motion.progress_at(now), [], super::ferese_surface_effects_v1::Role::Popover, diff --git a/shell/ferese-shell/src/surfaces.rs b/shell/ferese-shell/src/surfaces.rs index 2cff059..897aa90 100644 --- a/shell/ferese-shell/src/surfaces.rs +++ b/shell/ferese-shell/src/surfaces.rs @@ -557,7 +557,10 @@ impl FereseShell { .as_ref() .is_some_and(|entry| entry.id == layer_id) => { - let entry = self.notification_surface.as_mut().unwrap(); + let entry = self + .notification_surface + .as_mut() + .expect("the notification update branch matched an existing surface"); if entry.effects.is_none() { match EffectsBinding::attach_role( @@ -766,7 +769,10 @@ impl EffectsBinding { queue, })); } - let context = context.as_ref().unwrap().clone(); + let context = context + .as_ref() + .expect("surface effects binding initialized its native display context") + .clone(); let effects = context .manager .as_ref() diff --git a/shell/ferese-shell/src/system_modal.rs b/shell/ferese-shell/src/system_modal.rs index 7d57f78..f0acbbd 100644 --- a/shell/ferese-shell/src/system_modal.rs +++ b/shell/ferese-shell/src/system_modal.rs @@ -436,7 +436,11 @@ impl FereseShell { let Content::Power(power) = modal.content else { return self.close_system_modal(); }; - let approval = modal.inhibitors.as_ref().unwrap().clone(); + let approval = modal + .inhibitors + .as_ref() + .expect("power confirmation was populated with its inhibitor decision") + .clone(); let action = match power { PowerAction::Logout(serial) => { if let Some(control) = &self.control { @@ -516,7 +520,9 @@ impl FereseShell { |now| { if let Some(effects) = &surface.effects { let _ = effects.set_presentation( - &surface.regions.lock().unwrap(), + &surface.regions.lock().expect( + "surface rendering state must remain consistent across callbacks", + ), modal.motion.progress_at(now), [], ferese_surface_effects_v1::Role::Modal, diff --git a/shell/ferese-shell/src/workspace_ui.rs b/shell/ferese-shell/src/workspace_ui.rs index a591c34..d9cc104 100644 --- a/shell/ferese-shell/src/workspace_ui.rs +++ b/shell/ferese-shell/src/workspace_ui.rs @@ -305,8 +305,12 @@ impl FereseShell { width: bounds.width.round() as i32, height: bounds.height.round() as i32, }, - anchor: anchor.try_into().unwrap(), - gravity: gravity.try_into().unwrap(), + anchor: anchor + .try_into() + .expect("popup placement uses supported anchor and gravity combinations"), + gravity: gravity + .try_into() + .expect("popup placement uses supported anchor and gravity combinations"), offset: (0, gap), constraint_adjustment: 3, size_limits: cosmic::iced::Limits::NONE.max_width(320.).max_height(64.),