Explain runtime invariants and avoid redundant unwraps
This commit is contained in:
1 parent
34f2f07301
commit
1b51ba83b4
116 files changed
+1619
-491
No files matched your search
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -115,7 +115,10 @@ 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())
|
||||
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()
|
||||
};
|
||||
|
||||
+33
-11
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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(®ions) {
|
||||
return false;
|
||||
}
|
||||
@@ -606,11 +665,26 @@ fn detach(surface: &WlSurface) -> bool {
|
||||
return false;
|
||||
};
|
||||
let role_changed = effects.set_role(None);
|
||||
effects.region_opacities.lock().unwrap().clear();
|
||||
effects.pending.lock().unwrap().take();
|
||||
effects.panel.lock().unwrap().take();
|
||||
effects
|
||||
.region_opacities
|
||||
.lock()
|
||||
.expect("surface effect state must not be used after a mutator panics")
|
||||
.clear();
|
||||
effects
|
||||
.pending
|
||||
.lock()
|
||||
.expect("surface effect state must not be used after a mutator panics")
|
||||
.take();
|
||||
effects
|
||||
.panel
|
||||
.lock()
|
||||
.expect("surface effect state must not be used after a mutator panics")
|
||||
.take();
|
||||
effects.dismissing.store(false, Ordering::Release);
|
||||
let mut opacity = effects.opacity.lock().unwrap();
|
||||
let mut opacity = effects
|
||||
.opacity
|
||||
.lock()
|
||||
.expect("surface effect state must not be used after a mutator panics");
|
||||
let opacity_changed = *opacity != 1000;
|
||||
*opacity = 1000;
|
||||
let was_attached = effects.attached.swap(false, Ordering::AcqRel);
|
||||
@@ -624,7 +698,12 @@ pub(crate) fn surface_opacity(surface: &WlSurface) -> f32 {
|
||||
.data_map
|
||||
.get::<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);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,7 +177,9 @@ pub fn handle_commit(state: &mut Ferese, surface: &WlSurface) {
|
||||
std::sync::Mutex::new(None::<(LayoutState, KeyboardInteractivity)>)
|
||||
});
|
||||
let (changed, requested_focus) = {
|
||||
let mut previous = previous.lock().unwrap();
|
||||
let mut previous = previous
|
||||
.lock()
|
||||
.expect("layer configure state must remain consistent across callbacks");
|
||||
let changed = previous.as_ref().map(|(layout, _)| layout) != Some(¤t);
|
||||
let requested_focus = policy.keyboard_interactivity == KeyboardInteractivity::OnDemand
|
||||
&& previous
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -245,7 +245,11 @@ 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())
|
||||
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(
|
||||
@@ -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
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -114,8 +114,8 @@ impl IdentityRegistry {
|
||||
Some(base) => format!("{base}@{}", escaped(key)),
|
||||
None => format!("drm:{}", escaped(key)),
|
||||
};
|
||||
let preferred = self
|
||||
.known
|
||||
let preferred =
|
||||
self.known
|
||||
.get(key)
|
||||
.filter(|known| {
|
||||
base.as_ref()
|
||||
@@ -127,9 +127,13 @@ impl IdentityRegistry {
|
||||
if identity
|
||||
.as_ref()
|
||||
.is_some_and(|identity| identity.serial.is_some())
|
||||
&& !self.ambiguous.contains(base.as_ref().unwrap())
|
||||
&& !self.ambiguous.contains(base.as_ref().expect(
|
||||
"a unique serial-bearing monitor has a computed base identity",
|
||||
))
|
||||
{
|
||||
base.clone().unwrap()
|
||||
base.clone().expect(
|
||||
"a unique serial-bearing monitor has a computed base identity",
|
||||
)
|
||||
} else {
|
||||
qualified.clone()
|
||||
}
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(),
|
||||
);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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()?;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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> {
|
||||
[
|
||||
|
||||
@@ -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())),
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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],
|
||||
}
|
||||
|
||||
@@ -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])) {
|
||||
|
||||
@@ -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,7 +352,11 @@ 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 {
|
||||
*result
|
||||
.items
|
||||
.get_mut(&measured.id)
|
||||
.expect("visible items retain their allocation during width refinement") =
|
||||
Placement::Visible {
|
||||
representation,
|
||||
width,
|
||||
};
|
||||
@@ -363,14 +367,21 @@ pub fn resolve_with_gap(
|
||||
high = width
|
||||
}
|
||||
}
|
||||
*result.items.get_mut(&measured.id).unwrap() = Placement::Visible {
|
||||
*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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(()))?;
|
||||
|
||||
@@ -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
@@ -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()),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"))?;
|
||||
|
||||
@@ -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(&[]),
|
||||
)
|
||||
|
||||
@@ -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
@@ -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();
|
||||
|
||||
@@ -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>(
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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",
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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
Reference in new issue
Block a user