fix: synchronize floating window geometry

This commit is contained in:
Samuel Onoja committed 2026-09-24 11:26:08 +01:00
1 parent e1f1c1b9b6
commit 677e0ba5d1
5 files changed
+165 -5

No files matched your search

+17 -3
View File
@@ -16,6 +16,7 @@ pub struct MoveSurfaceGrab {
pub start_data: GrabStartData<Ferese>, pub start_data: GrabStartData<Ferese>,
pub window: Window, pub window: Window,
pub initial_location: Point<i32, Logical>, pub initial_location: Point<i32, Logical>,
pub finished: bool,
} }
impl PointerGrab<Ferese> for MoveSurfaceGrab { impl PointerGrab<Ferese> for MoveSurfaceGrab {
@@ -28,8 +29,11 @@ impl PointerGrab<Ferese> for MoveSurfaceGrab {
) { ) {
handle.motion(data, None, event); handle.motion(data, None, event);
let location = self.initial_location.to_f64() + (event.location - self.start_data.location); let location = self.initial_location.to_f64() + (event.location - self.start_data.location);
data.space let location = location.to_i32_round();
.map_element(self.window.clone(), location.to_i32_round(), true); let size = self.window.geometry().size;
data.set_floating_window_geometry(&self.window, location, size);
data.space.map_element(self.window.clone(), location, true);
} }
fn relative_motion( fn relative_motion(
@@ -50,6 +54,7 @@ impl PointerGrab<Ferese> for MoveSurfaceGrab {
) { ) {
handle.button(data, event); handle.button(data, event);
if handle.current_pressed().is_empty() { if handle.current_pressed().is_empty() {
self.finished = true;
handle.unset_grab(self, data, event.serial, event.time, true); handle.unset_grab(self, data, event.serial, event.time, true);
} }
} }
@@ -143,5 +148,14 @@ impl PointerGrab<Ferese> for MoveSurfaceGrab {
&self.start_data &self.start_data
} }
fn unset(&mut self, _data: &mut Ferese) {} fn unset(&mut self, data: &mut Ferese) {
if self.finished {
return;
}
let size = self.window.geometry().size;
data.set_floating_window_geometry(&self.window, self.initial_location, size);
data.space
.map_element(self.window.clone(), self.initial_location, true);
}
} }
+58 -1
View File
@@ -71,6 +71,7 @@ pub struct ResizeSurfaceGrab {
edges: ResizeEdge, edges: ResizeEdge,
initial_rect: Rectangle<i32, Logical>, initial_rect: Rectangle<i32, Logical>,
last_size: Size<i32, Logical>, last_size: Size<i32, Logical>,
finished: bool,
} }
impl ResizeSurfaceGrab { impl ResizeSurfaceGrab {
@@ -98,6 +99,7 @@ impl ResizeSurfaceGrab {
edges, edges,
initial_rect, initial_rect,
last_size: initial_rect.size, last_size: initial_rect.size,
finished: false,
} }
} }
} }
@@ -123,6 +125,8 @@ impl PointerGrab<Ferese> for ResizeSurfaceGrab {
}); });
self.last_size = self.last_size =
constrained_size(self.initial_rect.size, delta, self.edges, minimum, maximum); constrained_size(self.initial_rect.size, delta, self.edges, minimum, maximum);
let rect = resized_rect(self.initial_rect, self.last_size, self.edges);
data.set_floating_window_geometry(&self.window, rect.loc, rect.size);
surface.with_pending_state(|state| { surface.with_pending_state(|state| {
state.states.set(xdg_toplevel::State::Resizing); state.states.set(xdg_toplevel::State::Resizing);
@@ -149,6 +153,7 @@ impl PointerGrab<Ferese> for ResizeSurfaceGrab {
) { ) {
handle.button(data, event); handle.button(data, event);
if handle.current_pressed().is_empty() { if handle.current_pressed().is_empty() {
self.finished = true;
handle.unset_grab(self, data, event.serial, event.time, true); handle.unset_grab(self, data, event.serial, event.time, true);
let surface = self let surface = self
.window .window
@@ -250,7 +255,29 @@ impl PointerGrab<Ferese> for ResizeSurfaceGrab {
&self.start_data &self.start_data
} }
fn unset(&mut self, _data: &mut Ferese) {} fn unset(&mut self, data: &mut Ferese) {
if self.finished {
return;
}
let surface = self
.window
.toplevel()
.expect("managed window has a toplevel");
surface.with_pending_state(|state| {
state.states.unset(xdg_toplevel::State::Resizing);
state.size = Some(self.initial_rect.size);
});
surface.send_pending_configure();
ResizeState::with(surface.wl_surface(), |state| *state = ResizeState::Idle);
data.set_floating_window_geometry(
&self.window,
self.initial_rect.loc,
self.initial_rect.size,
);
data.space
.map_element(self.window.clone(), self.initial_rect.loc, true);
}
} }
#[derive(Clone, Copy, Debug, Default)] #[derive(Clone, Copy, Debug, Default)]
@@ -359,6 +386,23 @@ fn constrained_size(
.into() .into()
} }
fn resized_rect(
initial: Rectangle<i32, Logical>,
size: Size<i32, Logical>,
edges: ResizeEdge,
) -> Rectangle<i32, Logical> {
let mut location = initial.loc;
if edges.left() {
location.x += initial.size.w - size.w;
}
if edges.top() {
location.y += initial.size.h - size.h;
}
Rectangle::new(location, size)
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@@ -398,4 +442,17 @@ mod tests {
); );
assert_eq!(size, (1_024, 768).into()); assert_eq!(size, (1_024, 768).into());
} }
#[test]
fn resized_rect_keeps_the_opposite_edge_fixed() {
let initial = Rectangle::new((100, 80).into(), (800, 600).into());
let resized = resized_rect(
initial,
(700, 550).into(),
ResizeEdge(xdg_toplevel::ResizeEdge::TopLeft),
);
assert_eq!(resized.loc, (200, 130).into());
assert_eq!(resized.size, (700, 550).into());
}
} }
+7
View File
@@ -100,12 +100,16 @@ impl XdgShellHandler for Ferese {
let Some(initial_location) = self.space.element_location(&window) else { let Some(initial_location) = self.space.element_location(&window) else {
return; return;
}; };
if !self.is_floating_window(&window) {
return;
}
pointer.set_grab( pointer.set_grab(
self, self,
MoveSurfaceGrab { MoveSurfaceGrab {
start_data, start_data,
window, window,
initial_location, initial_location,
finished: false,
}, },
serial, serial,
Focus::Clear, Focus::Clear,
@@ -143,6 +147,9 @@ impl XdgShellHandler for Ferese {
let Some(location) = self.space.element_location(&window) else { let Some(location) = self.space.element_location(&window) else {
return; return;
}; };
if !self.is_floating_window(&window) {
return;
}
let rect = Rectangle::new(location, window.geometry().size); let rect = Rectangle::new(location, window.geometry().size);
pointer.set_grab( pointer.set_grab(
self, self,
+29 -1
View File
@@ -21,7 +21,7 @@ use smithay::{
protocol::wl_surface::WlSurface, protocol::wl_surface::WlSurface,
}, },
}, },
utils::{Logical, Point}, utils::{Logical, Point, Size},
wayland::{ wayland::{
compositor::{CompositorClientState, CompositorState}, compositor::{CompositorClientState, CompositorState},
fractional_scale::FractionalScaleManagerState, fractional_scale::FractionalScaleManagerState,
@@ -440,6 +440,34 @@ impl Ferese {
} }
} }
pub fn is_floating_window(&self, window: &Window) -> bool {
self.window_ids
.get(window)
.and_then(|id| self.workspaces.placement(*id))
.is_some_and(|placement| matches!(placement, WindowPlacement::Floating { .. }))
}
pub fn set_floating_window_geometry(
&mut self,
window: &Window,
location: Point<i32, Logical>,
size: Size<i32, Logical>,
) {
let Some(id) = self.window_ids.get(window).copied() else {
return;
};
let rect = Rect::new(
location.x as f64,
location.y as f64,
size.w.max(1) as f64,
size.h.max(1) as f64,
);
if let Err(error) = self.workspaces.set_floating_rect(id, rect) {
tracing::error!(%error, ?id, "failed to update floating window geometry");
}
}
pub fn switch_workspace(&mut self, index: u32) { pub fn switch_workspace(&mut self, index: u32) {
let focus = match self.workspaces.switch_to_numeric(index) { let focus = match self.workspaces.switch_to_numeric(index) {
Ok(focus) => focus, Ok(focus) => focus,
+54
View File
@@ -127,6 +127,28 @@ impl WorkspaceSet {
self.placements.get(&window).copied() self.placements.get(&window).copied()
} }
pub fn set_floating_rect(
&mut self,
window: WindowId,
rect: Rect,
) -> Result<(), WorkspaceError> {
let placement = self
.placements
.get_mut(&window)
.ok_or(LayoutError::UnknownWindow(window))?;
if !matches!(placement, WindowPlacement::Floating { .. }) {
return Err(WorkspaceError::InvalidState("window is not floating"));
}
*placement = WindowPlacement::Floating {
rect: normalized_floating_rect(rect),
};
debug_assert!(self.validate().is_ok());
Ok(())
}
pub fn focus_window(&mut self, window: WindowId) -> Result<(), WorkspaceError> { pub fn focus_window(&mut self, window: WindowId) -> Result<(), WorkspaceError> {
if self.workspace_for_window(window) != Some(self.active) { if self.workspace_for_window(window) != Some(self.active) {
return Err(WorkspaceError::InvalidState( return Err(WorkspaceError::InvalidState(
@@ -621,6 +643,38 @@ mod tests {
assert!(workspaces.validate().is_ok()); assert!(workspaces.validate().is_ok());
} }
#[test]
fn floating_geometry_updates_are_authoritative_and_validated() {
let mut workspaces = WorkspaceSet::default();
workspaces
.insert_window(WindowId(1), Axis::Horizontal, 0.5)
.unwrap();
assert_eq!(
workspaces.set_floating_rect(WindowId(1), Rect::default()),
Err(WorkspaceError::InvalidState("window is not floating"))
);
workspaces
.toggle_floating(
WindowId(1),
Rect::new(10.0, 20.0, 640.0, 480.0),
Axis::Horizontal,
0.5,
)
.unwrap();
workspaces
.set_floating_rect(WindowId(1), Rect::new(30.0, 40.0, f64::NAN, -2.0))
.unwrap();
assert_eq!(
workspaces.placement(WindowId(1)),
Some(WindowPlacement::Floating {
rect: Rect::new(30.0, 40.0, 1.0, 1.0)
})
);
assert!(workspaces.validate().is_ok());
}
#[test] #[test]
fn fullscreen_preserves_underlying_placement() { fn fullscreen_preserves_underlying_placement() {
let mut workspaces = WorkspaceSet::default(); let mut workspaces = WorkspaceSet::default();