micro optimization fixes

This commit is contained in:
Samuel Onoja committed 2026-10-01 20:47:05 +01:00
1 parent 0d7203f84b
commit da60ecb719
36 files changed
+1781 -521

No files matched your search

+239 -56
View File
@@ -20,7 +20,56 @@ impl Ferese {
self.last_animation_tick = Instant::now();
}
fn animations_need_tick(&self) -> bool {
if self.focus_swipe.is_some()
|| !self.workspace_slides.is_empty()
|| !self.dismissing_popups.is_empty()
|| self.render.snapshots().next().is_some()
|| self.overview.needs_tick()
{
return true;
}
let moving = |value: &AnimatedValue| value.current != value.target || value.velocity != 0.0;
if self.viewport_animations.iter().any(|(workspace, viewport)| {
self.output_workspaces
.output_for_workspace(*workspace)
.is_some_and(|output| self.output_workspaces.active_workspace(output) == Some(*workspace))
&& moving(viewport)
}) {
return true;
}
let selected = if self.overview.is_active() {
self.overview.selected()
} else {
self.focused_window
};
self.windows.records().any(|(&id, record)| {
let focus = if selected == Some(id) { 1.0 } else { 0.0 };
let dim = crate::dimming::target(
self.inactive_dim,
self.focused_window,
id,
self.overview.is_presenting(),
);
record_needs_tick(record, focus, dim, || {
self.windows
.window(id)
.is_some_and(|window| self.space.element_location(window).is_some())
})
})
}
pub(super) fn advance_animations_by(&mut self, delta: std::time::Duration) -> bool {
if !self.animations_need_tick() {
self.sync_window_stacking();
return false;
}
let delta = delta.mul_f64(self.animation_speed);
if self
.focus_swipe
@@ -30,21 +79,6 @@ impl Ferese {
self.focus_swipe = None;
}
let visible_workspaces = if self.viewport_animations.is_empty() {
HashSet::new()
} else {
self.space
.outputs()
.filter_map(|output| self.output_ids.get(output))
.filter_map(|output| self.output_workspaces.active_workspace(*output))
.collect::<HashSet<_>>()
};
let windows = self
.space
.elements()
.filter_map(|window| self.windows.ids().get(window).copied().map(|id| (window.clone(), id)))
.collect::<Vec<_>>();
let mut active_animation = false;
let mut completed_slides = Vec::new();
for (output, slide) in &mut self.workspace_slides {
@@ -102,9 +136,12 @@ impl Ferese {
dim_changed |= previous != focus.current;
}
for (_, id) in &windows {
let target = crate::dimming::target(dim_settings, self.focused_window, *id, self.overview.is_presenting());
let Some(record) = self.windows.record_mut(*id) else {
for window in self.space.elements() {
let Some(id) = self.windows.ids().get(window).copied() else {
continue;
};
let target = crate::dimming::target(dim_settings, self.focused_window, id, self.overview.is_presenting());
let Some(record) = self.windows.record_mut(id) else {
continue;
};
@@ -184,7 +221,11 @@ impl Ferese {
// Keep scheduling frames while waiting, so the deadline cannot stall.
active_animation |= !blocked_workspaces.is_empty();
for (workspace, viewport) in &mut self.viewport_animations {
if !visible_workspaces.contains(workspace) {
if !self
.output_workspaces
.output_for_workspace(*workspace)
.is_some_and(|output| self.output_workspaces.active_workspace(output) == Some(*workspace))
{
continue;
}
@@ -202,8 +243,13 @@ impl Ferese {
}
}
let mut settled_coupled_widths = Vec::new();
for (window, id) in windows {
let mut remaps = Vec::new();
for window in self.space.elements() {
let Some(id) = self.windows.ids().get(window).copied() else {
continue;
};
if self
.workspaces
.workspace_for_window(id)
@@ -229,6 +275,11 @@ impl Ferese {
geometry.visual.velocity.width = 0.0;
}
active_animation |= geometry.advance(delta, self.spring_config, self.animations_enabled);
if zooming != geometry.is_zooming() {
self.stacking_cache.invalidate();
}
if let Some(target) = coupled_target {
geometry.visual.target.width = target;
}
@@ -262,7 +313,7 @@ impl Ferese {
geometry.visual.current.width = width.current;
geometry.visual.velocity.width = width.velocity;
if !width_active {
settled_coupled_widths.push(id);
record.coupled_width = None;
}
}
@@ -287,12 +338,18 @@ impl Ferese {
}
let visual = geometry.visual.current;
self.space
.map_element(window, (visual.x.round() as i32, visual.y.round() as i32), false);
let location = (visual.x.round() as i32, visual.y.round() as i32).into();
if self.space.element_location(window) != Some(location) {
remaps.push((id, location));
}
}
for id in settled_coupled_widths {
self.windows.update(id, |record| record.coupled_width = None);
for (id, location) in remaps {
if let Some(window) = self.windows.window(id) {
self.space.map_element(window.clone(), location, false);
self.stacking_cache.remapped();
}
}
if !completed_slides.is_empty() {
@@ -320,6 +377,7 @@ impl Ferese {
active_animation |= self
.overview
.advance(delta, self.spring_config, self.animations_enabled);
self.refresh_workspace_slide_offsets();
self.sync_window_stacking();
if active_animation || dim_changed {
@@ -337,48 +395,173 @@ impl Ferese {
}
pub(crate) fn workspace_slide_offset(&self, window: WindowId) -> (f64, f64) {
self.workspace_slide_offset_at(window, Duration::ZERO)
self.workspaces
.workspace_for_window(window)
.and_then(|workspace| self.workspace_slide_offsets.get(&workspace).copied())
.unwrap_or_default()
}
pub(super) fn workspace_slide_offset_at(&self, window: WindowId, delta: Duration) -> (f64, f64) {
let Some(workspace) = self.workspaces.workspace_for_window(window) else {
return (0.0, 0.0);
};
pub(super) fn sample_workspace_slide_offsets(
&self,
output: Option<OutputId>,
delta: Duration,
) -> HashMap<WorkspaceId, (f64, f64)> {
let mut offsets = HashMap::new();
self.fill_workspace_slide_offsets(output, delta, &mut offsets);
offsets
}
let Some(output_id) = self
.workspace_slides
.iter()
.find_map(|(output, slide)| slide.contains(workspace).then_some(*output))
.or_else(|| self.output_workspaces.output_for_workspace(workspace))
else {
return (0.0, 0.0);
};
pub(super) fn refresh_workspace_slide_offsets(&mut self) {
// Reuse the input snapshot's allocation. Predicted render snapshots
// remain separate and never change hit-testing or gesture state.
let mut offsets = std::mem::take(&mut self.workspace_slide_offsets);
offsets.clear();
self.fill_workspace_slide_offsets(None, Duration::ZERO, &mut offsets);
self.workspace_slide_offsets = offsets;
}
let Some(slide) = self.workspace_slides.get(&output_id) else {
return (0.0, 0.0);
};
fn fill_workspace_slide_offsets(
&self,
output: Option<OutputId>,
delta: Duration,
offsets: &mut HashMap<WorkspaceId, (f64, f64)>,
) {
for (output_id, slide) in &self.workspace_slides {
if output.is_some_and(|output| output != *output_id) {
continue;
}
let Some(size) = self
.output_ids
.iter()
.find(|(_, id)| **id == output_id)
.and_then(|(output, _)| self.space.output_geometry(output))
.map(|geometry| geometry.size)
else {
return (0.0, 0.0);
};
let Some(size) = self
.outputs_by_id
.get(output_id)
.and_then(|output| self.space.output_geometry(output))
.map(|geometry| geometry.size)
else {
continue;
};
let progress = slide.progress_at(delta);
let mut slide = slide.clone();
if slide.held_progress.is_none() && !delta.is_zero() {
slide.advance(delta);
for item in &slide.items {
let position = item.start.between(item.target, progress);
offsets.insert(
item.workspace,
(position.x * f64::from(size.w), position.y * f64::from(size.h)),
);
}
}
slide.offset(workspace, f64::from(size.w), f64::from(size.h))
}
pub(crate) fn cancel_workspace_slides(&mut self) -> bool {
let active = !self.workspace_slides.is_empty();
self.workspace_slides.clear();
self.workspace_slide_offsets.clear();
active
}
}
// Inspect current targets, not the previous frame's activity. Target changes
// and client waits must wake a desktop that was settled on its last tick.
fn record_needs_tick(
record: &super::window_registry::WindowRecord,
focus: f64,
dim: f64,
mapped: impl FnOnce() -> bool,
) -> bool {
if record.resize.is_some()
|| record.closing.as_ref().is_some_and(|close| !close.close_sent)
|| record.focus.as_ref().is_none_or(|motion| motion.needs_update(focus))
{
return true;
}
let moving = |value: &AnimatedValue| value.current != value.target || value.velocity != 0.0;
let pending = record.dimming.as_ref().is_none_or(|motion| motion.needs_update(dim))
|| record.world_x.as_ref().is_some_and(|(_, world)| moving(world))
|| record.coupled_width.is_some()
|| record.geometry.is_some_and(|geometry| {
geometry.is_zooming()
|| geometry.presentation_changed
|| geometry.client.waiting_for_commit()
|| geometry.visual.current != geometry.visual.target
|| geometry.visual.velocity != ferese_animation::RectVelocity::default()
|| (!record.natural_floating_pending
&& geometry.client.last_configured_size != Some(ClientSize::from_rect(geometry.logical)))
});
// Hidden geometry and dimming do not advance in the full path.
// A stale hidden target must not keep waking the whole desktop.
pending && mapped()
}
#[cfg(test)]
mod tests {
use super::super::window_registry::WindowRecord;
use super::*;
fn settled() -> WindowRecord {
let rect = Rect::new(0.0, 0.0, 400.0, 300.0);
let mut geometry = WindowGeometry::new(rect, Some(ClientSize::from_rect(rect)));
geometry.presentation_changed = false;
WindowRecord {
geometry: Some(geometry),
focus: Some(DimAnimation::new(1.0)),
dimming: Some(DimAnimation::new(0.0)),
..Default::default()
}
}
#[test]
fn idle_check_skips_mapping_lookup_but_new_focus_and_dim_targets_wake_it() {
let record = settled();
for _ in 0..100 {
assert!(!record_needs_tick(&record, 1.0, 0.0, || panic!(
"settled record needs no mapping lookup"
)));
}
assert!(record_needs_tick(&record, 0.0, 0.0, || true));
assert!(record_needs_tick(&record, 1.0, 0.15, || true));
assert!(!record_needs_tick(&record, 1.0, 0.15, || false));
}
#[test]
fn geometry_motion_client_waits_and_resize_barriers_cannot_be_skipped() {
let mut record = settled();
record
.geometry
.as_mut()
.unwrap()
.visual
.set_target(Rect::new(10.0, 0.0, 400.0, 300.0));
assert!(record_needs_tick(&record, 1.0, 0.0, || true));
assert!(!record_needs_tick(&record, 1.0, 0.0, || false));
let mut record = settled();
record.geometry.as_mut().unwrap().client.request_size(
ClientSize {
width: 500,
height: 300,
},
Duration::ZERO,
);
assert!(record_needs_tick(&record, 1.0, 0.0, || true));
record.resize = Some(crate::resize_transaction::ResizeTransaction::new(
1.into(),
Duration::ZERO,
));
assert!(record_needs_tick(&record, 1.0, 0.0, || false));
}
#[test]
fn close_requests_and_coupled_width_cleanup_still_get_their_final_tick() {
let mut record = settled();
record.closing = Some(ClosingAnimation::default());
assert!(record_needs_tick(&record, 1.0, 0.0, || false));
record.closing.as_mut().unwrap().close_sent = true;
assert!(!record_needs_tick(&record, 1.0, 0.0, || true));
record.coupled_width = Some((WorkspaceId(1), AnimatedValue::new(400.0)));
assert!(record_needs_tick(&record, 1.0, 0.0, || true));
}
}
+10 -19
View File
@@ -17,12 +17,7 @@ impl Ferese {
Some(WindowPlacement::Floating { rect }) => rect,
None => return,
};
if let Some(handle) = self
.windows
.ids()
.iter()
.find_map(|(handle, id)| (*id == window).then_some(handle.clone()))
{
if let Some(handle) = self.windows.window(window).cloned() {
if matches!(
self.workspaces.placement(window),
Some(WindowPlacement::Floating { .. })
@@ -178,6 +173,10 @@ impl Ferese {
.geometry
.get_or_insert_with(|| WindowGeometry::new(rect, client_size(window)));
if geometry.is_zooming() {
self.stacking_cache.invalidate();
}
if let Some(size) = geometry.follow_pointer(rect, self.start_time.elapsed())
&& let Some(toplevel) = window.toplevel()
{
@@ -188,7 +187,7 @@ impl Ferese {
}
self.windows.update(id, |record| record.world_x = None);
self.windows.update(id, |record| record.coupled_width = None);
self.space.map_element(window.clone(), location, false);
self.map_window_geometry(window.clone(), location);
self.sync_window_stacking();
// Move/resize/cancel callbacks run with Smithay's pointer mutex held.
// Cursor rendering reads current_location(), which would lock it again.
@@ -269,14 +268,7 @@ impl Ferese {
let (app, parent) = Self::floating_metadata(window);
let parent_rect = parent
.as_ref()
.and_then(|surface| {
self.windows.ids().iter().find_map(|(window, id)| {
window
.toplevel()
.is_some_and(|top| top.wl_surface() == surface)
.then_some(*id)
})
})
.and_then(|surface| self.windows.id_for_surface(surface))
.and_then(|parent| self.presented_window_rect(parent))
.and_then(|rect| intersection(rect, work));
let dialog = crate::window_rules::is_native_dialog(app.as_deref());
@@ -420,10 +412,9 @@ impl Ferese {
.workspaces
.workspace_for_window(window)
.and_then(|workspace| self.output_workspaces.output_for_workspace(workspace));
self.output_ids
.iter()
.find(|(_, id)| Some(**id) == output_id)
.and_then(|(output, _)| self.output_bounds_for(output))
output_id
.and_then(|id| self.outputs_by_id.get(&id))
.and_then(|output| self.output_bounds_for(output))
.or_else(|| self.output_bounds())
}
+2 -2
View File
@@ -49,7 +49,7 @@ impl Ferese {
let available = self.focus_candidates();
if !preview {
self.cancel_focus_cycle();
let order = self.focus_history.candidates(self.focused_window, available.clone());
let order = self.focus_history.candidates(self.focused_window, &available);
let mut cycle = ferese_core::FocusCycle::new(order, self.focused_window);
if let Some(id) = cycle.advance(reverse, &available) {
self.activate_managed_window(id);
@@ -57,7 +57,7 @@ impl Ferese {
return;
}
if self.focus_cycle.is_none() {
let order = self.focus_history.candidates(self.focused_window, available.clone());
let order = self.focus_history.candidates(self.focused_window, &available);
if order.len() < 2 {
return;
}
+1 -4
View File
@@ -166,10 +166,7 @@ impl Ferese {
};
if overview_active {
let mut candidates = self.windows.ids().keys().cloned().collect::<Vec<_>>();
// Match Overview's front-to-back render order during overlapping motion.
candidates.sort_by_key(|window| std::cmp::Reverse(self.windows.ids().get(window).map_or(0, |id| id.0)));
candidates.iter().find_map(hit)
self.windows.overview_windows().find_map(hit)
} else {
self.space.elements().rev().find_map(hit)
}
+54 -26
View File
@@ -59,6 +59,7 @@ impl Ferese {
// Consume idle time before setting new targets; it must not become a
// large first animation step after a keypress on an idle desktop.
self.advance_animations(Instant::now());
self.stacking_cache.invalidate();
self.arrange_layers();
let mut previous_scrolling_world_x = self.windows.take_world_positions();
let pending_column_width_cycles = self.windows.take_column_width_requests();
@@ -303,7 +304,7 @@ impl Ferese {
let location = (visual.x.round() as i32, visual.y.round() as i32);
self.space.map_element(window.clone(), location, false);
self.map_window_geometry(window.clone(), location.into());
if let Some(toplevel) = window.toplevel() {
let state_changed = toplevel.with_pending_state(|state| {
if natural_pending {
@@ -372,39 +373,66 @@ impl Ferese {
}
self.space.raise_element(window, activate);
self.stacking_cache.remapped();
self.sync_window_stacking();
}
pub(crate) fn invalidate_window_stacking(&mut self) {
self.stacking_cache.invalidate();
}
pub(crate) fn sync_window_stacking(&mut self) {
let mut windows = self.space.elements().cloned().collect::<Vec<_>>();
windows.sort_by_key(|window| {
let id = self.windows.ids().get(window).copied();
let geometry = id.and_then(|id| self.windows.geometry(&id));
let floating =
id.is_some_and(|id| matches!(self.workspaces.placement(id), Some(WindowPlacement::Floating { .. })));
let priority = crate::stacking::layer_priority(
floating,
geometry.is_some_and(|geometry| geometry.is_zooming()),
geometry.is_some_and(|geometry| geometry.is_fullscreen()),
floating
&& id.is_some_and(|id| {
self.floating_above_fullscreen.get(&id).is_some_and(|parent| {
self.workspaces
.workspace_for_window(id)
.and_then(|workspace| self.workspaces.workspace(workspace))
.is_some_and(|workspace| workspace.fullscreen == Some(*parent))
})
}),
);
(priority, id.map_or(usize::MAX, |id| self.window_stack.rank(id)))
});
if stacking_order_settled(self.space.elements(), &windows, |window| window.z_index()) {
let revision = self.window_stack.revision();
let rebuild = self.stacking_cache.needs_rebuild(revision, self.space.elements().len());
if !rebuild && !self.stacking_cache.needs_restore() {
return;
}
for window in windows {
self.space.raise_element(&window, false);
if rebuild {
let mut windows = self.space.elements().cloned().collect::<Vec<_>>();
windows.sort_by_key(|window| {
let id = self.windows.ids().get(window).copied();
let geometry = id.and_then(|id| self.windows.geometry(&id));
let floating = id
.is_some_and(|id| matches!(self.workspaces.placement(id), Some(WindowPlacement::Floating { .. })));
let priority = crate::stacking::layer_priority(
floating,
geometry.is_some_and(|geometry| geometry.is_zooming()),
geometry.is_some_and(|geometry| geometry.is_fullscreen()),
floating
&& id.is_some_and(|id| {
self.floating_above_fullscreen.get(&id).is_some_and(|parent| {
self.workspaces
.workspace_for_window(id)
.and_then(|workspace| self.workspaces.workspace(workspace))
.is_some_and(|workspace| workspace.fullscreen == Some(*parent))
})
}),
);
(priority, id.map_or(usize::MAX, |id| self.window_stack.rank(id)))
});
self.stacking_cache.replace(windows, revision);
}
if !stacking_order_settled(self.space.elements(), self.stacking_cache.order(), |window| {
window.z_index()
}) {
for window in self.stacking_cache.order() {
self.space.raise_element(window, false);
}
}
self.stacking_cache.restored();
}
pub(crate) fn map_window_geometry(&mut self, window: Window, location: Point<i32, Logical>) {
if self.space.element_location(&window) == Some(location) {
return;
}
self.space.map_element(window, location, false);
self.stacking_cache.remapped();
}
pub(super) fn tiled_layout(&mut self, bounds: Rect) -> Result<LayoutResult, ferese_layout::LayoutError> {
+8 -20
View File
@@ -139,12 +139,7 @@ impl Ferese {
{
tracing::warn!(%error, ?current, ?next, "failed to slide swipe focus");
}
let Some(window) = self
.windows
.ids()
.iter()
.find_map(|(window, id)| (*id == next).then(|| window.clone()))
else {
let Some(window) = self.windows.window(next).cloned() else {
return;
};
let Some(surface) = window.toplevel().map(|toplevel| toplevel.wl_surface().clone()) else {
@@ -306,12 +301,7 @@ impl Ferese {
}
pub(crate) fn activate_managed_window(&mut self, id: WindowId) -> bool {
let Some(window) = self
.windows
.ids()
.iter()
.find_map(|(window, window_id)| (*window_id == id).then(|| window.clone()))
else {
let Some(window) = self.windows.window(id).cloned() else {
return false;
};
if !activate_window_workspace(&mut self.workspaces, &mut self.output_workspaces, id) {
@@ -441,6 +431,7 @@ impl Ferese {
.get_mut(&output)
.expect("gesture output exists")
.held_progress = Some(progress);
self.refresh_workspace_slide_offsets();
self.backdrop_generation = self.backdrop_generation.wrapping_add(1);
self.cursor_redraw_pending = true;
return;
@@ -555,14 +546,11 @@ impl Ferese {
self.focus_lock_surface();
return;
}
let surface = self.focused_window.and_then(|focused| {
self.windows.ids().iter().find_map(|(window, id)| {
(*id == focused)
.then(|| window.toplevel())
.flatten()
.map(|toplevel| toplevel.wl_surface().clone())
})
});
let surface = self
.focused_window
.and_then(|focused| self.windows.window(focused))
.and_then(|window| window.toplevel())
.map(|toplevel| toplevel.wl_surface().clone());
self.seat.get_keyboard().expect("seat has a keyboard").set_focus(
self,
+5 -3
View File
@@ -29,6 +29,8 @@ impl Ferese {
match registration {
Ok(_) => {
self.output_ids.insert(output.clone(), output_id);
self.outputs_by_id.insert(output_id, output.clone());
self.output_names.insert(output_id, output.name());
self.restore_output_focus();
let visible = self.visible_workspace_ids();
self.reconcile_workspaces(&visible);
@@ -45,6 +47,8 @@ impl Ferese {
return;
};
self.outputs_by_id.remove(&output_id);
self.output_names.remove(&output_id);
self.workspace_slides.remove(&output_id);
self.render.remove_output(output_id);
self.pending_screencopies.retain(|capture| {
@@ -113,9 +117,7 @@ impl Ferese {
pub(crate) fn focused_output(&self) -> Option<&Output> {
let focused = self.output_workspaces.focused_output()?;
self.output_ids
.iter()
.find_map(|(output, id)| (*id == focused).then_some(output))
self.outputs_by_id.get(&focused)
}
pub(crate) fn restore_output_focus(&mut self) {
+153 -15
View File
@@ -5,6 +5,8 @@ use super::*;
pub(crate) struct FrameScene {
pub windows: HashMap<WindowId, WindowFrame>,
pub overview: crate::overview::OverviewMotion,
// Plane selection and scheduling reuse the activity sampled with this scene.
pub animating: bool,
}
pub(crate) struct WindowFrame {
@@ -17,6 +19,31 @@ pub(crate) struct WindowFrame {
}
impl Ferese {
fn output_window_records<'a>(
&'a self,
output: &'a Output,
block_resizes: bool,
) -> impl Iterator<Item = (WindowId, &'a super::window_registry::WindowRecord, bool)> + 'a {
self.output_id(output)
.into_iter()
.flat_map(move |output| self.output_workspaces.assigned_workspaces(output))
.filter_map(move |id| self.workspaces.workspace(id))
.flat_map(move |workspace| {
let blocked = block_resizes
&& workspace
.layout
.window_ids()
.chain(workspace.floating.iter().copied())
.any(|id| self.windows.record(id).is_some_and(|record| record.resize.is_some()));
workspace
.layout
.window_ids()
.chain(workspace.floating.iter().copied())
.filter_map(move |id| Some((id, self.windows.record(id)?, blocked)))
})
}
pub(crate) fn output_has_animations(&self, output: &Output) -> bool {
if self.overview.is_animating(self.spring_config) || !self.dismissing_popups.is_empty() {
return true;
@@ -30,17 +57,17 @@ impl Ferese {
return true;
}
self.windows.records().any(|(id, record)| {
self.output_window_records(output, false).any(|(id, record, _)| {
let Some(mut geometry) = record.geometry else {
return false;
};
if !self.window_belongs_to_output(*id, output) {
if !self.window_belongs_to_output(id, output) {
return false;
}
if record.resize.is_some()
|| self.render.snapshot(id).is_some()
|| self.render.snapshot(&id).is_some()
|| record.closing.as_ref().is_some_and(|close| !close.close_sent)
|| record.focus.as_ref().is_some_and(|focus| focus.is_animating())
|| record.dimming.as_ref().is_some_and(|dim| dim.is_animating())
@@ -52,7 +79,7 @@ impl Ferese {
return true;
}
let Some(workspace) = self.workspaces.workspace_for_window(*id) else {
let Some(workspace) = self.workspaces.workspace_for_window(id) else {
return false;
};
@@ -71,28 +98,28 @@ impl Ferese {
}
pub(crate) fn sample_frame(&self, output: &Output, horizon: Duration) -> FrameScene {
let delta = if self.animations_enabled && self.output_has_animations(output) {
let animating = self.output_has_animations(output);
let delta = if self.animations_enabled && animating {
horizon.min(Duration::from_millis(100)).mul_f64(self.animation_speed)
} else {
Duration::ZERO
};
let overview = self.overview.sample(delta, self.spring_config);
let blocked = self
.windows
.resizing()
.filter_map(|id| self.workspaces.workspace_for_window(*id))
.collect::<HashSet<_>>();
let predicted_slide_offsets =
(!delta.is_zero()).then(|| self.sample_workspace_slide_offsets(self.output_id(output), delta));
let slide_offsets = predicted_slide_offsets
.as_ref()
.unwrap_or(&self.workspace_slide_offsets);
let mut windows = HashMap::new();
for (&id, record) in self.windows.records() {
for (id, record, blocked) in self.output_window_records(output, !delta.is_zero()) {
let Some(original) = record.geometry else { continue };
if !self.window_belongs_to_output(id, output) {
continue;
}
let workspace = self.workspaces.workspace_for_window(id);
let world = record.world_x.as_ref().and_then(|(workspace, world)| {
let viewport = self.viewport_animations.get(workspace)?;
let held = self
@@ -103,7 +130,7 @@ impl Ferese {
Some((*world, *viewport, held))
});
let width = record.coupled_width.as_ref().map(|(_, width)| *width);
let geometry = if delta.is_zero() || workspace.is_some_and(|workspace| blocked.contains(&workspace)) {
let geometry = if delta.is_zero() || blocked {
original
} else {
predict_geometry(
@@ -118,7 +145,11 @@ impl Ferese {
let mut rect = overview.presented_rect(id, geometry.visual.current);
if !overview.is_presenting() {
let (x, y) = self.workspace_slide_offset_at(id, delta);
let (x, y) = self
.workspaces
.workspace_for_window(id)
.and_then(|workspace| slide_offsets.get(&workspace).copied())
.unwrap_or_default();
rect.x += x;
rect.y += y;
}
@@ -160,7 +191,11 @@ impl Ferese {
);
}
FrameScene { windows, overview }
FrameScene {
windows,
overview,
animating,
}
}
}
@@ -259,12 +294,109 @@ mod tests {
{
let forecast = state.sample_frame(&output, horizon);
assert!(forecast.windows[&id].geometry.visual.current.x > geometry.visual.current.x);
assert!(forecast.animating);
}
assert_eq!(*state.windows.geometry(&id).unwrap(), geometry);
assert_eq!(state.last_animation_tick, tick);
}
// Sampling one output must not visit or include another output's records.
let other = Output::new(
"other-forecast".into(),
smithay::output::PhysicalProperties {
size: (0, 0).into(),
subpixel: smithay::output::Subpixel::Unknown,
make: "test".into(),
model: "test".into(),
},
);
other.change_current_state(
Some(smithay::output::Mode {
size: (1280, 720).into(),
refresh: 240_000,
}),
Some(smithay::utils::Transform::Normal),
None,
Some((1920, 0).into()),
);
state.space.map_output(&other, (1920, 0));
state.register_output(&other, "other-forecast".into());
let other_workspace = state
.output_workspaces
.active_workspace(state.output_id(&other).unwrap())
.unwrap();
let other_id = WindowId(2);
state
.workspaces
.insert_floating_window(other_id, other_workspace, geometry.visual.current, false)
.unwrap();
state.windows.records.insert(
other_id,
super::super::window_registry::WindowRecord {
geometry: Some(geometry),
..Default::default()
},
);
assert_eq!(
state
.output_window_records(&output, false)
.map(|(id, _, _)| id)
.collect::<Vec<_>>(),
vec![id]
);
assert_eq!(
state
.output_window_records(&other, false)
.map(|(id, _, _)| id)
.collect::<Vec<_>>(),
vec![other_id]
);
assert!(
!state
.sample_frame(&output, Duration::from_millis(8))
.windows
.contains_key(&other_id)
);
assert!(
!state
.sample_frame(&other, Duration::from_millis(4))
.windows
.contains_key(&id)
);
let output_id = state.output_id(&output).unwrap();
let from = state.workspaces.workspace_for_window(id).unwrap();
let to = state.workspaces.create_workspace();
state.output_workspaces.assign_workspace(output_id, to).unwrap();
let mut slide = WorkspaceSlide::new(None, from, to, SwipeDirection::Left);
slide.held_progress = Some(0.25);
state.workspace_slides.insert(output_id, slide);
state.refresh_workspace_slide_offsets();
let offsets = state.workspace_slide_offsets.clone();
assert_eq!(offsets[&from].0, -480.0);
assert_eq!(offsets[&to].0, 1440.0);
assert_eq!(
state.sample_frame(&output, Duration::from_millis(8)).windows[&id]
.rect
.x,
state.sample_frame(&output, Duration::from_millis(8)).windows[&id]
.geometry
.visual
.current
.x
- 480.0
);
assert_eq!(state.workspace_slide_offsets, offsets);
assert!(
state
.sample_workspace_slide_offsets(state.output_id(&other), Duration::from_millis(8))
.is_empty()
);
state.cancel_workspace_slides();
assert!(state.workspace_slide_offsets.is_empty());
// A prediction must neither advance nor expire a client resize barrier.
state.windows.set_transaction(
id,
@@ -278,6 +410,12 @@ mod tests {
state.animations_enabled = false;
let forecast = state.sample_frame(&output, Duration::from_millis(16));
assert_eq!(forecast.windows[&id].geometry, geometry);
let settled = WindowGeometry::new(Rect::new(0., 0., 400., 300.), None);
state.windows.set_geometry(id, settled);
state.animations_enabled = true;
assert!(!state.sample_frame(&output, Duration::ZERO).animating);
assert!(state.sample_frame(&other, Duration::ZERO).animating);
}
#[test]
+141 -6
View File
@@ -4,6 +4,10 @@ use std::hash::Hash;
use ferese_animation::{AnimatedValue, WindowGeometry};
use ferese_core::WorkspaceId;
use ferese_layout::{ColumnWidth, Rect, WindowId};
use smithay::desktop::Window;
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
use smithay::wayland::compositor::with_states;
use smithay::wayland::shell::xdg::XdgToplevelSurfaceData;
use crate::resize_transaction::ResizeTransaction;
@@ -12,6 +16,10 @@ use crate::resize_transaction::ResizeTransaction;
#[derive(Default)]
pub(crate) struct WindowRecord {
pub geometry: Option<WindowGeometry>,
pub surface: Option<WlSurface>,
pub app_id: String,
pub title: String,
pub metadata_revision: u64,
pub resize: Option<ResizeTransaction>,
pub focus: Option<crate::dimming::DimAnimation>,
pub dimming: Option<crate::dimming::DimAnimation>,
@@ -32,7 +40,11 @@ pub(crate) struct WindowRecord {
/// handle always updates its record as well. IDs are never reused on remapping.
pub(crate) struct WindowRegistry<W> {
ids: HashMap<W, WindowId>,
by_id: HashMap<WindowId, W>,
surface_ids: HashMap<WlSurface, WindowId>,
pub(super) records: HashMap<WindowId, WindowRecord>,
// Ascending IDs; overview reverses this for front-to-back traversal.
overview_order: Vec<WindowId>,
next_id: u64,
}
@@ -40,7 +52,10 @@ impl<W> Default for WindowRegistry<W> {
fn default() -> Self {
Self {
ids: HashMap::new(),
by_id: HashMap::new(),
surface_ids: HashMap::new(),
records: HashMap::new(),
overview_order: Vec::new(),
next_id: 1,
}
}
@@ -54,10 +69,16 @@ impl<W: Eq + Hash> WindowRegistry<W> {
id
}
pub fn register(&mut self, window: W, id: WindowId) {
pub fn register(&mut self, window: W, id: WindowId)
where
W: Clone,
{
assert!(!self.ids.contains_key(&window), "window already registered");
assert!(!self.records.contains_key(&id), "window ID already registered");
let index = self.overview_order.partition_point(|existing| existing.0 < id.0);
self.overview_order.insert(index, id);
self.by_id.insert(id, window.clone());
self.ids.insert(window, id);
self.records.insert(id, WindowRecord::default());
}
@@ -66,6 +87,24 @@ impl<W: Eq + Hash> WindowRegistry<W> {
&self.ids
}
/// Overview render and hit order, with the newest ID in front. Normal
/// stacking changes do not affect this independent presentation order.
pub fn overview_windows(&self) -> impl DoubleEndedIterator<Item = &W> {
self.overview_order.iter().rev().filter_map(|id| self.by_id.get(id))
}
pub fn window(&self, id: WindowId) -> Option<&W> {
self.by_id.get(&id)
}
pub fn id_for_surface(&self, surface: &WlSurface) -> Option<WindowId> {
self.surface_ids.get(surface).copied()
}
pub fn ordered_ids(&self) -> impl DoubleEndedIterator<Item = WindowId> + '_ {
self.overview_order.iter().copied()
}
pub fn record(&self, id: WindowId) -> Option<&WindowRecord> {
self.records.get(&id)
}
@@ -74,14 +113,14 @@ impl<W: Eq + Hash> WindowRegistry<W> {
self.records.get_mut(&id)
}
pub fn records_mut(&mut self) -> impl Iterator<Item = (&WindowId, &mut WindowRecord)> {
self.records.iter_mut()
}
pub fn records(&self) -> impl Iterator<Item = (&WindowId, &WindowRecord)> {
self.records.iter()
}
pub fn records_mut(&mut self) -> impl Iterator<Item = (&WindowId, &mut WindowRecord)> {
self.records.iter_mut()
}
pub fn take_world_positions(&mut self) -> HashMap<WindowId, (WorkspaceId, AnimatedValue)> {
self.records
.iter_mut()
@@ -140,7 +179,17 @@ impl<W: Eq + Hash> WindowRegistry<W> {
pub fn remove(&mut self, window: &W) -> Option<WindowId> {
let id = self.ids.remove(window)?;
self.records.remove(&id);
self.by_id.remove(&id);
if let Some(record) = self.records.remove(&id)
&& let Some(surface) = record.surface
{
self.surface_ids.remove(&surface);
}
let index = self
.overview_order
.binary_search_by_key(&id.0, |id| id.0)
.expect("registered windows have an overview entry");
self.overview_order.remove(index);
Some(id)
}
@@ -153,10 +202,96 @@ impl<W: Eq + Hash> WindowRegistry<W> {
}
}
impl WindowRegistry<Window> {
pub fn register_window(&mut self, window: Window, id: WindowId) {
let surface = window.toplevel().map(|toplevel| toplevel.wl_surface().clone());
self.register(window, id);
if let Some(surface) = surface {
self.surface_ids.insert(surface.clone(), id);
self.records.get_mut(&id).unwrap().surface = Some(surface.clone());
self.refresh_metadata(&surface);
}
}
pub fn refresh_metadata(&mut self, surface: &WlSurface) -> bool {
let Some(id) = self.id_for_surface(surface) else {
return false;
};
let record = self.records.get_mut(&id).unwrap();
with_states(surface, |states| {
let attributes = states.data_map.get::<XdgToplevelSurfaceData>().unwrap().lock().unwrap();
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;
if changed {
record.app_id.clear();
record.app_id.push_str(app_id);
record.title.clear();
record.title.push_str(title);
record.metadata_revision = record.metadata_revision.wrapping_add(1);
}
changed
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn overview_order_tracks_registration_removal_and_remapping() {
let mut windows = WindowRegistry::default();
let first = windows.allocate_id();
let second = windows.allocate_id();
let third = windows.allocate_id();
// Registration need not happen in allocation order.
windows.register("second", second);
windows.register("first", first);
windows.register("third", third);
assert_eq!(
windows.overview_windows().copied().collect::<Vec<_>>(),
["third", "second", "first"]
);
windows.remove(&"second");
assert_eq!(
windows.overview_windows().copied().collect::<Vec<_>>(),
["third", "first"]
);
windows.remove(&"first");
let remapped = windows.allocate_id();
windows.register("first", remapped);
assert_eq!(
windows.overview_windows().copied().collect::<Vec<_>>(),
["first", "third"]
);
windows.remove(&"first");
windows.remove(&"third");
assert_eq!(windows.overview_windows().count(), 0);
}
#[test]
fn overview_iteration_borrows_handles_and_removal_releases_cached_handles() {
let handle = std::rc::Rc::new("surface");
let mut windows = WindowRegistry::default();
let id = windows.allocate_id();
windows.register(handle.clone(), id);
assert_eq!(std::rc::Rc::strong_count(&handle), 3);
for _ in 0..100 {
assert!(std::rc::Rc::ptr_eq(windows.overview_windows().next().unwrap(), &handle));
assert_eq!(std::rc::Rc::strong_count(&handle), 3);
}
windows.remove(&handle);
assert_eq!(std::rc::Rc::strong_count(&handle), 1);
}
#[test]
fn unmap_drops_metadata_and_late_updates_cannot_resurrect_it() {
let mut windows = WindowRegistry::default();
+5 -10
View File
@@ -115,7 +115,7 @@ impl Ferese {
return;
}
self.windows.register(window.clone(), id);
self.windows.register_window(window.clone(), id);
self.windows.update(id, |w| w.placement_anchor = Some(anchor));
if rule.width.is_none() && rule.height.is_none() && client_size(&window).is_none() && remembered_work.is_none()
@@ -156,7 +156,7 @@ impl Ferese {
return;
}
self.windows.register(window.clone(), id);
self.windows.register_window(window.clone(), id);
self.window_stack.insert(id);
if focus_new_window {
self.focused_window = Some(id);
@@ -196,7 +196,7 @@ impl Ferese {
return;
}
self.windows.register(window.clone(), id);
self.windows.register_window(window.clone(), id);
self.window_stack.insert(id);
if client_size(&window).is_none() {
self.windows.update(id, |w| w.natural_floating_pending = true);
@@ -649,7 +649,7 @@ impl Ferese {
}
pub(crate) fn close_managed_window(&mut self, id: WindowId) -> bool {
if !self.windows.ids().values().any(|window_id| *window_id == id) {
if !self.windows.window(id).is_some() {
return false;
}
@@ -666,12 +666,7 @@ impl Ferese {
}
pub(super) fn send_window_close(&self, id: WindowId) {
let Some(toplevel) = self
.windows
.ids()
.iter()
.find_map(|(window, window_id)| (*window_id == id).then(|| window.toplevel()).flatten())
else {
let Some(toplevel) = self.windows.window(id).and_then(|window| window.toplevel()) else {
return;
};