fix: synchronize floating window geometry
This commit is contained in:
1 parent
e1f1c1b9b6
commit
677e0ba5d1
5 files changed
+165
-5
No files matched your search
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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
@@ -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,
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
Reference in new issue
Block a user