Split shell, settings, and compositor into focused modules
This commit is contained in:
1 parent
2b9f30fa5d
commit
cbf048566a
38 files changed
+9998
-9817
No files matched your search
@@ -0,0 +1,642 @@
|
||||
use super::*;
|
||||
|
||||
impl Ferese {
|
||||
pub(crate) fn restore_initial_floating_size(&mut self, window: &Window) {
|
||||
let Some(toplevel) = window.toplevel() else { return };
|
||||
let (app_id, title, transient) = with_states(toplevel.wl_surface(), |states| {
|
||||
let attributes = states.data_map.get::<XdgToplevelSurfaceData>().unwrap().lock().unwrap();
|
||||
(
|
||||
attributes.app_id.clone(),
|
||||
attributes.title.clone(),
|
||||
attributes.parent.is_some(),
|
||||
)
|
||||
});
|
||||
let special_mode = toplevel.with_pending_state(|pending| {
|
||||
pending.states.contains(xdg_toplevel::State::Fullscreen)
|
||||
|| pending.states.contains(xdg_toplevel::State::Maximized)
|
||||
});
|
||||
let rule = resolve_window_rules(&self.window_rules, app_id.as_deref(), title.as_deref(), transient);
|
||||
if transient
|
||||
|| special_mode
|
||||
|| rule.fullscreen == Some(true)
|
||||
|| !rule.floating.unwrap_or(rule.width.is_some() || rule.height.is_some())
|
||||
{
|
||||
return;
|
||||
}
|
||||
let outputs = self.floating_outputs();
|
||||
let Some((remembered, work)) = app_id
|
||||
.as_deref()
|
||||
.and_then(|app| self.floating_memory.get(app))
|
||||
.and_then(|entry| {
|
||||
let rect = entry.restore(&outputs)?;
|
||||
let work = outputs.iter().find(|(name, _)| *name == entry.output)?.1;
|
||||
Some((rect, work))
|
||||
})
|
||||
else {
|
||||
return;
|
||||
};
|
||||
// Metadata is available on the initial bufferless commit. Restore the
|
||||
// size before the client creates its first buffer, without mapping or
|
||||
// focusing the window. Use the same rule/constraint precedence as map.
|
||||
let rect = self.place_floating(
|
||||
window,
|
||||
None,
|
||||
work,
|
||||
(remembered.width, remembered.height),
|
||||
Some((rule.width, rule.height)),
|
||||
);
|
||||
let size = ClientSize::from_rect(rect);
|
||||
toplevel.with_pending_state(|pending| pending.size = Some((size.width, size.height).into()));
|
||||
}
|
||||
|
||||
pub(crate) fn add_rule_placed_window(
|
||||
&mut self,
|
||||
window: Window,
|
||||
app_id: Option<&str>,
|
||||
title: Option<&str>,
|
||||
parent: Option<WindowId>,
|
||||
) {
|
||||
let rule = resolve_window_rules(&self.window_rules, app_id, title, parent.is_some());
|
||||
let floating = rule
|
||||
.floating
|
||||
.unwrap_or(parent.is_some() || rule.width.is_some() || rule.height.is_some());
|
||||
if !floating {
|
||||
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.and_then(|app| self.floating_memory.get(app)).and_then(|entry| {
|
||||
let outputs = self.floating_outputs();
|
||||
entry.restore(&outputs)?;
|
||||
outputs
|
||||
.into_iter()
|
||||
.find(|(name, _)| *name == entry.output)
|
||||
.map(|(_, work)| work)
|
||||
});
|
||||
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 size_rect = client_size(&window)
|
||||
.map(|size| natural_floating_rect(bounds, size))
|
||||
.unwrap_or_else(|| centered_floating_rect(bounds));
|
||||
let rect = self.place_floating(&window, None, bounds, (size_rect.width, size_rect.height), None);
|
||||
let anchor = self
|
||||
.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
|
||||
.workspaces
|
||||
.insert_floating_window(id, self.workspaces.active_id(), rect, focus)
|
||||
{
|
||||
tracing::error!(%error, ?id, "failed to insert rule-placed floating window");
|
||||
return;
|
||||
}
|
||||
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.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 focus_new_window = self.workspaces.active().fullscreen.is_none();
|
||||
|
||||
let axis = self
|
||||
.output_bounds()
|
||||
.and_then(|bounds| {
|
||||
self.workspaces
|
||||
.active()
|
||||
.layout
|
||||
.automatic_axis(self.focused_window, bounds)
|
||||
.ok()
|
||||
})
|
||||
.unwrap_or(Axis::Horizontal);
|
||||
|
||||
if let Err(error) = self.workspaces.insert_window(id, axis, 0.5) {
|
||||
tracing::error!(%error, ?id, "failed to insert window into layout");
|
||||
return;
|
||||
}
|
||||
|
||||
self.window_ids.insert(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();
|
||||
|
||||
if focus_new_window {
|
||||
self.restore_keyboard_focus();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_transient_window(&mut self, window: Window, parent: WindowId) {
|
||||
let Some(workspace) = self.workspaces.workspace_for_window(parent) else {
|
||||
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 focus = workspace == self.workspaces.active_id()
|
||||
&& (self.focused_window == Some(parent) || self.workspaces.active().fullscreen == Some(parent));
|
||||
if let Err(error) = self.workspaces.insert_floating_window(id, workspace, rect, focus) {
|
||||
tracing::error!(%error, ?id, ?parent, "failed to insert transient window");
|
||||
return;
|
||||
}
|
||||
|
||||
self.window_ids.insert(window.clone(), id);
|
||||
self.window_stack.insert(id);
|
||||
if client_size(&window).is_none() {
|
||||
self.natural_floating_pending.insert(id);
|
||||
}
|
||||
if self
|
||||
.workspaces
|
||||
.workspace(workspace)
|
||||
.is_some_and(|workspace| workspace.fullscreen == Some(parent))
|
||||
{
|
||||
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 {
|
||||
self.restore_keyboard_focus();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn apply_initial_window_rules(
|
||||
&mut self,
|
||||
window: &Window,
|
||||
app_id: Option<&str>,
|
||||
title: Option<&str>,
|
||||
transient: bool,
|
||||
) {
|
||||
let Some(id) = self.window_ids.get(window).copied() else {
|
||||
return;
|
||||
};
|
||||
if !self.window_rules_applied.insert(id) {
|
||||
return;
|
||||
}
|
||||
|
||||
let rule = resolve_window_rules(&self.window_rules, app_id, title, transient);
|
||||
self.apply_window_rule_result(window, rule);
|
||||
}
|
||||
|
||||
pub(super) fn reapply_window_rules(&mut self, old_rules: &[WindowRule]) {
|
||||
let windows = self.window_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();
|
||||
(
|
||||
attributes.app_id.clone(),
|
||||
attributes.title.clone(),
|
||||
attributes.parent.is_some(),
|
||||
)
|
||||
});
|
||||
let old = resolve_window_rules(old_rules, app_id.as_deref(), title.as_deref(), transient);
|
||||
let new = resolve_window_rules(&self.window_rules, app_id.as_deref(), title.as_deref(), transient);
|
||||
if let Some(new) = crate::window_rules::live_result(old, new, transient) {
|
||||
self.apply_window_rule_result(&window, new);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
return;
|
||||
};
|
||||
let Some(bounds) = self.floating_bounds_for_window(id) else {
|
||||
return;
|
||||
};
|
||||
if rule == Default::default() {
|
||||
return;
|
||||
}
|
||||
let axis = self
|
||||
.workspaces
|
||||
.active()
|
||||
.layout
|
||||
.automatic_axis(self.focused_window, bounds)
|
||||
.unwrap_or(Axis::Horizontal);
|
||||
|
||||
if let Some(workspace) = rule.workspace
|
||||
&& let Err(error) = self.workspaces.move_window_to_numeric(id, workspace, axis, 0.5)
|
||||
{
|
||||
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
|
||||
.floating
|
||||
.or((rule.width.is_some() || rule.height.is_some()).then_some(true));
|
||||
if let Some(should_float) = should_float {
|
||||
let is_floating = matches!(self.workspaces.placement(id), Some(WindowPlacement::Floating { .. }));
|
||||
let mut rect = centered_floating_rect(bounds);
|
||||
if should_float && rule.width.is_none() && rule.height.is_none() {
|
||||
if let Some(size) = client_size(window) {
|
||||
rect = natural_floating_rect(bounds, size);
|
||||
} else if Self::floating_metadata(window)
|
||||
.0
|
||||
.as_deref()
|
||||
.and_then(|app| self.floating_memory.get(app))
|
||||
.and_then(|entry| entry.restore(&self.floating_outputs()))
|
||||
.is_none()
|
||||
{
|
||||
self.natural_floating_pending.insert(id);
|
||||
}
|
||||
}
|
||||
rect.width = rule.width.unwrap_or(rect.width).min(bounds.width);
|
||||
rect.height = rule.height.unwrap_or(rect.height).min(bounds.height);
|
||||
if should_float {
|
||||
rect = self.place_floating(
|
||||
window,
|
||||
Some(id),
|
||||
bounds,
|
||||
(rect.width, rect.height),
|
||||
Some((rule.width, rule.height)),
|
||||
);
|
||||
}
|
||||
|
||||
let result = if should_float != is_floating {
|
||||
self.workspaces.toggle_floating(id, rect, axis, 0.5).map(|_| ())
|
||||
} else if should_float && (rule.width.is_some() || rule.height.is_some()) {
|
||||
self.workspaces.set_floating_rect(id, rect)
|
||||
} else {
|
||||
Ok(())
|
||||
};
|
||||
if let Err(error) = result {
|
||||
tracing::warn!(%error, ?id, "failed to apply floating window rule");
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(fullscreen) = rule.fullscreen
|
||||
&& let Err(error) = self.workspaces.set_fullscreen(id, fullscreen)
|
||||
{
|
||||
tracing::warn!(%error, ?id, fullscreen, "failed to apply fullscreen window rule");
|
||||
}
|
||||
|
||||
if self.workspaces.workspace_for_window(id) != Some(self.workspaces.active_id())
|
||||
&& self.focused_window == Some(id)
|
||||
{
|
||||
self.focused_window = self.workspaces.active().last_focused;
|
||||
}
|
||||
|
||||
self.relayout();
|
||||
self.restore_keyboard_focus();
|
||||
}
|
||||
|
||||
pub fn make_window_transient(&mut self, window: &Window, parent: WindowId) {
|
||||
let Some(id) = self.window_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)
|
||||
.map(|s| natural_floating_rect(bounds, s))
|
||||
.unwrap_or_else(|| centered_transient_rect(visible_parent));
|
||||
let rect = self.place_floating(window, Some(id), bounds, (size_rect.width, size_rect.height), None);
|
||||
let result = match self.workspaces.placement(id) {
|
||||
Some(WindowPlacement::Tiled) => self
|
||||
.workspaces
|
||||
.toggle_floating(id, rect, Axis::Horizontal, 0.5)
|
||||
.map(|_| ()),
|
||||
Some(WindowPlacement::Floating { .. }) => self.workspaces.set_floating_rect(id, rect),
|
||||
None => return,
|
||||
};
|
||||
|
||||
if let Err(error) = result {
|
||||
tracing::warn!(%error, ?id, ?parent, "failed to apply transient placement");
|
||||
return;
|
||||
}
|
||||
|
||||
if self
|
||||
.workspaces
|
||||
.workspace_for_window(parent)
|
||||
.and_then(|workspace| self.workspaces.workspace(workspace))
|
||||
.is_some_and(|workspace| workspace.fullscreen == Some(parent))
|
||||
{
|
||||
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");
|
||||
} else {
|
||||
self.focused_window = Some(id);
|
||||
self.window_stack.raise(id);
|
||||
}
|
||||
}
|
||||
|
||||
self.relayout();
|
||||
self.restore_keyboard_focus();
|
||||
}
|
||||
|
||||
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 {
|
||||
return;
|
||||
};
|
||||
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.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;
|
||||
}
|
||||
|
||||
self.relayout();
|
||||
}
|
||||
|
||||
pub(crate) fn capture_resize_before_commit(&mut self, surface: &WlSurface) {
|
||||
let Some((window, id)) = self
|
||||
.window_ids
|
||||
.iter()
|
||||
.find(|(window, _)| {
|
||||
window
|
||||
.toplevel()
|
||||
.is_some_and(|toplevel| toplevel.wl_surface() == surface)
|
||||
})
|
||||
.map(|(window, id)| (window.clone(), *id))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let Some(transaction) = self.resize_transactions.get(&id) else {
|
||||
return;
|
||||
};
|
||||
let serial = with_states(surface, |states| {
|
||||
states
|
||||
.data_map
|
||||
.get::<XdgToplevelSurfaceData>()
|
||||
.and_then(|data| data.lock().ok().and_then(|data| data.current_serial))
|
||||
});
|
||||
if !transaction.accepts(serial) {
|
||||
return;
|
||||
}
|
||||
let Some(output) = self
|
||||
.space
|
||||
.outputs()
|
||||
.find(|output| self.window_belongs_to_output(id, output))
|
||||
.cloned()
|
||||
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| {
|
||||
crate::presentation::snapshot_covers_source(
|
||||
smithay::backend::renderer::Texture::size(&snapshot.texture),
|
||||
source_size,
|
||||
snapshot.scale,
|
||||
scale,
|
||||
)
|
||||
}) {
|
||||
return;
|
||||
}
|
||||
let used: usize = self.resize_snapshots.values().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;
|
||||
// still never risk reentrant renderer access or a compositor panic.
|
||||
match backend.try_borrow_mut() {
|
||||
Ok(mut backend) => crate::render::capture_resize_snapshot(
|
||||
backend.renderer(),
|
||||
&window,
|
||||
source_geometry,
|
||||
output.current_scale().fractional_scale(),
|
||||
remaining,
|
||||
),
|
||||
Err(_) => return,
|
||||
}
|
||||
} else if let Some(backend) = &mut self.direct_backend {
|
||||
backend.capture_resize_snapshot(&window, source_geometry, &output, remaining)
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
match result {
|
||||
Ok(Some(snapshot)) => {
|
||||
tracing::debug!(?id, bytes = snapshot.bytes(), "captured native resize handoff");
|
||||
self.resize_snapshots.insert(id, snapshot);
|
||||
}
|
||||
Ok(None) => {}
|
||||
Err(error) => {
|
||||
tracing::debug!(%error, ?id, "resize snapshot unavailable; using native crop")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn record_client_commit(&mut self, window: &Window) {
|
||||
let Some(&id) = self.window_ids.get(window) else {
|
||||
return;
|
||||
};
|
||||
// current_serial is promoted by Smithay on commit, unlike
|
||||
// configure_serial, which only records an acknowledgement.
|
||||
let committed_serial = window.toplevel().and_then(|toplevel| {
|
||||
with_states(toplevel.wl_surface(), |states| {
|
||||
states
|
||||
.data_map
|
||||
.get::<XdgToplevelSurfaceData>()
|
||||
.and_then(|data| data.lock().ok().and_then(|data| data.current_serial))
|
||||
})
|
||||
});
|
||||
if self
|
||||
.resize_transactions
|
||||
.get(&id)
|
||||
.is_some_and(|transaction| transaction.accepts(committed_serial))
|
||||
{
|
||||
self.resize_transactions.remove(&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);
|
||||
if initial_floating
|
||||
&& normal_geometry
|
||||
&& matches!(self.workspaces.placement(id), Some(WindowPlacement::Floating { .. }))
|
||||
&& let Some(bounds) = self.floating_bounds_for_window(id)
|
||||
{
|
||||
let rect = self.place_floating(
|
||||
window,
|
||||
Some(id),
|
||||
bounds,
|
||||
(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);
|
||||
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.relayout();
|
||||
}
|
||||
}
|
||||
// A normal floating client may choose a different size (minimum sizes,
|
||||
// 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);
|
||||
let settled_configure = window.toplevel().is_some_and(|toplevel| {
|
||||
with_states(toplevel.wl_surface(), |states| {
|
||||
states.data_map.get::<XdgToplevelSurfaceData>().is_some_and(|data| {
|
||||
let Ok(data) = data.lock() else { return false };
|
||||
floating_commit_is_current(
|
||||
data.pending_configures().is_empty(),
|
||||
data.current_serial,
|
||||
data.configure_serial,
|
||||
data.current.states.contains(xdg_toplevel::State::Resizing),
|
||||
)
|
||||
})
|
||||
})
|
||||
});
|
||||
if settled_configure
|
||||
&& normal_geometry
|
||||
&& let Some(WindowPlacement::Floating { rect }) = self.workspaces.placement(id)
|
||||
&& 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) {
|
||||
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.relayout();
|
||||
}
|
||||
}
|
||||
if settled_configure && self.floating_resize_anchors.remove(&id).is_some() {
|
||||
self.remember_floating(window);
|
||||
}
|
||||
let Some(geometry) = self.window_geometry.get_mut(&id) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let matches_target = geometry.client.commit(size);
|
||||
tracing::debug!(?id, ?size, matches_target, "recorded client geometry commit");
|
||||
}
|
||||
|
||||
pub fn close_focused_window(&mut self) {
|
||||
let Some(focused) = self.focused_window else {
|
||||
return;
|
||||
};
|
||||
|
||||
if !self.animations_enabled {
|
||||
self.send_window_close(focused);
|
||||
return;
|
||||
}
|
||||
|
||||
self.closing_windows.entry(focused).or_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) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if self.animations_enabled {
|
||||
self.closing_windows.entry(id).or_default();
|
||||
crate::backends::direct::render_all(self);
|
||||
} else {
|
||||
self.send_window_close(id);
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub(super) fn send_window_close(&self, id: WindowId) {
|
||||
let Some(toplevel) = self
|
||||
.window_ids
|
||||
.iter()
|
||||
.find_map(|(window, window_id)| (*window_id == id).then(|| window.toplevel()).flatten())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
toplevel.send_close();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user