Refactor window ownership and immutable frame sampling

This commit is contained in:
Samuel Onoja committed 2026-10-01 18:03:01 +01:00
1 parent c0010ccbb6
commit 07bda5a21c
31 files changed
+1222 -623

No files matched your search

+72 -40
View File
@@ -29,6 +29,7 @@ impl Ferese {
{
self.focus_swipe = None;
}
let visible_workspaces = if self.viewport_animations.is_empty() {
HashSet::new()
} else {
@@ -38,10 +39,11 @@ impl Ferese {
.filter_map(|output| self.output_workspaces.active_workspace(*output))
.collect::<HashSet<_>>()
};
let windows = self
.space
.elements()
.filter_map(|window| self.window_ids.get(window).copied().map(|id| (window.clone(), id)))
.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();
@@ -49,16 +51,19 @@ impl Ferese {
if slide.held_progress.is_some() {
continue;
}
if slide.advance(delta) {
active_animation = true;
} else {
completed_slides.push(*output);
}
}
self.dismissing_popups.retain_mut(|(root, popup, motion)| {
if !smithay::utils::IsAlive::alive(popup.wl_surface()) {
return false;
}
let duration = if self.animations_enabled { 140.0 } else { 0.0 };
let active = motion.advance_visual(0.0, delta, duration);
crate::effects::fade_dismissed_surface(popup.wl_surface(), motion.current);
@@ -75,69 +80,72 @@ impl Ferese {
} else {
0.0
};
let mut dim_changed = false;
// Include hidden workspace windows: overview can present them too.
// Read selection once so these immutable fields can be borrowed alongside
// window_focus without allocating a temporary window-ID vector.
// each record without allocating a temporary window-ID vector.
let selected_window = if self.overview.is_active() {
self.overview.selected()
} else {
self.focused_window
};
for id in self.window_ids.values().copied() {
for (&id, record) in self.windows.records_mut() {
let selected = selected_window == Some(id);
let target = if selected { 1.0 } else { 0.0 };
let focus = self
.window_focus
.entry(id)
.or_insert_with(|| crate::dimming::DimAnimation::new(target));
let focus = record
.focus
.get_or_insert_with(|| crate::dimming::DimAnimation::new(target));
let previous = focus.current;
active_animation |= focus.advance_visual(target, delta, duration);
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 dim = self
.window_dimming
.entry(*id)
.or_insert_with(|| crate::dimming::DimAnimation::new(target));
let Some(record) = self.windows.record_mut(*id) else {
continue;
};
let dim = record
.dimming
.get_or_insert_with(|| crate::dimming::DimAnimation::new(target));
let previous = dim.current;
active_animation |= dim.advance_visual(target, delta, duration);
dim_changed |= previous != dim.current;
}
let mut ready_to_close = Vec::new();
for (id, animation) in &mut self.closing_windows {
for (id, record) in self.windows.records_mut() {
let Some(animation) = &mut record.closing else { continue };
if animation.advance(delta, self.animations_enabled) {
ready_to_close.push(*id);
} else if !animation.close_sent {
active_animation = true;
}
}
for id in ready_to_close {
self.send_window_close(id);
}
let now = self.start_time.elapsed();
self.resize_transactions.retain(|id, transaction| {
if transaction.expired(now) {
tracing::warn!(?id, "resize presentation deadline reached");
false
} else {
true
}
});
self.windows.expire_transactions(now);
let blocked_workspaces = self
.resize_transactions
.keys()
.windows
.resizing()
.filter_map(|id| self.workspaces.workspace_for_window(*id))
.collect::<HashSet<_>>();
let animations_enabled = self.animations_enabled;
let animation_speed = self.animation_speed;
self.resize_snapshots.retain(|id, snapshot| {
self.render.retain_snapshots(|id, snapshot| {
if !animations_enabled {
return false;
}
let waiting_for_client = self
.workspaces
.workspace_for_window(*id)
@@ -145,7 +153,7 @@ impl Ferese {
// A shrinking client's destination buffer arrives before the
// animated bounds reach it. Keep the old native pixels covering
// that strip rather than fading them into the neutral resize fill.
let uncovered = self.window_geometry.get(id).is_some_and(|geometry| {
let uncovered = self.windows.geometry(id).is_some_and(|geometry| {
geometry.client.committed_size.is_some_and(|size| {
crate::presentation::resize_needs_old_frame(
geometry.visual.current,
@@ -166,21 +174,24 @@ impl Ferese {
if !blocked {
snapshot.commit.increment();
}
if !active {
tracing::debug!(?id, bytes = snapshot.bytes(), "released resize handoff snapshot");
}
active
});
active_animation |= !self.resize_snapshots.is_empty();
active_animation |= self.render.snapshots().next().is_some();
// 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) {
continue;
}
if blocked_workspaces.contains(workspace) {
continue;
}
if let Some(swipe) = self.focus_swipe.as_ref().filter(|swipe| swipe.workspace == *workspace) {
viewport.current = swipe.position();
viewport.velocity = 0.0;
@@ -200,12 +211,19 @@ impl Ferese {
{
continue;
}
let Some(geometry) = self.window_geometry.get_mut(&id) else {
let Some(record) = self.windows.record_mut(id) else {
continue;
};
let natural_pending = record.natural_floating_pending;
let Some(geometry) = record.geometry.as_mut() else {
continue;
};
let zooming = geometry.is_zooming();
let coupled_target = self.viewport_coupled_widths.get(&id).map(|(_, width)| width.target);
let coupled_target = record.coupled_width.as_ref().map(|(_, width)| width.target);
if coupled_target.is_some() {
geometry.visual.target.width = geometry.visual.current.width;
geometry.visual.velocity.width = 0.0;
@@ -214,7 +232,8 @@ impl Ferese {
if let Some(target) = coupled_target {
geometry.visual.target.width = target;
}
if let Some((workspace, world_x)) = self.scrolling_world_x.get_mut(&id)
if let Some((workspace, world_x)) = record.world_x.as_mut()
&& let Some(viewport) = self.viewport_animations.get(workspace)
{
if zooming {
@@ -225,12 +244,14 @@ impl Ferese {
} else {
world_x.snap();
}
if !zooming {
geometry.visual.current.x = world_x.current - viewport.current;
geometry.visual.velocity.x = world_x.velocity - viewport.velocity;
}
}
if let Some((_, width)) = self.viewport_coupled_widths.get_mut(&id) {
if let Some((_, width)) = record.coupled_width.as_mut() {
let width_active = if self.animations_enabled {
width.advance(delta, self.viewport_spring_config)
} else {
@@ -244,6 +265,7 @@ impl Ferese {
settled_coupled_widths.push(id);
}
}
if let Some(size) = geometry.client.expire_wait(now) {
tracing::warn!(
?id,
@@ -253,8 +275,9 @@ impl Ferese {
"client did not commit the final configured size within 500 ms"
);
}
if let Some(size) = geometry.presentation_size_request(now)
&& !self.natural_floating_pending.contains(&id)
&& !natural_pending
&& let Some(toplevel) = window.toplevel()
{
toplevel.with_pending_state(|state| {
@@ -262,20 +285,25 @@ impl Ferese {
});
toplevel.send_pending_configure();
}
let visual = geometry.visual.current;
self.space
.map_element(window, (visual.x.round() as i32, visual.y.round() as i32), false);
}
for id in settled_coupled_widths {
self.viewport_coupled_widths.remove(&id);
self.windows.update(id, |record| record.coupled_width = None);
}
if !completed_slides.is_empty() {
for output in completed_slides {
self.workspace_slides.remove(&output);
}
let visible = self.visible_workspace_ids();
let visible_windows = self
.window_ids
.windows
.ids()
.values()
.copied()
.filter(|id| {
@@ -308,19 +336,15 @@ impl Ferese {
scaled_animation_duration(duration, self.animations_enabled, self.animation_speed)
}
pub(crate) fn closing_visual(&self, id: WindowId) -> (f64, f32) {
let Some(animation) = self.closing_windows.get(&id) else {
return (1.0, 1.0);
};
let eased = smoothstep(animation.progress);
(1.0 - eased * 0.02, (1.0 - eased) as f32)
pub(crate) fn workspace_slide_offset(&self, window: WindowId) -> (f64, f64) {
self.workspace_slide_offset_at(window, Duration::ZERO)
}
pub(crate) fn workspace_slide_offset(&self, window: WindowId) -> (f64, f64) {
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);
};
let Some(output_id) = self
.workspace_slides
.iter()
@@ -329,9 +353,11 @@ impl Ferese {
else {
return (0.0, 0.0);
};
let Some(slide) = self.workspace_slides.get(&output_id) else {
return (0.0, 0.0);
};
let Some(size) = self
.output_ids
.iter()
@@ -341,6 +367,12 @@ impl Ferese {
else {
return (0.0, 0.0);
};
let mut slide = slide.clone();
if slide.held_progress.is_none() && !delta.is_zero() {
slide.advance(delta);
}
slide.offset(workspace, f64::from(size.w), f64::from(size.h))
}
+40 -25
View File
@@ -18,7 +18,8 @@ impl Ferese {
None => return,
};
if let Some(handle) = self
.window_ids
.windows
.ids()
.iter()
.find_map(|(handle, id)| (*id == window).then_some(handle.clone()))
{
@@ -75,7 +76,7 @@ impl Ferese {
.workspace_for_window(window)
.and_then(|workspace| self.workspaces.workspace(workspace))
.is_some_and(|workspace| workspace.fullscreen == Some(window));
let enabled = fullscreen || !self.maximized_windows.contains(&window);
let enabled = fullscreen || !self.windows.record(window).is_some_and(|w| w.maximized);
self.set_window_maximized(window, enabled);
}
@@ -85,9 +86,9 @@ impl Ferese {
}
if enabled {
let _ = self.workspaces.set_fullscreen(window, false);
self.maximized_windows.insert(window);
self.windows.update(window, |w| w.maximized = true);
} else {
self.maximized_windows.remove(&window);
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();
@@ -96,10 +97,16 @@ impl Ferese {
{
if enabled {
if let Some(column) = layout.columns().iter().find(|column| column.windows.contains(&window)) {
self.maximized_column_widths.entry(window).or_insert(column.width);
self.windows.update(window, |w| {
w.maximized_column_width.get_or_insert(column.width);
});
}
let _ = layout.set_column_width(window, ColumnWidth::Proportion(1.0));
} else if let Some(width) = self.maximized_column_widths.remove(&window) {
} else if let Some(width) = self
.windows
.update(window, |w| w.maximized_column_width.take())
.flatten()
{
let _ = layout.set_column_width(window, width);
}
}
@@ -108,7 +115,11 @@ impl Ferese {
}
pub(crate) fn output_has_fullscreen(&self, output: &Output) -> bool {
!self.overview.is_presenting()
self.output_has_fullscreen_for_frame(output, self.overview.is_presenting())
}
pub(crate) fn output_has_fullscreen_for_frame(&self, output: &Output, overview: bool) -> bool {
!overview
&& self
.output_id(output)
.and_then(|id| self.output_workspaces.active_workspace(id))
@@ -127,7 +138,8 @@ impl Ferese {
}
pub fn is_floating_window(&self, window: &Window) -> bool {
self.window_ids
self.windows
.ids()
.get(window)
.and_then(|id| self.workspaces.placement(*id))
.is_some_and(|placement| matches!(placement, WindowPlacement::Floating { .. }))
@@ -139,10 +151,10 @@ impl Ferese {
location: Point<i32, Logical>,
size: Size<i32, Logical>,
) {
let Some(id) = self.window_ids.get(window).copied() else {
let Some(id) = self.windows.ids().get(window).copied() else {
return;
};
self.floating_resize_anchors.remove(&id);
self.windows.update(id, |w| w.resize_anchor.take()).flatten();
let rect = Rect::new(
location.x as f64,
location.y as f64,
@@ -157,12 +169,15 @@ impl Ferese {
// Direct manipulation follows the pointer, including while the client is
// still drawing its next buffer. Rendering and hit testing share this rect.
self.resize_transactions.remove(&id);
self.resize_snapshots.remove(&id);
let geometry = self
.window_geometry
.entry(id)
.or_insert_with(|| WindowGeometry::new(rect, client_size(window)));
self.windows.clear_transaction(&id);
self.render.clear_snapshot(&id);
let Some(record) = self.windows.record_mut(id) else {
return;
};
let geometry = record
.geometry
.get_or_insert_with(|| WindowGeometry::new(rect, client_size(window)));
if let Some(size) = geometry.follow_pointer(rect, self.start_time.elapsed())
&& let Some(toplevel) = window.toplevel()
{
@@ -171,8 +186,8 @@ impl Ferese {
});
toplevel.send_pending_configure();
}
self.scrolling_world_x.remove(&id);
self.viewport_coupled_widths.remove(&id);
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.sync_window_stacking();
// Move/resize/cancel callbacks run with Smithay's pointer mutex held.
@@ -212,7 +227,7 @@ impl Ferese {
self.space
.elements()
.filter_map(|window| {
let id = *self.window_ids.get(window)?;
let id = *self.windows.ids().get(window)?;
if Some(id) == exclude {
return None;
}
@@ -255,7 +270,7 @@ impl Ferese {
let parent_rect = parent
.as_ref()
.and_then(|surface| {
self.window_ids.iter().find_map(|(window, id)| {
self.windows.ids().iter().find_map(|(window, id)| {
window
.toplevel()
.is_some_and(|top| top.wl_surface() == surface)
@@ -266,7 +281,7 @@ impl Ferese {
.and_then(|rect| intersection(rect, work));
let dialog = crate::window_rules::is_native_dialog(app.as_deref());
let remembered = id
.and_then(|id| self.floating_window_memory.get(&id))
.and_then(|id| self.windows.record(id).and_then(|w| w.floating_memory.as_ref()))
.or_else(|| {
if parent.is_none() {
app.as_deref().and_then(|app| self.floating_memory.get(app))
@@ -317,7 +332,7 @@ impl Ferese {
.map(|(_, r)| r)
.collect::<Vec<_>>();
let anchor = id
.and_then(|id| self.floating_placement_anchors.get(&id).copied())
.and_then(|id| self.windows.record(id).and_then(|w| w.placement_anchor))
.or_else(|| {
self.focused_window
.filter(|focused| Some(*focused) != id)
@@ -337,7 +352,7 @@ impl Ferese {
}
pub(crate) fn remember_floating(&mut self, window: &Window) {
let Some(id) = self.window_ids.get(window).copied() else {
let Some(id) = self.windows.ids().get(window).copied() else {
return;
};
let Some(WindowPlacement::Floating { rect }) = self.workspaces.placement(id) else {
@@ -360,7 +375,7 @@ impl Ferese {
return;
};
let entry = crate::floating::Remembered::new(output, rect, work);
self.floating_window_memory.insert(id, entry.clone());
self.windows.update(id, |w| w.floating_memory = Some(entry.clone()));
if let Some(app) = app {
self.floating_memory.save(app, entry);
}
@@ -377,7 +392,7 @@ impl Ferese {
window: &Window,
raw: Rect,
) -> (Vec<crate::floating::SnapLine>, Vec<crate::floating::SnapLine>) {
let id = self.window_ids.get(window).copied();
let id = self.windows.ids().get(window).copied();
let work = self
.floating_outputs()
.into_iter()
+5 -4
View File
@@ -2,22 +2,23 @@ use super::*;
impl Ferese {
pub(super) fn focus_candidates(&self) -> Vec<WindowId> {
self.window_ids
self.windows
.ids()
.iter()
.filter_map(|(window, id)| {
self.workspaces.workspace_for_window(*id)?;
(window.toplevel().is_some()
&& self.window_content_ready(window)
&& self.window_geometry.contains_key(id)
&& self.windows.geometry(id).is_some()
&& self.output_workspaces.focused_output().is_some()
&& !self.closing_windows.contains_key(id))
&& !self.windows.record(*id).is_some_and(|record| record.closing.is_some()))
.then_some(*id)
})
.collect()
}
pub(crate) fn focus_preview_output(&self, id: WindowId) -> Option<OutputId> {
if self.closing_windows.contains_key(&id) {
if self.windows.record(id).is_some_and(|record| record.closing.is_some()) {
return None;
}
self.workspaces
+11 -10
View File
@@ -41,7 +41,7 @@ impl Ferese {
let workspace = self.workspace_under_pointer(position)?;
self.space.elements().rev().find_map(|window| {
let id = self.window_ids.get(window)?;
let id = self.windows.ids().get(window)?;
if self.workspaces.workspace_for_window(*id) != Some(workspace) {
return None;
}
@@ -71,7 +71,7 @@ impl Ferese {
}
pub(super) fn window_fills_visual_bounds(&self, id: WindowId, workspace: WorkspaceId) -> bool {
self.maximized_windows.contains(&id)
self.windows.record(id).is_some_and(|w| w.maximized)
|| self
.workspaces
.workspace(workspace)
@@ -137,7 +137,7 @@ impl Ferese {
return None;
}
let id = self.window_ids.get(window)?;
let id = self.windows.ids().get(window)?;
if self.workspaces.workspace_for_window(*id) != Some(workspace) {
return None;
}
@@ -166,9 +166,9 @@ impl Ferese {
};
if overview_active {
let mut candidates = self.window_ids.keys().cloned().collect::<Vec<_>>();
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.window_ids.get(window).map_or(0, |id| id.0)));
candidates.sort_by_key(|window| std::cmp::Reverse(self.windows.ids().get(window).map_or(0, |id| id.0)));
candidates.iter().find_map(hit)
} else {
self.space.elements().rev().find_map(hit)
@@ -199,8 +199,8 @@ impl Ferese {
if !self.overview.is_presenting() {
return Some((1.0, 1.0));
}
let id = self.window_ids.get(window)?;
let geometry = self.window_geometry.get(id)?;
let id = self.windows.ids().get(window)?;
let geometry = self.windows.geometry(id)?;
let source = geometry.client.committed_size?;
let presented = self.presented_window_rect(*id)?;
@@ -215,7 +215,7 @@ impl Ferese {
}
pub(crate) fn visual_rect_for_window(&self, window: &Window) -> Option<Rectangle<i32, Logical>> {
let id = self.window_ids.get(window)?;
let id = self.windows.ids().get(window)?;
let rect = self.presented_window_rect(*id)?;
let size = ClientSize::from_rect(rect);
Some(Rectangle::new(
@@ -227,9 +227,10 @@ impl Ferese {
pub(crate) fn window_content_ready(&self, window: &Window) -> bool {
window_has_buffer(window)
&& self
.window_ids
.windows
.ids()
.get(window)
.and_then(|id| self.window_geometry.get(id))
.and_then(|id| self.windows.geometry(id))
.is_some_and(|geometry| geometry.client.committed_size.is_some())
}
}
+64 -33
View File
@@ -26,16 +26,25 @@ impl Ferese {
pub(super) fn unmap_invisible_windows(&mut self, visible: &HashSet<WindowId>) -> bool {
let mut changed = false;
for (window, id) in &self.window_ids {
if visible.contains(id) {
let windows = self
.windows
.ids()
.iter()
.map(|(window, id)| (window.clone(), *id))
.collect::<Vec<_>>();
for (window, id) in windows {
if visible.contains(&id) {
continue;
}
let was_mapped = self.space.element_location(window).is_some();
let was_mapped = self.space.element_location(&window).is_some();
changed |= was_mapped;
if was_mapped && let Some(geometry) = self.window_geometry.get_mut(id) {
if was_mapped && let Some(geometry) = self.windows.geometry_mut(&id) {
geometry.settle_presentation();
}
self.space.unmap_elem(window);
self.space.unmap_elem(&window);
}
changed
}
@@ -51,8 +60,8 @@ impl Ferese {
// large first animation step after a keypress on an idle desktop.
self.advance_animations(Instant::now());
self.arrange_layers();
let mut previous_scrolling_world_x = std::mem::take(&mut self.scrolling_world_x);
let pending_column_width_cycles = std::mem::take(&mut self.pending_column_width_cycles);
let mut previous_scrolling_world_x = self.windows.take_world_positions();
let pending_column_width_cycles = self.windows.take_column_width_requests();
let outputs = self.space.outputs().cloned().collect::<Vec<_>>();
let constraints = self.window_constraints();
let mut visible = HashSet::new();
@@ -63,6 +72,7 @@ impl Ferese {
let Some(output_id) = self.output_ids.get(&output).copied() else {
continue;
};
let Some(active_workspace) = self.output_workspaces.active_workspace(output_id) else {
continue;
};
@@ -80,13 +90,16 @@ impl Ferese {
let Some(bounds) = self.output_bounds_for(&output) else {
continue;
};
let fullscreen_bounds = self.full_output_bounds_for(&output).unwrap_or(bounds);
for layer in layer_map_for_output(&output).layers() {
crate::handlers::set_surface_tree_output(layer.wl_surface(), &output);
}
let Some(workspace) = self.workspaces.workspace(workspace_id) else {
continue;
};
let workspace_fullscreen = workspace.fullscreen;
let is_scrolling_layout = matches!(workspace.layout, WorkspaceLayout::Scrolling(_));
let workspace_focus = workspace.last_focused;
@@ -97,6 +110,7 @@ impl Ferese {
let Some(workspace) = self.workspaces.workspace_mut(workspace_id) else {
continue;
};
let layout =
match workspace
.layout
@@ -134,15 +148,17 @@ impl Ferese {
);
}
for (window, id) in &self.window_ids {
for (window, id) in self.windows.ids() {
if self.workspaces.workspace_for_window(*id) != Some(workspace_id) {
continue;
}
if let Some(toplevel) = window.toplevel() {
crate::handlers::set_surface_tree_output(toplevel.wl_surface(), &output);
}
let is_maximized = self.maximized_windows.contains(id) && workspace_fullscreen != Some(*id);
let is_maximized =
self.windows.record(*id).is_some_and(|w| w.maximized) && workspace_fullscreen != Some(*id);
let rect = if workspace_fullscreen == Some(*id) {
fullscreen_bounds
} else if is_maximized && !is_scrolling_layout {
@@ -166,6 +182,7 @@ impl Ferese {
None => continue,
}
};
let is_fullscreen = workspace_fullscreen == Some(*id);
let is_floating = matches!(self.workspaces.placement(*id), Some(WindowPlacement::Floating { .. }));
@@ -195,19 +212,25 @@ impl Ferese {
let now = self.start_time.elapsed();
let mut scrolling_world_x = HashMap::new();
placements.sort_by_key(|(_, id, ..)| self.window_stack.rank(*id));
for (window, id, rect, is_fullscreen, is_maximized, is_floating, scrolling, couple_width) in placements {
let was_mapped = self.space.element_location(&window).is_some();
let Some(record) = self.windows.record_mut(id) else {
continue;
};
// A viewport-coupled width must never override fullscreen/floating geometry.
if scrolling.is_none() {
self.viewport_coupled_widths.remove(&id);
record.coupled_width = None;
}
let (geometry, had_geometry) = match self.window_geometry.entry(id) {
Entry::Occupied(entry) => (entry.into_mut(), true),
Entry::Vacant(entry) => (entry.insert(WindowGeometry::new(rect, client_size(&window))), false),
};
let natural_pending = record.natural_floating_pending && is_floating && !is_fullscreen && !is_maximized;
let had_geometry = record.geometry.is_some();
let geometry = record
.geometry
.get_or_insert_with(|| WindowGeometry::new(rect, client_size(&window)));
let mode = if is_fullscreen {
PresentationMode::Fullscreen
} else if is_maximized {
@@ -220,13 +243,16 @@ impl Ferese {
if !was_mapped && (had_geometry || is_fullscreen) {
geometry.settle_presentation();
}
if !self.animations_enabled {
geometry.advance(Duration::ZERO, self.spring_config, false);
requested_size = geometry.presentation_size_request(now).or(requested_size);
}
if geometry.is_zooming() {
self.viewport_coupled_widths.remove(&id);
record.coupled_width = None;
}
if let Some((workspace, world_x, viewport_x)) = scrolling {
let restored_world_x =
restored_scrolling_world_x(had_geometry, geometry.visual.current.x, viewport_x, world_x);
@@ -239,23 +265,23 @@ impl Ferese {
if !self.animations_enabled {
animated_world_x.snap();
}
if !geometry.is_zooming() {
geometry.visual.current.x = animated_world_x.current - viewport_x;
geometry.visual.velocity.x = animated_world_x.velocity;
}
scrolling_world_x.insert(id, (workspace, animated_world_x));
record.world_x = Some((workspace, animated_world_x));
let coupled = !geometry.is_zooming()
&& (couple_width
|| self
.viewport_coupled_widths
.get(&id)
|| record
.coupled_width
.as_ref()
.is_some_and(|(previous_workspace, _)| *previous_workspace == workspace));
if coupled {
let width = self
.viewport_coupled_widths
.entry(id)
.or_insert_with(|| (workspace, AnimatedValue::new(geometry.visual.current.width)));
let width = record
.coupled_width
.get_or_insert_with(|| (workspace, AnimatedValue::new(geometry.visual.current.width)));
if width.0 != workspace {
*width = (workspace, AnimatedValue::new(geometry.visual.current.width));
}
@@ -266,15 +292,15 @@ impl Ferese {
geometry.visual.current.width = width.1.current;
geometry.visual.velocity.width = width.1.velocity;
} else if pending_column_width_cycles.contains(&id) {
self.viewport_coupled_widths.remove(&id);
record.coupled_width = None;
}
}
let visual = geometry.visual.current;
let natural_pending =
self.natural_floating_pending.contains(&id) && is_floating && !is_fullscreen && !is_maximized;
if natural_pending {
requested_size = None;
}
let location = (visual.x.round() as i32, visual.y.round() as i32);
self.space.map_element(window.clone(), location, false);
@@ -297,6 +323,7 @@ impl Ferese {
} else {
state.states.unset(xdg_toplevel::State::Maximized)
};
let tiled = !is_floating && !is_fullscreen;
let tiled_changed = if tiled {
state.states.set(xdg_toplevel::State::TiledLeft)
@@ -319,7 +346,7 @@ impl Ferese {
&& had_geometry
&& self.animations_enabled
{
self.resize_transactions.insert(
self.windows.set_transaction(
id,
crate::resize_transaction::ResizeTransaction::new(serial, now)
.with_source_geometry(window.geometry()),
@@ -327,10 +354,11 @@ impl Ferese {
}
}
}
self.scrolling_world_x = scrolling_world_x;
if layout_changed {
self.backdrop_generation = self.backdrop_generation.wrapping_add(1);
}
self.sync_window_stacking();
self.retarget_overview();
self.send_shell_snapshots();
@@ -339,9 +367,10 @@ impl Ferese {
}
pub(crate) fn raise_window(&mut self, window: &Window, activate: bool) {
if let Some(id) = self.window_ids.get(window) {
if let Some(id) = self.windows.ids().get(window) {
self.window_stack.raise(*id);
}
self.space.raise_element(window, activate);
self.sync_window_stacking();
}
@@ -349,8 +378,8 @@ impl Ferese {
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.window_ids.get(window).copied();
let geometry = id.and_then(|id| self.window_geometry.get(&id));
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(
@@ -372,6 +401,7 @@ impl Ferese {
if stacking_order_settled(self.space.elements(), &windows, |window| window.z_index()) {
return;
}
for window in windows {
self.space.raise_element(&window, false);
}
@@ -387,7 +417,8 @@ impl Ferese {
}
pub(super) fn window_constraints(&self) -> HashMap<WindowId, SizeConstraints> {
self.window_ids
self.windows
.ids()
.iter()
.filter_map(|(window, id)| {
let toplevel = window.toplevel()?;
+6 -4
View File
@@ -140,7 +140,8 @@ impl Ferese {
tracing::warn!(%error, ?current, ?next, "failed to slide swipe focus");
}
let Some(window) = self
.window_ids
.windows
.ids()
.iter()
.find_map(|(window, id)| (*id == next).then(|| window.clone()))
else {
@@ -277,7 +278,7 @@ impl Ferese {
match self.workspaces.cycle_column_width(window, &self.column_width_presets) {
Ok(true) => {
self.pending_column_width_cycles.insert(window);
self.windows.update(window, |w| w.column_width_pending = true);
self.relayout();
}
Ok(false) => {}
@@ -306,7 +307,8 @@ impl Ferese {
pub(crate) fn activate_managed_window(&mut self, id: WindowId) -> bool {
let Some(window) = self
.window_ids
.windows
.ids()
.iter()
.find_map(|(window, window_id)| (*window_id == id).then(|| window.clone()))
else {
@@ -560,7 +562,7 @@ impl Ferese {
return;
}
let surface = self.focused_window.and_then(|focused| {
self.window_ids.iter().find_map(|(window, id)| {
self.windows.ids().iter().find_map(|(window, id)| {
(*id == focused)
.then(|| window.toplevel())
.flatten()
+3 -2
View File
@@ -48,7 +48,7 @@ impl Ferese {
};
self.workspace_slides.remove(&output_id);
self.overview_scrims.remove(&output_id);
self.render.remove_output(output_id);
self.pending_screencopies.retain(|capture| {
if capture.output == *output {
capture.fail();
@@ -82,7 +82,8 @@ impl Ferese {
pub(crate) fn reposition_output_floats(&mut self, output: OutputId, old: Rect, new: Rect) {
let floats = self
.window_ids
.windows
.ids()
.values()
.filter_map(|id| {
let workspace = self.workspaces.workspace_for_window(*id)?;
+116 -126
View File
@@ -1,49 +1,19 @@
use super::*;
/// Rendering samples an isolated forecast. Restore authoritative state before
/// returning to input, client commits, or another monitor's render deadline.
pub(crate) struct RenderForecast<'a> {
state: &'a mut Ferese,
geometry: Vec<(WindowId, WindowGeometry)>,
slides: HashMap<OutputId, WorkspaceSlide>,
closing: HashMap<WindowId, ClosingAnimation>,
focus: HashMap<WindowId, crate::dimming::DimAnimation>,
dimming: HashMap<WindowId, crate::dimming::DimAnimation>,
overview: Option<crate::overview::OverviewAnimationSnapshot>,
/// Output-local presentation values. Sampling never changes authoritative
/// geometry, configure barriers, animation clocks, or another output's view.
pub(crate) struct FrameScene {
pub windows: HashMap<WindowId, WindowFrame>,
pub overview: crate::overview::OverviewMotion,
}
impl std::ops::Deref for RenderForecast<'_> {
type Target = Ferese;
fn deref(&self) -> &Ferese {
self.state
}
}
impl std::ops::DerefMut for RenderForecast<'_> {
fn deref_mut(&mut self) -> &mut Ferese {
self.state
}
}
impl Drop for RenderForecast<'_> {
fn drop(&mut self) {
if self.overview.is_none() {
return;
}
for (id, geometry) in self.geometry.drain(..) {
self.state.window_geometry.insert(id, geometry);
}
self.state.workspace_slides = std::mem::take(&mut self.slides);
self.state.closing_windows = std::mem::take(&mut self.closing);
self.state.window_focus = std::mem::take(&mut self.focus);
self.state.window_dimming = std::mem::take(&mut self.dimming);
if let Some(overview) = self.overview.take() {
self.state.overview.restore_prediction(overview);
}
}
pub(crate) struct WindowFrame {
pub geometry: WindowGeometry,
pub rect: Rect,
pub close_scale: f64,
pub close_alpha: f32,
pub focus: f64,
pub dim: f64,
}
impl Ferese {
@@ -60,21 +30,24 @@ impl Ferese {
return true;
}
self.window_geometry.iter().any(|(id, geometry)| {
self.windows.records().any(|(id, record)| {
let Some(mut geometry) = record.geometry else {
return false;
};
if !self.window_belongs_to_output(*id, output) {
return false;
}
if self.resize_transactions.contains_key(id)
|| self.resize_snapshots.contains_key(id)
|| self.closing_windows.get(id).is_some_and(|close| !close.close_sent)
|| self.window_focus.get(id).is_some_and(|focus| focus.is_animating())
|| self.window_dimming.get(id).is_some_and(|dim| dim.is_animating())
if record.resize.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())
{
return true;
}
let mut geometry = *geometry;
if geometry.advance(Duration::ZERO, self.spring_config, self.animations_enabled) {
return true;
}
@@ -90,104 +63,104 @@ impl Ferese {
.as_ref()
.is_some_and(|swipe| swipe.workspace == workspace);
!held && viewport.advance(Duration::ZERO, self.viewport_spring_config)
}) || self.viewport_coupled_widths.get(id).is_some_and(|(_, width)| {
}) || record.coupled_width.as_ref().is_some_and(|(_, width)| {
let mut width = *width;
width.advance(Duration::ZERO, self.viewport_spring_config)
})
})
}
pub(crate) fn forecast_render(&mut self, output: &Output, horizon: Duration) -> RenderForecast<'_> {
if horizon.is_zero() || !self.animations_enabled || !self.output_has_animations(output) {
return RenderForecast {
state: self,
geometry: Vec::new(),
slides: HashMap::new(),
closing: HashMap::new(),
focus: HashMap::new(),
dimming: HashMap::new(),
overview: None,
};
}
let delta = if self.animations_enabled {
pub(crate) fn sample_frame(&self, output: &Output, horizon: Duration) -> FrameScene {
let delta = if self.animations_enabled && self.output_has_animations(output) {
horizon.min(Duration::from_millis(100)).mul_f64(self.animation_speed)
} else {
Duration::ZERO
};
let geometry = self
.window_geometry
.iter()
.filter(|(id, _)| self.window_belongs_to_output(**id, output))
.map(|(id, geometry)| (*id, *geometry))
.collect::<Vec<_>>();
let slides = self.workspace_slides.clone();
let closing = self.closing_windows.clone();
let focus = self.window_focus.clone();
let dimming = self.window_dimming.clone();
let overview = self.overview.predict(delta, self.spring_config);
let overview = self.overview.sample(delta, self.spring_config);
let blocked = self
.resize_transactions
.keys()
.windows
.resizing()
.filter_map(|id| self.workspaces.workspace_for_window(*id))
.collect::<HashSet<_>>();
for (id, original) in &geometry {
let workspace = self.workspaces.workspace_for_window(*id);
if workspace.is_some_and(|workspace| blocked.contains(&workspace)) {
let mut windows = HashMap::new();
for (&id, record) in self.windows.records() {
let Some(original) = record.geometry else { continue };
if !self.window_belongs_to_output(id, output) {
continue;
}
let world = self.scrolling_world_x.get(id).and_then(|(workspace, world)| {
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
.focus_swipe
.as_ref()
.is_some_and(|swipe| swipe.workspace == *workspace);
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)) {
original
} else {
predict_geometry(
original,
delta,
self.spring_config,
self.viewport_spring_config,
world,
width,
)
};
let width = self.viewport_coupled_widths.get(id).map(|(_, width)| *width);
let predicted = predict_geometry(
*original,
delta,
self.spring_config,
self.viewport_spring_config,
world,
width,
let mut rect = overview.presented_rect(id, geometry.visual.current);
if !overview.is_presenting() {
let (x, y) = self.workspace_slide_offset_at(id, delta);
rect.x += x;
rect.y += y;
}
let mut close = record.closing.as_ref().copied().unwrap_or_default();
if record.closing.is_some() && !delta.is_zero() {
close.advance(delta, true);
}
let eased = smoothstep(close.progress);
let focused = if self.overview.is_active() {
self.overview_selected(id)
} else {
self.focused_window == Some(id)
};
let sample_dim = |motion: Option<&DimAnimation>, fallback| {
let Some(mut motion) = motion.cloned() else {
return fallback;
};
if !delta.is_zero() {
motion.predict(delta, self.inactive_dim.duration_ms);
}
motion.current
};
windows.insert(
id,
WindowFrame {
geometry,
rect,
close_scale: 1.0 - eased * 0.02,
close_alpha: (1.0 - eased) as f32,
focus: sample_dim(record.focus.as_ref(), if focused { 1.0 } else { 0.0 }),
dim: sample_dim(record.dimming.as_ref(), 0.0),
},
);
self.window_geometry.insert(*id, predicted);
}
for slide in self
.workspace_slides
.values_mut()
.filter(|slide| slide.held_progress.is_none())
{
slide.advance(delta);
}
for close in self.closing_windows.values_mut() {
close.advance(delta, self.animations_enabled);
}
for focus in self.window_focus.values_mut() {
focus.predict(delta, self.inactive_dim.duration_ms);
}
for dim in self.window_dimming.values_mut() {
dim.predict(delta, self.inactive_dim.duration_ms);
}
RenderForecast {
state: self,
geometry,
slides,
closing,
focus,
dimming,
overview: Some(overview),
}
FrameScene { windows, overview }
}
}
@@ -235,7 +208,7 @@ mod tests {
#[test]
#[ignore = "requires a private XDG_RUNTIME_DIR and permission to bind test sockets"]
fn render_forecast_restores_compositor_state_between_output_deadlines() {
fn frame_sampling_preserves_compositor_state_between_output_deadlines() {
let runtime = std::path::PathBuf::from(std::env::var_os("XDG_RUNTIME_DIR").unwrap());
assert!(
runtime.starts_with(std::env::temp_dir()),
@@ -269,7 +242,14 @@ mod tests {
state.workspaces.insert_window(id, Axis::Horizontal, 0.5).unwrap();
let mut geometry = WindowGeometry::new(Rect::new(0., 0., 400., 300.), None);
geometry.visual.set_target(Rect::new(600., 0., 400., 300.));
state.window_geometry.insert(id, geometry);
// This test isolates frame sampling from the protocol client fixture.
state.windows.records.insert(
id,
super::super::window_registry::WindowRecord {
geometry: Some(geometry),
..Default::default()
},
);
let tick = state.last_animation_tick;
for horizon in [
Duration::from_millis(16),
@@ -277,17 +257,27 @@ mod tests {
Duration::from_millis(8),
] {
{
let forecast = state.forecast_render(&output, horizon);
assert!(forecast.window_geometry[&id].visual.current.x > geometry.visual.current.x);
let forecast = state.sample_frame(&output, horizon);
assert!(forecast.windows[&id].geometry.visual.current.x > geometry.visual.current.x);
}
assert_eq!(state.window_geometry[&id], geometry);
assert_eq!(*state.windows.geometry(&id).unwrap(), geometry);
assert_eq!(state.last_animation_tick, tick);
}
// A prediction must neither advance nor expire a client resize barrier.
state.windows.set_transaction(
id,
crate::resize_transaction::ResizeTransaction::new(9.into(), Duration::ZERO),
);
let blocked = state.sample_frame(&output, Duration::from_millis(16));
assert_eq!(blocked.windows[&id].geometry, geometry);
assert!(state.windows.transaction(&id).is_some());
state.windows.clear_transaction(&id);
state.animations_enabled = false;
let forecast = state.forecast_render(&output, Duration::from_millis(16));
assert_eq!(forecast.window_geometry[&id], geometry);
let forecast = state.sample_frame(&output, Duration::from_millis(16));
assert_eq!(forecast.windows[&id].geometry, geometry);
}
#[test]
+253
View File
@@ -0,0 +1,253 @@
use std::collections::{HashMap, HashSet};
use std::hash::Hash;
use ferese_animation::{AnimatedValue, WindowGeometry};
use ferese_core::WorkspaceId;
use ferese_layout::{ColumnWidth, Rect, WindowId};
use crate::resize_transaction::ResizeTransaction;
/// Metadata with exactly the lifetime of a managed window. Layouts and focus
/// history reference its ID; they do not own or duplicate this metadata.
#[derive(Default)]
pub(crate) struct WindowRecord {
pub geometry: Option<WindowGeometry>,
pub resize: Option<ResizeTransaction>,
pub focus: Option<crate::dimming::DimAnimation>,
pub dimming: Option<crate::dimming::DimAnimation>,
pub(super) closing: Option<super::ClosingAnimation>,
pub world_x: Option<(WorkspaceId, AnimatedValue)>,
pub coupled_width: Option<(WorkspaceId, AnimatedValue)>,
pub maximized: bool,
pub maximized_column_width: Option<ColumnWidth>,
pub natural_floating_pending: bool,
pub floating_memory: Option<crate::floating::Remembered>,
pub placement_anchor: Option<Rect>,
pub resize_anchor: Option<(bool, bool, Rect)>,
pub column_width_pending: bool,
pub rules_applied: bool,
}
/// The handle index is read-only outside this owner. Registering and removing a
/// handle always updates its record as well. IDs are never reused on remapping.
pub(crate) struct WindowRegistry<W> {
ids: HashMap<W, WindowId>,
pub(super) records: HashMap<WindowId, WindowRecord>,
next_id: u64,
}
impl<W> Default for WindowRegistry<W> {
fn default() -> Self {
Self {
ids: HashMap::new(),
records: HashMap::new(),
next_id: 1,
}
}
}
impl<W: Eq + Hash> WindowRegistry<W> {
pub fn allocate_id(&mut self) -> WindowId {
let id = WindowId(self.next_id);
self.next_id = self.next_id.checked_add(1).expect("window IDs exhausted");
id
}
pub fn register(&mut self, window: W, id: WindowId) {
assert!(!self.ids.contains_key(&window), "window already registered");
assert!(!self.records.contains_key(&id), "window ID already registered");
self.ids.insert(window, id);
self.records.insert(id, WindowRecord::default());
}
pub fn ids(&self) -> &HashMap<W, WindowId> {
&self.ids
}
pub fn record(&self, id: WindowId) -> Option<&WindowRecord> {
self.records.get(&id)
}
pub fn record_mut(&mut self, id: WindowId) -> Option<&mut WindowRecord> {
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 take_world_positions(&mut self) -> HashMap<WindowId, (WorkspaceId, AnimatedValue)> {
self.records
.iter_mut()
.filter_map(|(id, record)| Some((*id, record.world_x.take()?)))
.collect()
}
pub fn geometry(&self, id: &WindowId) -> Option<&WindowGeometry> {
self.records.get(id)?.geometry.as_ref()
}
pub fn geometry_mut(&mut self, id: &WindowId) -> Option<&mut WindowGeometry> {
self.records.get_mut(id)?.geometry.as_mut()
}
pub fn geometries(&self) -> impl Iterator<Item = (&WindowId, &WindowGeometry)> {
self.records
.iter()
.filter_map(|(id, record)| Some((id, record.geometry.as_ref()?)))
}
pub fn set_geometry(&mut self, id: WindowId, geometry: WindowGeometry) {
self.update(id, |record| record.geometry = Some(geometry));
}
pub fn transaction(&self, id: &WindowId) -> Option<&ResizeTransaction> {
self.records.get(id)?.resize.as_ref()
}
pub fn set_transaction(&mut self, id: WindowId, transaction: ResizeTransaction) {
self.update(id, |record| record.resize = Some(transaction));
}
pub fn clear_transaction(&mut self, id: &WindowId) {
self.update(*id, |record| record.resize = None);
}
pub fn resizing(&self) -> impl Iterator<Item = &WindowId> {
self.records
.iter()
.filter_map(|(id, record)| record.resize.is_some().then_some(id))
}
pub fn expire_transactions(&mut self, now: std::time::Duration) {
for (id, record) in &mut self.records {
if record.resize.is_some_and(|transaction| transaction.expired(now)) {
tracing::warn!(?id, "resize presentation deadline reached");
record.resize = None;
}
}
}
pub fn update<R>(&mut self, id: WindowId, update: impl FnOnce(&mut WindowRecord) -> R) -> Option<R> {
self.records.get_mut(&id).map(update)
}
pub fn remove(&mut self, window: &W) -> Option<WindowId> {
let id = self.ids.remove(window)?;
self.records.remove(&id);
Some(id)
}
pub fn take_column_width_requests(&mut self) -> HashSet<WindowId> {
self.records
.iter_mut()
.filter_map(|(id, record)| std::mem::take(&mut record.column_width_pending).then_some(*id))
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn unmap_drops_metadata_and_late_updates_cannot_resurrect_it() {
let mut windows = WindowRegistry::default();
let id = windows.allocate_id();
windows.register("surface", id);
windows.update(id, |record| {
record.maximized = true;
record.resize_anchor = Some((true, false, Rect::new(0., 0., 800., 600.)));
record.column_width_pending = true;
record.rules_applied = true;
record.focus = Some(crate::dimming::DimAnimation::new(1.0));
record.closing = Some(super::super::ClosingAnimation::default());
record.world_x = Some((WorkspaceId(1), AnimatedValue::new(50.0)));
record.coupled_width = Some((WorkspaceId(1), AnimatedValue::new(800.0)));
});
let geometry = WindowGeometry::new(Rect::new(0., 0., 800., 600.), None);
let resize = ResizeTransaction::new(12.into(), std::time::Duration::ZERO);
windows.set_geometry(id, geometry);
windows.set_transaction(id, resize);
assert_eq!(windows.remove(&"surface"), Some(id));
assert_eq!(windows.remove(&"surface"), None);
assert!(windows.record(id).is_none());
assert!(windows.update(id, |record| record.maximized = true).is_none());
windows.set_geometry(id, geometry);
windows.set_transaction(id, resize);
assert!(windows.geometry(&id).is_none());
assert!(windows.transaction(&id).is_none());
assert_eq!(windows.resizing().count(), 0);
assert!(windows.ids().is_empty());
assert!(windows.take_column_width_requests().is_empty());
}
#[test]
fn remap_has_new_identity_and_does_not_inherit_old_requests() {
let mut windows = WindowRegistry::default();
let old = windows.allocate_id();
windows.register("surface", old);
windows.update(old, |record| record.rules_applied = true);
windows.remove(&"surface");
let new = windows.allocate_id();
windows.register("surface", new);
assert_ne!(old, new);
assert!(
windows
.update(old, |record| record.column_width_pending = true)
.is_none()
);
assert!(!windows.record(new).unwrap().rules_applied);
assert!(windows.take_column_width_requests().is_empty());
}
#[test]
fn pending_width_changes_are_consumed_once_and_survive_other_window_removal() {
let mut windows = WindowRegistry::default();
let first = windows.allocate_id();
let second = windows.allocate_id();
windows.register("first", first);
windows.register("second", second);
windows.update(first, |record| record.column_width_pending = true);
windows.update(second, |record| record.column_width_pending = true);
windows.remove(&"first");
assert_eq!(windows.take_column_width_requests(), HashSet::from([second]));
assert!(windows.take_column_width_requests().is_empty());
assert!(windows.record(second).is_some());
}
#[test]
fn expired_resize_releases_only_its_barrier_and_preserves_geometry() {
use std::time::Duration;
let mut windows = WindowRegistry::default();
let first = windows.allocate_id();
let second = windows.allocate_id();
windows.register("first", first);
windows.register("second", second);
let geometry = WindowGeometry::new(Rect::new(0., 0., 800., 600.), None);
windows.set_geometry(first, geometry);
windows.set_transaction(first, ResizeTransaction::new(1.into(), Duration::ZERO));
windows.set_transaction(second, ResizeTransaction::new(2.into(), Duration::from_millis(100)));
windows.expire_transactions(Duration::from_millis(300));
assert_eq!(windows.geometry(&first), Some(&geometry));
assert!(windows.transaction(&first).is_none());
assert!(windows.transaction(&second).unwrap().accepts(Some(2.into())));
assert_eq!(windows.resizing().copied().collect::<Vec<_>>(), vec![second]);
}
}
+97 -69
View File
@@ -24,6 +24,7 @@ impl Ferese {
{
return;
}
let outputs = self.floating_outputs();
let Some((remembered, work)) = app_id
.as_deref()
@@ -65,13 +66,16 @@ impl Ferese {
self.add_tiled_window(window);
return;
}
if let Some(parent) = parent {
self.add_transient_window(window, parent);
return;
}
if let Some(pointer) = self.seat.get_pointer() {
self.focus_output_at(pointer.current_location());
}
let remembered_work = app_id
.filter(|app| !crate::window_rules::is_native_dialog(Some(app)))
.and_then(|app| self.floating_memory.get(app))
@@ -86,12 +90,13 @@ impl Ferese {
if let Some(work) = remembered_work {
self.focus_output_at((work.x + work.width / 2.0, work.y + work.height / 2.0).into());
}
let Some(bounds) = self.output_bounds() else {
self.add_tiled_window(window);
return;
};
let id = WindowId(self.next_window_id);
self.next_window_id += 1;
let id = self.windows.allocate_id();
let size_rect = client_size(&window)
.map(|size| natural_floating_rect(bounds, size))
.unwrap_or_else(|| centered_floating_rect(bounds));
@@ -100,7 +105,6 @@ impl Ferese {
.focused_window
.and_then(|focused| self.presented_window_rect(focused))
.unwrap_or(bounds);
self.floating_placement_anchors.insert(id, anchor);
let fullscreen = self.workspaces.active().fullscreen;
let focus = true;
if let Err(error) = self
@@ -110,25 +114,30 @@ impl Ferese {
tracing::error!(%error, ?id, "failed to insert rule-placed floating window");
return;
}
self.windows.register(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()
{
self.natural_floating_pending.insert(id);
self.windows.update(id, |w| w.natural_floating_pending = true);
}
self.window_ids.insert(window.clone(), id);
self.window_stack.insert(id);
if let Some(fullscreen) = fullscreen {
self.floating_above_fullscreen.insert(id, fullscreen);
}
if focus {
self.focused_window = Some(id);
}
self.space.map_element(window, (0, 0), focus);
// apply_initial_window_rules performs the first relayout/configure.
}
pub fn add_tiled_window(&mut self, window: Window) {
let id = WindowId(self.next_window_id);
self.next_window_id += 1;
let id = self.windows.allocate_id();
let focus_new_window = self.workspaces.active().fullscreen.is_none();
let axis = self
@@ -147,11 +156,12 @@ impl Ferese {
return;
}
self.window_ids.insert(window.clone(), id);
self.windows.register(window.clone(), id);
self.window_stack.insert(id);
if focus_new_window {
self.focused_window = Some(id);
}
self.space.map_element(window, (0, 0), focus_new_window);
self.relayout();
@@ -165,18 +175,19 @@ impl Ferese {
self.add_tiled_window(window);
return;
};
let Some(bounds) = self.floating_bounds_for_window(parent) else {
self.add_tiled_window(window);
return;
};
let parent_rect = self.presented_window_rect(parent).unwrap_or(bounds);
let visible_parent = crate::floating::intersection(parent_rect, bounds).unwrap_or(bounds);
let size_rect = client_size(&window)
.map(|s| natural_floating_rect(bounds, s))
.unwrap_or_else(|| centered_transient_rect(visible_parent));
let rect = self.place_floating(&window, None, bounds, (size_rect.width, size_rect.height), None);
let id = WindowId(self.next_window_id);
self.next_window_id += 1;
let id = self.windows.allocate_id();
let focus = workspace == self.workspaces.active_id()
&& (self.focused_window == Some(parent) || self.workspaces.active().fullscreen == Some(parent));
@@ -185,11 +196,12 @@ impl Ferese {
return;
}
self.window_ids.insert(window.clone(), id);
self.windows.register(window.clone(), id);
self.window_stack.insert(id);
if client_size(&window).is_none() {
self.natural_floating_pending.insert(id);
self.windows.update(id, |w| w.natural_floating_pending = true);
}
if self
.workspaces
.workspace(workspace)
@@ -197,9 +209,11 @@ impl Ferese {
{
self.floating_above_fullscreen.insert(id, parent);
}
if focus {
self.focused_window = Some(id);
}
self.space.map_element(window, (0, 0), focus);
self.relayout();
if focus {
@@ -214,10 +228,15 @@ impl Ferese {
title: Option<&str>,
transient: bool,
) {
let Some(id) = self.window_ids.get(window).copied() else {
let Some(id) = self.windows.ids().get(window).copied() else {
return;
};
if !self.window_rules_applied.insert(id) {
if !self
.windows
.update(id, |w| !std::mem::replace(&mut w.rules_applied, true))
.unwrap_or(false)
{
return;
}
@@ -226,11 +245,12 @@ impl Ferese {
}
pub(super) fn reapply_window_rules(&mut self, old_rules: &[WindowRule]) {
let windows = self.window_ids.keys().cloned().collect::<Vec<_>>();
let windows = self.windows.ids().keys().cloned().collect::<Vec<_>>();
for window in windows {
let Some(toplevel) = window.toplevel() else {
continue;
};
let (app_id, title, transient) = with_states(toplevel.wl_surface(), |states| {
let attributes = states.data_map.get::<XdgToplevelSurfaceData>().unwrap().lock().unwrap();
(
@@ -248,15 +268,18 @@ impl Ferese {
}
pub(super) fn apply_window_rule_result(&mut self, window: &Window, rule: crate::window_rules::WindowRuleResult) {
let Some(id) = self.window_ids.get(window).copied() else {
let Some(id) = self.windows.ids().get(window).copied() else {
return;
};
let Some(bounds) = self.floating_bounds_for_window(id) else {
return;
};
if rule == Default::default() {
return;
}
let axis = self
.workspaces
.active()
@@ -269,6 +292,7 @@ impl Ferese {
{
tracing::warn!(%error, ?id, workspace, "failed to apply window workspace rule");
}
let bounds = self.floating_bounds_for_window(id).unwrap_or(bounds);
let should_float = rule
@@ -287,7 +311,7 @@ impl Ferese {
.and_then(|entry| entry.restore(&self.floating_outputs()))
.is_none()
{
self.natural_floating_pending.insert(id);
self.windows.update(id, |w| w.natural_floating_pending = true);
}
}
rect.width = rule.width.unwrap_or(rect.width).min(bounds.width);
@@ -309,6 +333,7 @@ impl Ferese {
} else {
Ok(())
};
if let Err(error) = result {
tracing::warn!(%error, ?id, "failed to apply floating window rule");
}
@@ -331,16 +356,19 @@ impl Ferese {
}
pub fn make_window_transient(&mut self, window: &Window, parent: WindowId) {
let Some(id) = self.window_ids.get(window).copied() else {
let Some(id) = self.windows.ids().get(window).copied() else {
return;
};
if self.workspaces.workspace_for_window(id) != self.workspaces.workspace_for_window(parent) {
tracing::warn!(?id, ?parent, "ignored transient parent on another workspace");
return;
}
let Some(bounds) = self.floating_bounds_for_window(parent) else {
return;
};
let parent_rect = self.presented_window_rect(parent).unwrap_or(bounds);
let visible_parent = crate::floating::intersection(parent_rect, bounds).unwrap_or(bounds);
let size_rect = client_size(window)
@@ -369,6 +397,7 @@ impl Ferese {
{
self.floating_above_fullscreen.insert(id, parent);
}
if self.focused_window == Some(parent) {
if let Err(error) = self.workspaces.focus_window(id) {
tracing::warn!(%error, ?id, "failed to focus transient window");
@@ -385,35 +414,18 @@ impl Ferese {
pub fn remove_tiled_window(&mut self, window: &Window) {
// Uncommitted toplevels have not entered a workspace yet.
self.space.unmap_elem(window);
let Some(id) = self.window_ids.remove(window) else {
let Some(id) = self.windows.remove(window) else {
return;
};
self.focus_history.remove(id);
self.window_geometry.remove(&id);
self.resize_transactions.remove(&id);
self.resize_snapshots.remove(&id);
self.window_resize_fills.remove(&id);
self.maximized_windows.remove(&id);
self.maximized_column_widths.remove(&id);
self.render.remove_window(id);
self.window_stack.remove(id);
self.floating_above_fullscreen.remove(&id);
self.natural_floating_pending.remove(&id);
self.floating_window_memory.remove(&id);
self.floating_placement_anchors.remove(&id);
self.floating_resize_anchors.remove(&id);
self.window_borders.remove(&id);
self.window_dims.remove(&id);
self.window_dimming.remove(&id);
self.window_focus.remove(&id);
self.window_shadows.remove(&id);
self.closing_windows.remove(&id);
self.window_rules_applied.remove(&id);
self.scrolling_world_x.remove(&id);
self.viewport_coupled_widths.remove(&id);
self.pending_column_width_cycles.remove(&id);
if let Err(error) = self.workspaces.remove_window(id) {
tracing::error!(%error, ?id, "failed to remove window from layout");
}
if self.focused_window == Some(id) {
self.focused_window = self.workspaces.active().last_focused;
}
@@ -423,7 +435,8 @@ impl Ferese {
pub(crate) fn capture_resize_before_commit(&mut self, surface: &WlSurface) {
let Some((window, id)) = self
.window_ids
.windows
.ids()
.iter()
.find(|(window, _)| {
window
@@ -434,9 +447,11 @@ impl Ferese {
else {
return;
};
let Some(transaction) = self.resize_transactions.get(&id) else {
let Some(transaction) = self.windows.transaction(&id) else {
return;
};
let serial = with_states(surface, |states| {
states
.data_map
@@ -446,6 +461,7 @@ impl Ferese {
if !transaction.accepts(serial) {
return;
}
let Some(output) = self
.space
.outputs()
@@ -454,10 +470,11 @@ impl Ferese {
else {
return;
};
let source_geometry = transaction.source_geometry().unwrap_or_else(|| window.geometry());
let scale = output.current_scale().fractional_scale();
let source_size = source_geometry.size.to_physical_precise_round(scale);
if self.resize_snapshots.get(&id).is_some_and(|snapshot| {
if self.render.snapshot(&id).is_some_and(|snapshot| {
crate::presentation::snapshot_covers_source(
smithay::backend::renderer::Texture::size(&snapshot.texture),
source_size,
@@ -467,7 +484,8 @@ impl Ferese {
}) {
return;
}
let used: usize = self.resize_snapshots.values().map(|snapshot| snapshot.bytes()).sum();
let used: usize = self.render.snapshots().map(|snapshot| snapshot.bytes()).sum();
let remaining = crate::presentation::SNAPSHOT_BUDGET.saturating_sub(used);
let result = if let Some(backend) = &self.nested_backend {
// Commit dispatch does not run inside the Winit event callback;
@@ -490,7 +508,7 @@ impl Ferese {
match result {
Ok(Some(snapshot)) => {
tracing::debug!(?id, bytes = snapshot.bytes(), "captured native resize handoff");
self.resize_snapshots.insert(id, snapshot);
self.render.set_snapshot(id, snapshot);
}
Ok(None) => {}
Err(error) => {
@@ -500,7 +518,7 @@ impl Ferese {
}
pub fn record_client_commit(&mut self, window: &Window) {
let Some(&id) = self.window_ids.get(window) else {
let Some(&id) = self.windows.ids().get(window) else {
return;
};
// current_serial is promoted by Smithay on commit, unlike
@@ -514,28 +532,30 @@ impl Ferese {
})
});
if self
.resize_transactions
.get(&id)
.windows
.transaction(&id)
.is_some_and(|transaction| transaction.accepts(committed_serial))
{
self.resize_transactions.remove(&id);
self.windows.clear_transaction(&id);
}
let Some(size) = client_size(window) else {
return;
};
let fullscreen = self
.workspaces
.workspace_for_window(id)
.and_then(|workspace| self.workspaces.workspace(workspace))
.is_some_and(|workspace| workspace.fullscreen == Some(id));
let normal_geometry = !fullscreen
&& !self.maximized_windows.contains(&id)
&& self
.window_geometry
.get(&id)
.is_none_or(|geometry| !geometry.is_zooming());
let initial_floating =
self.natural_floating_pending.remove(&id) || self.floating_placement_anchors.contains_key(&id);
&& !self.windows.record(id).is_some_and(|w| w.maximized)
&& self.windows.geometry(&id).is_none_or(|geometry| !geometry.is_zooming());
let initial_floating = self
.windows
.update(id, |w| std::mem::take(&mut w.natural_floating_pending))
.unwrap_or(false)
|| self.windows.record(id).is_some_and(|w| w.placement_anchor.is_some());
if initial_floating
&& normal_geometry
&& matches!(self.workspaces.placement(id), Some(WindowPlacement::Floating { .. }))
@@ -548,12 +568,12 @@ impl Ferese {
(f64::from(size.width), f64::from(size.height)),
Some((Some(f64::from(size.width)), Some(f64::from(size.height)))),
);
self.floating_placement_anchors.remove(&id);
self.windows.update(id, |w| w.placement_anchor = None);
if self.workspaces.set_floating_rect(id, rect).is_ok() {
// This is the first mapped client buffer, not a user resize.
// Do not animate from the temporary placement box.
self.window_geometry.insert(id, WindowGeometry::new(rect, Some(size)));
self.resize_transactions.remove(&id);
self.windows.set_geometry(id, WindowGeometry::new(rect, Some(size)));
self.windows.clear_transaction(&id);
self.relayout();
}
}
@@ -561,7 +581,7 @@ impl Ferese {
// terminal cell grids, or a dialog changing its contents). Its committed
// window geometry is authoritative once the latest configure is committed.
// Never let an old buffer undo a newer resize or a fullscreen transition.
self.floating_placement_anchors.remove(&id);
self.windows.update(id, |w| w.placement_anchor = None);
let settled_configure = window.toplevel().is_some_and(|toplevel| {
with_states(toplevel.wl_surface(), |states| {
states.data_map.get::<XdgToplevelSurfaceData>().is_some_and(|data| {
@@ -581,22 +601,25 @@ impl Ferese {
&& ClientSize::from_rect(rect) != size
{
let mut rect = Rect::new(rect.x, rect.y, f64::from(size.width), f64::from(size.height));
if let Some((left, top, anchor)) = self.floating_resize_anchors.get(&id) {
if let Some((left, top, anchor)) = self.windows.record(id).and_then(|w| w.resize_anchor.as_ref()) {
rect = crate::floating::anchored_size(*anchor, (rect.width, rect.height), *left, *top);
} else if initial_floating && let Some(work) = self.floating_bounds_for_window(id) {
rect = crate::floating::clamp_to_work(rect, work);
}
if self.workspaces.set_floating_rect(id, rect).is_ok() {
self.window_geometry.insert(id, WindowGeometry::new(rect, Some(size)));
self.resize_transactions.remove(&id);
self.resize_snapshots.remove(&id);
self.windows.set_geometry(id, WindowGeometry::new(rect, Some(size)));
self.windows.clear_transaction(&id);
self.render.clear_snapshot(&id);
self.relayout();
}
}
if settled_configure && self.floating_resize_anchors.remove(&id).is_some() {
if settled_configure && self.windows.update(id, |w| w.resize_anchor.take()).flatten().is_some() {
self.remember_floating(window);
}
let Some(geometry) = self.window_geometry.get_mut(&id) else {
let Some(geometry) = self.windows.geometry_mut(&id) else {
return;
};
@@ -614,17 +637,21 @@ impl Ferese {
return;
}
self.closing_windows.entry(focused).or_default();
self.windows.update(focused, |record| {
record.closing.get_or_insert_default();
});
crate::backends::direct::render_all(self);
}
pub(crate) fn close_managed_window(&mut self, id: WindowId) -> bool {
if !self.window_ids.values().any(|window_id| *window_id == id) {
if !self.windows.ids().values().any(|window_id| *window_id == id) {
return false;
}
if self.animations_enabled {
self.closing_windows.entry(id).or_default();
self.windows.update(id, |record| {
record.closing.get_or_insert_default();
});
crate::backends::direct::render_all(self);
} else {
self.send_window_close(id);
@@ -635,7 +662,8 @@ impl Ferese {
pub(super) fn send_window_close(&self, id: WindowId) {
let Some(toplevel) = self
.window_ids
.windows
.ids()
.iter()
.find_map(|(window, window_id)| (*window_id == id).then(|| window.toplevel()).flatten())
else {