Explain runtime invariants and avoid redundant unwraps

This commit is contained in:
Samuel Onoja committed 2026-10-22 01:28:03 +01:00
1 parent 34f2f07301
commit 1b51ba83b4
116 files changed
+1643 -515

No files matched your search

+4 -1
View File
@@ -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);
+116 -31
View File
@@ -387,7 +387,7 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, 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<bool, ()> {
.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<bool, ()> {
}
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;
+5 -1
View File
@@ -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(),
+46 -12
View File
@@ -148,7 +148,11 @@ fn read_acpi() -> Option<bool> {
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::<zbus::zvariant::OwnedFd>()
});
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::<Probe>();
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<bool>) {
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);
+8 -1
View File
@@ -131,7 +131,14 @@ pub(super) fn cursor_on_output(state: &crate::Ferese, output: &smithay::output::
states
.data_map
.get::<CursorImageSurfaceData>()
.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(
@@ -23,13 +23,21 @@ impl<S: Session> SessionDevice<S> {
}
pub fn device_fd(&self) -> io::Result<DeviceFd> {
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<S: Session> Drop for SessionDevice<S> {
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"
+76 -24
View File
@@ -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::<Ferese>(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);
+47 -20
View File
@@ -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::<Vec<_>>();
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;
}
+5 -2
View File
@@ -115,8 +115,11 @@ impl Config {
theme: &ferese_config::theme::ResolvedTheme,
) -> Result<ThemeSettings, ConfigError> {
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()?;
+33 -11
View File
@@ -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 {
+4 -1
View File
@@ -66,7 +66,10 @@ impl<K: Eq, C: Eq, T> Queue<K, C, T> {
mut complete: impl FnMut(Pending<K, C, T>),
) {
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 {
+110 -27
View File
@@ -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<SemanticRole>) -> 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<WlSurface> {
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<FereseMaterialManagerV1, ()> for Ferese {
states
.data_map
.insert_if_missing_threadsafe(SurfaceEffectsState::new);
let effects = states.data_map.get::<SurfaceEffectsState>().unwrap();
let effects = states
.data_map
.get::<SurfaceEffectsState>()
.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<Vec<[f64; 5]>> {
.get::<SurfaceEffectsState>()?
.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<PanelPre
.get::<SurfaceEffectsState>()?
.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<u32> {
states
.data_map
.get::<SurfaceEffectsState>()
.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<Vec<[f64; 5]>> {
pub(crate) fn surface_role(surface: &WlSurface) -> Option<(SemanticRole, u64)> {
with_states(surface, |states| {
let effects = states.data_map.get::<SurfaceEffectsState>()?;
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<FereseEffectsManagerV1, ()> for Ferese {
states
.data_map
.get::<SurfaceEffectsState>()
.unwrap()
.expect("effects state was inserted before this access")
.attached
.swap(true, Ordering::AcqRel)
});
@@ -404,7 +450,7 @@ impl Dispatch<FereseEffectsManagerV1, ()> for Ferese {
states
.data_map
.get::<SurfaceEffectsState>()
.unwrap()
.expect("effects state was inserted before this access")
.presentation_supported
.store(effects.version() >= 3, Ordering::Release)
});
@@ -448,7 +494,9 @@ impl Dispatch<FereseSurfaceEffectsV1, SurfaceEffectsUserData> for Ferese {
}
with_states(&surface, |states| {
if let Some(effects) = states.data_map.get::<SurfaceEffectsState>() {
*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<FereseSurfaceEffectsV1, SurfaceEffectsUserData> for Ferese {
};
with_states(&surface, |states| {
if let Some(effects) = states.data_map.get::<SurfaceEffectsState>() {
*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<FereseSurfaceEffectsV1, SurfaceEffectsUserData> 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<FereseSurfaceEffectsV1, SurfaceEffectsUserData> for Ferese {
let Some(effects) = states.data_map.get::<SurfaceEffectsState>() 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<FereseSurfaceEffectsV1, SurfaceEffectsUserData> for Ferese {
let Some(effects) = states.data_map.get::<SurfaceEffectsState>() 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(&regions) {
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::<SurfaceEffectsState>()
.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::<SurfaceEffectsState>() {
*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);
}
});
+18 -5
View File
@@ -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(())
}
}
+3 -1
View File
@@ -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(&current);
let requested_focus = policy.keyboard_interactivity == KeyboardInteractivity::OnDemand
&& previous
+14 -3
View File
@@ -250,7 +250,10 @@ impl Dispatch<ZwlrScreencopyFrameV1, Arc<FrameData>> 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;
+36 -10
View File
@@ -35,7 +35,9 @@ struct Permit {
impl Permit {
fn reserve(budget: &Arc<Mutex<Budget>>, bytes: usize) -> Option<Self> {
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::<Arc<FrameData>>() 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<FereseWindowCaptureManagerV1, Global> 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<FereseWindowCaptureManagerV1, Global> for Ferese {
{
*resource
.data::<Arc<FrameData>>()
.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;
};
+2 -2
View File
@@ -329,9 +329,9 @@ pub(crate) fn initial_configure_sent(toplevel: &ToplevelSurface) -> bool {
states
.data_map
.get::<XdgToplevelSurfaceData>()
.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
})
}
+1 -1
View File
@@ -142,7 +142,7 @@ impl Ferese {
states
.data_map
.get::<XdgToplevelSurfaceData>()
.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,
+11 -3
View File
@@ -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()),
+16 -3
View File
@@ -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<Point<i32
states
.data_map
.get::<RendererSurfaceStateUserData>()
.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<Point<i32
&& let Some(view) = states
.data_map
.get::<RendererSurfaceStateUserData>()
.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));
}
+23 -6
View File
@@ -174,7 +174,11 @@ impl InputCapture {
self.disable_all();
let ids = self.sessions.keys().copied().collect::<Vec<_>>();
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::<Vec<_>>();
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::<Vec<_>>()})
} else {
+5 -2
View File
@@ -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::<XdgToplevelSurfaceData>().unwrap().lock().unwrap();
let attributes = states.data_map.get::<XdgToplevelSurfaceData>().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(),
+9 -2
View File
@@ -78,7 +78,9 @@ impl Subscribers {
})
.collect::<Vec<_>>();
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
+24 -20
View File
@@ -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 {
+25 -4
View File
@@ -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::<Vec<_>>();
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 };
+4 -1
View File
@@ -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
}
+7 -2
View File
@@ -108,10 +108,15 @@ fn update_shared<E: Element>(
) -> 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
}
+10 -2
View File
@@ -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,
+1 -1
View File
@@ -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,
+9 -2
View File
@@ -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<f64, Buffer> {
+8 -1
View File
@@ -561,7 +561,14 @@ pub(super) fn cursor_elements(
states
.data_map
.get::<CursorImageSurfaceData>()
.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 =
+16 -4
View File
@@ -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
}
}
+13 -3
View File
@@ -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();
+8 -6
View File
@@ -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();
+3 -1
View File
@@ -30,7 +30,9 @@ impl Ferese {
let Some(data) = states.data_map.get::<XdgToplevelSurfaceData>() 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(),
+4 -1
View File
@@ -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
{
+5 -1
View File
@@ -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(
+7 -2
View File
@@ -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
+7 -4
View File
@@ -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(|| {
@@ -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::<Vec<_>>(),
previous_viewport.remove(&workspace.id).unwrap_or_default(),
);
+14 -3
View File
@@ -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.
+10 -4
View File
@@ -236,7 +236,10 @@ impl WindowRegistry<Window> {
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<Window> {
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::<XdgToplevelSurfaceData>()
.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;
+4 -4
View File
@@ -28,9 +28,9 @@ impl Ferese {
let attributes = states
.data_map
.get::<XdgToplevelSurfaceData>()
.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::<XdgToplevelSurfaceData>()
.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(),
+24 -8
View File
@@ -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
+9 -1
View File
@@ -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
+23 -8
View File
@@ -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<Result<Pixels, String>> {
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();
+1 -1
View File
@@ -185,7 +185,7 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, 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()?;
+1 -1
View File
@@ -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 {
+7 -2
View File
@@ -310,13 +310,18 @@ pub(crate) fn parse(node: &KdlNode) -> Result<Value, Error> {
.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<Rule> {
[
+14 -4
View File
@@ -166,7 +166,10 @@ fn object(doc: &KdlDocument, parent: &str) -> Result<Map<String, Value>, 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<Valu
return Err(Error(format!("{} requires one value", node.name())));
}
Ok(args.into_iter().next().unwrap())
Ok(args
.into_iter()
.next()
.expect("a scalar KDL node was checked to have one argument"))
}
#[derive(Clone, Debug)]
@@ -305,7 +311,8 @@ impl Document {
let mut document = self.clone();
document.value["theme"] =
serde_json::to_value(&theme.tokens).expect("serializable theme tokens");
document.value["theme"]["appearance"] = serde_json::to_value(theme.appearance).unwrap();
document.value["theme"]["appearance"] = serde_json::to_value(theme.appearance)
.expect("typed configuration and IPC values must serialize to JSON");
document
}
@@ -882,7 +889,10 @@ fn kdl_value(value: &Value) -> Result<KdlValue, Error> {
} 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())),
+4 -2
View File
@@ -64,10 +64,12 @@ pub fn builtins() -> Vec<Family> {
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()
+54 -22
View File
@@ -552,8 +552,10 @@ fn token_value(tokens: &Tokens) -> Result<Value, String> {
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<Tokens, String> {
} 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<String> {
),
];
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<S
for target in [[0., 0., 0., 1.], [1.; 4]] {
for step in 0..=1000 {
let distance = f64::from(step) / 1000.;
let color = rgba(&hex(blend(requested, target, distance))).unwrap();
let color = rgba(&hex(blend(requested, target, distance)))
.expect("hex output must be a valid RGBA color");
if acceptable(color) && distance < best_distance {
best = Some(color);
best_distance = distance;
@@ -858,14 +871,17 @@ fn transform(t: &mut Tokens, accessibility: &Accessibility, warnings: &mut Vec<S
}
if accessibility.increase_contrast {
t.colors.border = hex(readable_across(
rgba(&t.colors.border).unwrap(),
rgba(&t.colors.border).expect("resolved theme colors must be valid hexadecimal RGBA"),
&backgrounds,
3.,
));
if backgrounds
.iter()
.any(|background| contrast(rgba(&t.colors.border).unwrap(), *background) < 3.)
{
if backgrounds.iter().any(|background| {
contrast(
rgba(&t.colors.border)
.expect("resolved theme colors must be valid hexadecimal RGBA"),
*background,
) < 3.
}) {
warnings.push("Border cannot reach 3:1 contrast on all configured surfaces".into());
}
resolve_material_contrast(t, minimum);
@@ -889,10 +905,14 @@ fn resolve_gradients(tokens: &mut Tokens, preset_end: Option<&str>) {
};
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,
));
}
}
+14 -3
View File
@@ -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::<Vec<_>>();
// 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);
+4 -1
View File
@@ -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;
+4 -1
View File
@@ -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),
}
+8 -2
View File
@@ -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::<Vec<_>>();
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,
+3 -3
View File
@@ -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],
}
+8 -4
View File
@@ -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])) {
+27 -16
View File
@@ -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
}
+75 -36
View File
@@ -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
+14 -5
View File
@@ -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<M: Clone + 'static>(
family: &Family,
options: TileOptions,
@@ -36,7 +40,9 @@ pub fn tile<M: Clone + 'static>(
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::collections::HashMap<String, (Family, icon::Handle)>>,
> = 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("<clipPath id=\"preview\"><polygon points=\"");
for [x, y] in points {
use std::fmt::Write as _;
write!(result, "{x:.4},{y:.4} ").unwrap();
write!(result, "{x:.4},{y:.4} ").expect("formatting into an owned String cannot fail");
}
result.push_str("\"/></clipPath></defs><g clip-path=\"url(#preview)\">");
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#"<circle cx="183" cy="16" r="10" fill="{}"/><path d="M178 16l3 3 6-6" fill="none" stroke="{foreground}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>"#, p.accent));
+9 -2
View File
@@ -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 {
+12 -2
View File
@@ -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);
+4 -1
View File
@@ -6,7 +6,10 @@ pub fn font(family: Option<&str>) -> cosmic::font::Font {
return cosmic::font::default();
};
static FONTS: OnceLock<Mutex<HashMap<String, cosmic::font::Font>>> = 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;
}
+3 -1
View File
@@ -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
+3 -1
View File
@@ -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.
+11 -5
View File
@@ -16,7 +16,9 @@ pub(super) struct Updates(Arc<(Mutex<Pending>, 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);
+18 -4
View File
@@ -15,7 +15,11 @@ fn prepare(source: &str) -> Result<String, Box<dyn Error>> {
serde_json::to_value(vec![panel::Panel::default()])?,
)?;
}
let panels: Vec<panel::Panel> = serde_json::from_value(doc.get("panels").unwrap().clone())?;
let panels: Vec<panel::Panel> = 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<String, Box<dyn Error>> {
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<String, Box<dyn Error>> {
)?;
}
}
let panels = serde_json::from_value::<Vec<panel::Panel>>(doc.get("panels").unwrap().clone())?;
let panels = serde_json::from_value::<Vec<panel::Panel>>(
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<dyn Error>> {
}
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(()))?;
+1 -1
View File
@@ -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",
+22 -5
View File
@@ -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<Child, String> {
.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<dyn std::error::Error>> {
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()),
+4 -1
View File
@@ -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
+5 -1
View File
@@ -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();
+8 -2
View File
@@ -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<Option<u64>, 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
+5 -3
View File
@@ -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
+12 -2
View File
@@ -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<u32> = serde_json::from_value(result["failed_barriers"].clone())
.map_err(|_| invalid("Invalid native barrier response"))?;
+5 -2
View File
@@ -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(&[]),
)
+2 -1
View File
@@ -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())?;
+10 -2
View File
@@ -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();
+2 -1
View File
@@ -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<B: serde::Serialize + zbus::zvariant::DynamicType>(
+5 -2
View File
@@ -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
+20 -6
View File
@@ -210,7 +210,8 @@ impl GlobalShortcuts {
session: Arc<Session>,
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<Option<Shortcuts>, 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(
+18 -9
View File
@@ -106,7 +106,7 @@ fn pod(object: spa::pod::Object) -> Vec<u8> {
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;
+4 -1
View File
@@ -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);
+14 -3
View File
@@ -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);
}
}
+14 -3
View File
@@ -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<dyn std::error::Error>> {
@@ -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 {
+3 -1
View File
@@ -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",
)))
}
}
+67 -14
View File
@@ -68,7 +68,10 @@ enum Error {
impl Pairing {
fn begin(&self, target: &str) -> Result<u64, String> {
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<String> {
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<Prompt> {
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<Zeroizing<String>>) {
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<Zeroizing<String>, 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());
@@ -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)
}
}
@@ -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);
@@ -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,
)
{
+6 -1
View File
@@ -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 {
@@ -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);
+10 -3
View File
@@ -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(),
));
}
+6 -3
View File
@@ -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(
+7 -3
View File
@@ -258,7 +258,7 @@ impl Widget<Message, Theme, Renderer> 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<Message, Theme, Renderer> 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<Message, Theme, Renderer> 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()
+18 -8
View File
@@ -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<Vec<Edit>, 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<Vec<Edit>, 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<Vec<Edit>, 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::<usize>();
@@ -366,7 +375,7 @@ pub(super) fn plan(snapshot: &Snapshot, action: Action) -> Result<Vec<Edit>, 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<Vec<Edit>, 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<Vec<Edit>, 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)
}
+9 -4
View File
@@ -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 } => {
@@ -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,
);
}
+5 -1
View File
@@ -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())
+5 -1
View File
@@ -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.
};
Loaded 100 of 116 files, more files were not shown because too many files have changed in this diff. Show more