702 lines
27 KiB
Rust
702 lines
27 KiB
Rust
use super::*;
|
|
|
|
impl Ferese {
|
|
pub(crate) fn window_for_surface(&self, surface: &WlSurface) -> Option<Window> {
|
|
self.windows
|
|
.id_for_surface(surface)
|
|
.and_then(|id| self.windows.window(id))
|
|
.or_else(|| {
|
|
self.space.elements().find(|window| {
|
|
window
|
|
.toplevel()
|
|
.is_some_and(|toplevel| toplevel.wl_surface() == surface)
|
|
})
|
|
})
|
|
.cloned()
|
|
}
|
|
|
|
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 crate::window_rules::is_native_dialog(app_id.as_deref())
|
|
|| 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
|
|
.filter(|app| !crate::window_rules::is_native_dialog(Some(app)))
|
|
.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 = self.windows.allocate_id();
|
|
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);
|
|
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;
|
|
}
|
|
|
|
self.windows.register_window(window.clone(), id);
|
|
self.refresh_capture_privacy();
|
|
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.windows.update(id, |w| w.natural_floating_pending = true);
|
|
}
|
|
|
|
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 = self.windows.allocate_id();
|
|
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.windows.register_window(window.clone(), id);
|
|
self.refresh_capture_privacy();
|
|
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 = self.windows.allocate_id();
|
|
|
|
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.windows.register_window(window.clone(), id);
|
|
self.refresh_capture_privacy();
|
|
self.window_stack.insert(id);
|
|
if client_size(&window).is_none() {
|
|
self.windows.update(id, |w| w.natural_floating_pending = true);
|
|
}
|
|
|
|
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.windows.ids().get(window).copied() else {
|
|
return;
|
|
};
|
|
|
|
if !self
|
|
.windows
|
|
.update(id, |w| !std::mem::replace(&mut w.rules_applied, true))
|
|
.unwrap_or(false)
|
|
{
|
|
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.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();
|
|
(
|
|
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.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()
|
|
.layout
|
|
.automatic_axis(self.focused_window, bounds)
|
|
.unwrap_or(Axis::Horizontal);
|
|
|
|
if let Some(index) = rule.workspace
|
|
&& let Some(output) = self
|
|
.workspaces
|
|
.workspace_for_window(id)
|
|
.and_then(|workspace| self.output_workspaces.output_for_workspace(workspace))
|
|
&& let Some(workspace) = self.output_workspaces.workspace_at(&self.workspaces, output, index)
|
|
&& let Err(error) = self.workspaces.move_window_to_workspace(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.windows.update(id, |w| w.natural_floating_pending = true);
|
|
}
|
|
}
|
|
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.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)
|
|
.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) {
|
|
self.update_capture_cursor_privacy();
|
|
self.retain_closed_window(window);
|
|
// Uncommitted toplevels have not entered a workspace yet.
|
|
self.space.unmap_elem(window);
|
|
let Some(id) = self.windows.remove(window) else {
|
|
return;
|
|
};
|
|
|
|
self.refresh_capture_privacy();
|
|
self.focus_history.remove(id);
|
|
#[cfg(feature = "resize-metrics")]
|
|
self.resize_metrics
|
|
.end(id, self.presentation_now(), crate::resize_metrics::End::Cancelled);
|
|
self.render.remove_window(id);
|
|
self.capture_render.remove_window(id);
|
|
self.window_stack.remove(id);
|
|
self.floating_above_fullscreen.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(id) = self.windows.id_for_surface(surface) else {
|
|
return;
|
|
};
|
|
let Some(window) = self.windows.window(id).cloned() else {
|
|
return;
|
|
};
|
|
|
|
let Some(transaction) = self.windows.transaction(&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.render.snapshot(&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.render.snapshots().map(|snapshot| snapshot.bytes()).sum::<usize>()
|
|
+ self.render.closing.iter().map(|window| window.bytes()).sum::<usize>();
|
|
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.render.set_snapshot(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.windows.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
|
|
.windows
|
|
.transaction(&id)
|
|
.is_some_and(|transaction| transaction.accepts(committed_serial))
|
|
{
|
|
#[cfg(feature = "resize-metrics")]
|
|
{
|
|
self.measure_resize_pauses(self.presentation_now());
|
|
self.resize_metrics
|
|
.end(id, self.presentation_now(), crate::resize_metrics::End::Commit);
|
|
}
|
|
self.windows.clear_transaction(&id);
|
|
self.release_presentation_dependencies();
|
|
}
|
|
|
|
let Some(size) = client_size(window) else {
|
|
return;
|
|
};
|
|
|
|
if let Some(record) = self.windows.record_mut(id)
|
|
&& !record.mapped_once
|
|
{
|
|
record.mapped_once = true;
|
|
if self.animations_enabled {
|
|
record.opening = Some(AnimatedValue {
|
|
current: 0.0,
|
|
target: 1.0,
|
|
velocity: 0.0,
|
|
});
|
|
self.last_animation_tick = Instant::now();
|
|
}
|
|
}
|
|
|
|
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.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 { .. }))
|
|
&& 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.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.windows.set_geometry(id, WindowGeometry::new(rect, Some(size)));
|
|
#[cfg(feature = "resize-metrics")]
|
|
self.resize_metrics
|
|
.end(id, self.presentation_now(), crate::resize_metrics::End::Cancelled);
|
|
self.windows.clear_transaction(&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.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| {
|
|
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.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.windows.set_geometry(id, WindowGeometry::new(rect, Some(size)));
|
|
#[cfg(feature = "resize-metrics")]
|
|
self.resize_metrics
|
|
.end(id, self.presentation_now(), crate::resize_metrics::End::Cancelled);
|
|
self.windows.clear_transaction(&id);
|
|
self.render.clear_snapshot(&id);
|
|
self.relayout();
|
|
}
|
|
}
|
|
|
|
if settled_configure && self.windows.update(id, |w| w.resize_anchor.take()).flatten().is_some() {
|
|
self.remember_floating(window);
|
|
}
|
|
|
|
let Some(geometry) = self.windows.geometry_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) {
|
|
if let Some(focused) = self.focused_window {
|
|
self.send_window_close(focused);
|
|
}
|
|
}
|
|
|
|
pub(crate) fn close_managed_window(&mut self, id: WindowId) -> bool {
|
|
if self.windows.window(id).is_none() {
|
|
return false;
|
|
}
|
|
self.send_window_close(id);
|
|
true
|
|
}
|
|
|
|
pub(super) fn send_window_close(&self, id: WindowId) {
|
|
let Some(toplevel) = self.windows.window(id).and_then(|window| window.toplevel()) else {
|
|
return;
|
|
};
|
|
|
|
toplevel.send_close();
|
|
}
|
|
}
|