Split shell, settings, and compositor into focused modules

This commit is contained in:
Samuel Onoja committed 2026-10-01 12:47:26 +01:00
1 parent 2b9f30fa5d
commit cbf048566a
38 files changed
+9998 -9817

No files matched your search

+429
View File
@@ -0,0 +1,429 @@
use super::*;
impl Ferese {
pub(super) fn visible_workspace_ids(&self) -> HashSet<WorkspaceId> {
let mut visible = self
.output_workspaces
.connected_outputs()
.filter_map(|output| self.output_workspaces.active_workspace(output))
.collect::<HashSet<_>>();
visible.extend(
self.workspace_slides
.values()
.flat_map(|slide| slide.items.iter().map(|item| item.workspace)),
);
visible
}
pub(super) fn prune_empty_workspaces(&mut self, visible: &HashSet<WorkspaceId>) {
let mut protected = visible.clone();
protected.extend(self.output_workspaces.history_workspaces());
for workspace in self.workspaces.prune_empty(&protected) {
self.output_workspaces.forget_workspace(workspace);
self.viewport_animations.remove(&workspace);
}
}
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) {
continue;
}
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) {
geometry.settle_presentation();
}
self.space.unmap_elem(window);
}
changed
}
pub fn relayout(&mut self) {
self.refresh_input_capture_zones();
let visible_workspaces = self.visible_workspace_ids();
self.prune_empty_workspaces(&visible_workspaces);
if self.session_lock.active {
self.configure_lock_surfaces();
}
// 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.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 outputs = self.space.outputs().cloned().collect::<Vec<_>>();
let constraints = self.window_constraints();
let mut visible = HashSet::new();
let mut placements = Vec::new();
let mut visible_workspace_pairs = Vec::new();
for output in outputs {
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;
};
visible_workspace_pairs.push((output.clone(), active_workspace));
if let Some(slide) = self.workspace_slides.get(&output_id) {
for item in &slide.items {
if item.workspace != active_workspace {
visible_workspace_pairs.push((output.clone(), item.workspace));
}
}
}
}
for (output, workspace_id) in visible_workspace_pairs {
let Some(bounds) = self.output_bounds_for(&output) else {
continue;
};
let fullscreen_bounds = self.full_output_bounds_for(&output).unwrap_or(bounds);
let scale = output.current_scale().fractional_scale();
for layer in layer_map_for_output(&output).layers() {
crate::handlers::set_surface_tree_scale(layer.wl_surface(), scale);
}
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;
let focused = self
.focused_window
.filter(|window| self.workspaces.workspace_for_window(*window) == Some(workspace_id))
.or(workspace_focus);
let Some(workspace) = self.workspaces.workspace_mut(workspace_id) else {
continue;
};
let layout =
match workspace
.layout
.geometry_with_constraints(bounds, self.gap_config, &constraints, focused)
{
Ok(layout) => layout,
Err(error) => {
tracing::error!(%error, ?workspace_id, "failed to compute tiled geometry");
continue;
}
};
let viewport_target = workspace.layout.viewport_x();
let viewport_motion = viewport_target.map(|target| {
let viewport = self
.viewport_animations
.entry(workspace_id)
.or_insert_with(|| AnimatedValue::new(target));
let target_changed = (viewport.target - target).abs() > 0.001;
viewport.retarget_preserving_motion(target);
if !self.animations_enabled {
viewport.snap();
}
(viewport.current, target_changed)
});
let viewport_current = viewport_motion.map(|(current, _)| current);
let viewport_target_changed = viewport_motion.is_some_and(|(_, changed)| changed);
for warning in layout.warnings {
tracing::warn!(
window = ?warning.window,
kind = ?warning.kind,
requested = warning.requested,
assigned = warning.assigned,
"window size constraint could not be satisfied exactly"
);
}
for (window, id) in &self.window_ids {
if self.workspaces.workspace_for_window(*id) != Some(workspace_id) {
continue;
}
if let Some(toplevel) = window.toplevel() {
crate::handlers::set_surface_tree_scale(toplevel.wl_surface(), scale);
}
let is_maximized = self.maximized_windows.contains(id) && workspace_fullscreen != Some(*id);
let rect = if workspace_fullscreen == Some(*id) {
fullscreen_bounds
} else if is_maximized && !is_scrolling_layout {
maximized_rect(bounds, self.gap_config.outer)
} else {
match self.workspaces.placement(*id) {
Some(WindowPlacement::Tiled) => {
let Some(rect) = layout.geometry.get(id).copied() else {
continue;
};
rect
}
Some(WindowPlacement::Floating { rect }) => {
let constrained =
constrained_floating_rect(rect, constraints.get(id).copied().unwrap_or_default());
if constrained != rect {
let _ = self.workspaces.set_floating_rect(*id, constrained);
}
constrained
}
None => continue,
}
};
let is_fullscreen = workspace_fullscreen == Some(*id);
let is_floating = matches!(self.workspaces.placement(*id), Some(WindowPlacement::Floating { .. }));
visible.insert(*id);
let scrolling = if !is_fullscreen && !is_floating && (!is_maximized || is_scrolling_layout) {
viewport_target
.zip(viewport_current)
.map(|(target, current)| (workspace_id, rect.x + target, current))
} else {
None
};
placements.push((
window.clone(),
*id,
rect,
is_fullscreen,
is_maximized,
is_floating,
scrolling,
pending_column_width_cycles.contains(id) && viewport_target_changed,
));
}
}
let mut layout_changed = self.unmap_invisible_windows(&visible);
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();
// A viewport-coupled width must never override fullscreen/floating geometry.
if scrolling.is_none() {
self.viewport_coupled_widths.remove(&id);
}
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 mode = if is_fullscreen {
PresentationMode::Fullscreen
} else if is_maximized {
PresentationMode::Maximized
} else {
PresentationMode::Normal
};
layout_changed |= !had_geometry || geometry.logical != rect;
let mut requested_size = geometry.set_presentation_mode(rect, mode, now);
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);
}
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);
let mut animated_world_x = previous_scrolling_world_x
.remove(&id)
.filter(|(previous_workspace, _)| *previous_workspace == workspace)
.map(|(_, world_x)| world_x)
.unwrap_or_else(|| AnimatedValue::new(restored_world_x));
animated_world_x.set_target(world_x);
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));
let coupled = !geometry.is_zooming()
&& (couple_width
|| self
.viewport_coupled_widths
.get(&id)
.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)));
if width.0 != workspace {
*width = (workspace, AnimatedValue::new(geometry.visual.current.width));
}
width.1.retarget_preserving_motion(rect.width);
if !self.animations_enabled {
width.1.snap();
}
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);
}
}
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);
if let Some(toplevel) = window.toplevel() {
let state_changed = toplevel.with_pending_state(|state| {
if natural_pending {
state.size = None;
} else if let Some(size) = requested_size {
state.size = Some((size.width, size.height).into());
}
let fullscreen_changed = if is_fullscreen {
state.states.set(xdg_toplevel::State::Fullscreen)
} else {
state.states.unset(xdg_toplevel::State::Fullscreen)
};
let maximized_changed = if is_maximized {
state.states.set(xdg_toplevel::State::Maximized)
} 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)
| state.states.set(xdg_toplevel::State::TiledRight)
| state.states.set(xdg_toplevel::State::TiledTop)
| state.states.set(xdg_toplevel::State::TiledBottom)
} else {
state.states.unset(xdg_toplevel::State::TiledLeft)
| state.states.unset(xdg_toplevel::State::TiledRight)
| state.states.unset(xdg_toplevel::State::TiledTop)
| state.states.unset(xdg_toplevel::State::TiledBottom)
};
let decoration_mode = if is_floating && !is_fullscreen && !is_maximized {
DecorationMode::ClientSide
} else {
DecorationMode::ServerSide
};
let decoration_changed = state.decoration_mode != Some(decoration_mode);
state.decoration_mode = Some(decoration_mode);
fullscreen_changed || maximized_changed || tiled_changed || decoration_changed
});
if (natural_pending || requested_size.is_some() || state_changed)
&& let Some(serial) = toplevel.send_pending_configure()
&& requested_size.is_some()
&& had_geometry
&& self.animations_enabled
{
self.resize_transactions.insert(
id,
crate::resize_transaction::ResizeTransaction::new(serial, now)
.with_source_geometry(window.geometry()),
);
}
}
}
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();
crate::backends::direct::render_all(self);
}
pub(crate) fn raise_window(&mut self, window: &Window, activate: bool) {
if let Some(id) = self.window_ids.get(window) {
self.window_stack.raise(*id);
}
self.space.raise_element(window, activate);
self.sync_window_stacking();
}
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 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()) {
return;
}
for window in windows {
self.space.raise_element(&window, false);
}
}
pub(super) fn tiled_layout(&mut self, bounds: Rect) -> Result<LayoutResult, ferese_layout::LayoutError> {
let constraints = self.window_constraints();
let focused = self.focused_window;
self.workspaces
.active_mut()
.layout
.geometry_with_constraints(bounds, self.gap_config, &constraints, focused)
}
pub(super) fn window_constraints(&self) -> HashMap<WindowId, SizeConstraints> {
self.window_ids
.iter()
.filter_map(|(window, id)| {
let toplevel = window.toplevel()?;
let (minimum, maximum) = with_states(toplevel.wl_surface(), |states| {
let mut cached = states.cached_state.get::<SurfaceCachedState>();
let state = cached.current();
(state.min_size, state.max_size)
});
Some((
*id,
SizeConstraints {
min_width: minimum.w.max(1) as f64,
min_height: minimum.h.max(1) as f64,
max_width: (maximum.w > 0).then_some(maximum.w as f64),
max_height: (maximum.h > 0).then_some(maximum.h as f64),
},
))
})
.collect()
}
pub(super) fn arrange_layers(&self) {
let outputs = self.space.outputs().cloned().collect::<Vec<_>>();
for output in outputs {
layer_map_for_output(&output).arrange();
}
}
}