diff --git a/src/shell/element/mod.rs b/src/shell/element/mod.rs index e74e7aa5..e3da4d5e 100644 --- a/src/shell/element/mod.rs +++ b/src/shell/element/mod.rs @@ -489,6 +489,14 @@ impl CosmicMapped { } } + pub fn latest_size_committed(&self) -> bool { + match &self.element { + CosmicMappedInternal::Stack(s) => s.surfaces().any(|s| s.latest_size_committed()), + CosmicMappedInternal::Window(w) => w.surface().latest_size_committed(), + _ => unreachable!(), + } + } + pub fn configure(&self) -> Option { match &self.element { CosmicMappedInternal::Stack(s) => { diff --git a/src/shell/element/surface.rs b/src/shell/element/surface.rs index 1c8e305b..05b1da45 100644 --- a/src/shell/element/surface.rs +++ b/src/shell/element/surface.rs @@ -45,6 +45,7 @@ use smithay::{ }, xwayland::{xwm::X11Relatable, X11Surface}, }; +use tracing::trace; use crate::{ state::{State, SurfaceDmabufFeedback}, @@ -499,6 +500,60 @@ impl CosmicSurface { } } + pub fn serial_past(&self, serial: &Serial) -> bool { + match self.0.underlying_surface() { + WindowSurface::Wayland(toplevel) => with_states(toplevel.wl_surface(), |states| { + let attrs = states + .data_map + .get::() + .unwrap() + .lock() + .unwrap(); + attrs + .current_serial + .as_ref() + .map(|s| s >= serial) + .unwrap_or(false) + }), + WindowSurface::X11(_surface) => true, + } + } + + pub fn latest_size_committed(&self) -> bool { + match self.0.underlying_surface() { + WindowSurface::Wayland(toplevel) => { + with_states(toplevel.wl_surface(), |states| { + let attributes = states + .data_map + .get::() + .unwrap() + .lock() + .unwrap(); + + let current_server = attributes.current_server_state(); + if attributes.current.size == current_server.size { + // The window had committed for our previous size change, so we can + // change the size again. + trace!( + "current size matches server size: {:?}", + attributes.current.size + ); + true + } else { + // The window had not committed for our previous size change yet. + // This throttling is done because some clients do not batch size requests, + // leading to bad behavior with very fast input devices (i.e. a 1000 Hz + // mouse). This throttling also helps interactive resize transactions + // preserve visual consistency. + trace!("throttling resize"); + false + } + }) + } + WindowSurface::X11(_) => true, + } + } + pub fn force_configure(&self) -> Option { match self.0.underlying_surface() { WindowSurface::Wayland(toplevel) => Some(toplevel.send_configure()), diff --git a/src/shell/layout/floating/grabs/resize.rs b/src/shell/layout/floating/grabs/resize.rs index 6163630a..9e2a0dd9 100644 --- a/src/shell/layout/floating/grabs/resize.rs +++ b/src/shell/layout/floating/grabs/resize.rs @@ -28,6 +28,7 @@ use smithay::{ }, Seat, }, + output::Output, utils::{IsAlive, Logical, Point, Rectangle, Serial, Size}, }; @@ -56,6 +57,7 @@ pub struct ResizeSurfaceGrab { seat: Seat, window: CosmicMapped, edges: ResizeEdge, + output: Output, initial_window_size: Size, last_window_size: Size, release: ReleaseMode, @@ -111,16 +113,14 @@ impl ResizeSurfaceGrab { self.last_window_size = (new_window_width, new_window_height).into(); + let mut win_loc = location.as_local().to_global(&self.output).to_i32_round(); + win_loc.y += self.window.ssd_height(false).unwrap_or(0); self.window.set_resizing(true); - self.window.set_geometry(Rectangle::new( - if let Some(s) = self.window.active_window().x11_surface() { - s.geometry().loc.as_global() - } else { - (0, 0).into() - }, - self.last_window_size.as_global(), - )); - self.window.configure(); + self.window + .set_geometry(Rectangle::new(win_loc, self.last_window_size.as_global())); + if self.window.latest_size_committed() { + self.window.configure(); + } false } @@ -374,6 +374,7 @@ impl ResizeSurfaceGrab { start_data: GrabStartData, mapped: CosmicMapped, edges: ResizeEdge, + output: Output, initial_window_location: Point, initial_window_size: Size, seat: &Seat, @@ -412,6 +413,7 @@ impl ResizeSurfaceGrab { seat: seat.clone(), window: mapped, edges, + output, initial_window_size, last_window_size: initial_window_size, release, @@ -523,14 +525,6 @@ impl ResizeSurfaceGrab { } self.window.set_resizing(false); - self.window.set_geometry(Rectangle::new( - if let Some(x11_surface) = self.window.active_window().x11_surface() { - x11_surface.geometry().loc.as_global() - } else { - (0, 0).into() - }, - self.last_window_size.as_global(), - )); self.window.configure(); let mut resize_state = self.window.resize_state.lock().unwrap(); diff --git a/src/shell/layout/floating/mod.rs b/src/shell/layout/floating/mod.rs index 97dc2b7e..21175a1f 100644 --- a/src/shell/layout/floating/mod.rs +++ b/src/shell/layout/floating/mod.rs @@ -899,6 +899,7 @@ impl FloatingLayout { start_data, mapped.clone(), edges, + self.space.outputs().next().cloned().unwrap(), location, size, seat, @@ -990,7 +991,9 @@ impl FloatingLayout { geo.as_local() .to_global(self.space.outputs().next().unwrap()), ); - mapped.configure(); + if mapped.latest_size_committed() { + mapped.configure(); + } true } diff --git a/src/shell/layout/tiling/blocker.rs b/src/shell/layout/tiling/blocker.rs index badbd075..decb933f 100644 --- a/src/shell/layout/tiling/blocker.rs +++ b/src/shell/layout/tiling/blocker.rs @@ -9,25 +9,18 @@ use smithay::{ }; use std::{ collections::HashMap, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }, time::{Duration, Instant}, }; #[derive(Debug, Clone)] pub struct TilingBlocker { pub necessary_acks: Vec<(CosmicSurface, Serial)>, - ready: Arc, - signaled: Arc, start: Instant, } impl Blocker for TilingBlocker { fn state(&self) -> BlockerState { - self.signaled.store(true, Ordering::SeqCst); - if self.ready.load(Ordering::SeqCst) { + if self.is_ready() { BlockerState::Released } else { BlockerState::Pending @@ -39,8 +32,6 @@ impl TilingBlocker { pub fn new(configures: impl IntoIterator) -> Self { TilingBlocker { necessary_acks: configures.into_iter().collect(), - ready: Arc::new(AtomicBool::new(false)), - signaled: Arc::new(AtomicBool::new(false)), start: Instant::now(), } } @@ -53,14 +44,15 @@ impl TilingBlocker { .all(|(surf, serial)| !surf.alive() || surf.serial_acked(serial)) } - pub fn is_signaled(&self) -> bool { - self.signaled.load(Ordering::SeqCst) - || !self.necessary_acks.iter().any(|(surf, _)| surf.alive()) + pub fn is_processed(&self) -> bool { + Instant::now().duration_since(self.start) >= Duration::from_millis(500) + || self + .necessary_acks + .iter() + .all(|(surf, serial)| !surf.alive() || surf.serial_past(serial)) } - #[must_use] - pub fn signal_ready(&self) -> HashMap { - self.ready.swap(true, Ordering::SeqCst); + pub fn clients(&self) -> HashMap { self.necessary_acks .iter() .flat_map(|(surface, _)| surface.wl_surface().and_then(|s| s.client())) diff --git a/src/shell/layout/tiling/grabs/resize.rs b/src/shell/layout/tiling/grabs/resize.rs index eacd443d..899e92af 100644 --- a/src/shell/layout/tiling/grabs/resize.rs +++ b/src/shell/layout/tiling/grabs/resize.rs @@ -217,8 +217,14 @@ impl ResizeForkGrab { impl ResizeForkGrab { // Returns `true` if grab should be unset - fn update_location(&mut self, data: &mut State, location: Point) -> bool { + fn update_location( + &mut self, + data: &mut State, + location: Point, + force: bool, + ) -> bool { let delta = location - self.last_loc.as_logical(); + self.last_loc = location.as_global(); if let Some(output) = self.output.upgrade() { let mut shell = data.common.shell.write().unwrap(); @@ -228,7 +234,7 @@ impl ResizeForkGrab { let tiling_layer = &mut workspace.tiling_layer; let gaps = tiling_layer.gaps(); - let tree = &mut tiling_layer.queue.trees.back_mut().unwrap().0; + let mut tree = tiling_layer.queue.trees.back().unwrap().0.copy_clone(); match &mut self.old_tree { Some(old_tree) => { // it would be so nice to just `zip` here, but `zip` just returns `None` once either returns `None`. @@ -258,7 +264,7 @@ impl ResizeForkGrab { *old_tree = tree.copy_clone(); self.accumulated_delta = 0.0; } else { - *tree = old_tree.copy_clone(); + tree = old_tree.copy_clone(); } } x @ None => { @@ -284,6 +290,10 @@ impl ResizeForkGrab { return true; }; + if self.accumulated_delta.round() as i32 == 0 { + return false; + } + match tree.get_mut(&self.node).unwrap().data_mut() { Data::Group { sizes, orientation, .. @@ -319,9 +329,18 @@ impl ResizeForkGrab { _ => unreachable!(), } - self.last_loc = location.as_global(); - let blocker = TilingLayout::update_positions(&output, tree, gaps); - tiling_layer.pending_blockers.extend(blocker); + let should_configure = force + || tree + .traverse_pre_order(&self.node) + .unwrap() + .all(|node| match node.data() { + Data::Mapped { mapped, .. } => mapped.latest_size_committed(), + _ => true, + }); + if should_configure { + let blocker = TilingLayout::update_positions(&output, &mut tree, gaps); + tiling_layer.queue.push_tree(tree, None, blocker); + } } else { return true; } @@ -348,7 +367,7 @@ impl PointerGrab for ResizeForkGrab { // While the grab is active, no client has pointer focus handle.motion(data, None, event); - if self.update_location(data, event.location) { + if self.update_location(data, event.location, false) { handle.unset_grab(self, data, event.serial, event.time, true); } } @@ -477,7 +496,9 @@ impl PointerGrab for ResizeForkGrab { } } - fn unset(&mut self, _data: &mut State) {} + fn unset(&mut self, data: &mut State) { + self.update_location(data, self.last_loc.as_logical(), true); + } } impl TouchGrab for ResizeForkGrab { @@ -515,7 +536,7 @@ impl TouchGrab for ResizeForkGrab { seq: Serial, ) { if event.slot == >::start_data(self).slot { - if self.update_location(data, event.location) { + if self.update_location(data, event.location, false) { handle.unset_grab(self, data); } } @@ -558,5 +579,7 @@ impl TouchGrab for ResizeForkGrab { handle.orientation(data, event, seq) } - fn unset(&mut self, _data: &mut State) {} + fn unset(&mut self, data: &mut State) { + self.update_location(data, self.last_loc.as_logical(), true); + } } diff --git a/src/shell/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs index fbe8f84b..e3b8fdb2 100644 --- a/src/shell/layout/tiling/mod.rs +++ b/src/shell/layout/tiling/mod.rs @@ -130,7 +130,6 @@ impl TreeQueue { pub struct TilingLayout { output: Output, queue: TreeQueue, - pending_blockers: Vec, placeholder_id: Id, swapping_stack_surface_id: Id, last_overview_hover: Option<(Option, TargetZone)>, @@ -353,7 +352,6 @@ impl TilingLayout { animation_start: None, }, output: output.clone(), - pending_blockers: Vec::new(), placeholder_id: Id::new(), swapping_stack_surface_id: Id::new(), last_overview_hover: None, @@ -2345,8 +2343,20 @@ impl TilingLayout { pub fn update_animation_state(&mut self) -> HashMap { let mut clients = HashMap::new(); - for blocker in self.pending_blockers.drain(..) { - clients.extend(blocker.signal_ready()); + let mut ready_trees = 0; + for (_, _, blocker) in self.queue.trees.iter().skip(1) { + if let Some(blocker) = blocker.as_ref() { + if blocker.is_processed() { + ready_trees += 1; + } + if blocker.is_ready() { + clients.extend(blocker.clients()); + continue; + } + break; + } else { + ready_trees += 1; + } } if let Some(start) = self.queue.animation_start { @@ -2361,6 +2371,7 @@ impl TilingLayout { { let _ = self.queue.animation_start.take(); let _ = self.queue.trees.pop_front(); + ready_trees -= 1; let front = self.queue.trees.front_mut().unwrap(); if let Some(root_id) = front.0.root_node_id() { for node in front @@ -2383,28 +2394,12 @@ impl TilingLayout { } } - let ready_trees = self - .queue - .trees - .iter() - .skip(1) - .take_while(|(_, _, blocker)| { - blocker - .as_ref() - .map(|blocker| blocker.is_ready() && blocker.is_signaled()) - .unwrap_or(true) - }) - .count(); - // merge let other_duration = if ready_trees > 1 { self.queue .trees .drain(1..ready_trees) - .fold(None, |res, (_, duration, blocker)| { - if let Some(blocker) = blocker { - clients.extend(blocker.signal_ready()); - } + .fold(None, |res, (_, duration, _)| { Some( res.map(|old_duration: Duration| old_duration.max(duration)) .unwrap_or(duration), @@ -2416,13 +2411,10 @@ impl TilingLayout { // start if ready_trees > 0 { - let (_, duration, blocker) = self.queue.trees.get_mut(1).unwrap(); + let (_, duration, _) = self.queue.trees.get_mut(1).unwrap(); *duration = other_duration .map(|other| other.max(*duration)) .unwrap_or(*duration); - if let Some(blocker) = blocker { - clients.extend(blocker.signal_ready()); - } self.queue.animation_start = Some(Instant::now()); } @@ -2589,8 +2581,18 @@ impl TilingLayout { } _ => unreachable!(), } - let blocker = TilingLayout::update_positions(&self.output, &mut tree, gaps); - self.queue.push_tree(tree, None, blocker); + + let should_configure = + tree.traverse_pre_order(&group_id) + .unwrap() + .all(|node| match node.data() { + Data::Mapped { mapped, .. } => mapped.latest_size_committed(), + _ => true, + }); + if should_configure { + let blocker = TilingLayout::update_positions(&self.output, &mut tree, gaps); + self.queue.push_tree(tree, None, blocker); + } return true; } @@ -3965,9 +3967,17 @@ impl TilingLayout { .then(|| &self.queue.trees.front().unwrap().0); let percentage = if let Some(animation_start) = self.queue.animation_start { - let percentage = Instant::now().duration_since(animation_start).as_millis() as f32 - / duration.as_millis() as f32; - ease(EaseInOutCubic, 0.0, 1.0, percentage) + if *duration == Duration::ZERO { + 1.0 + } else { + let now = Instant::now(); + let total = duration.as_millis() as f32; + let since = now.duration_since(animation_start).as_millis() as f32; + let percentage = since / total; + debug_assert!(!percentage.is_nan()); + debug_assert!(!percentage.is_infinite()); + ease(EaseInOutCubic, 0.0, 1.0, percentage) + } } else { 1.0 }; diff --git a/src/wayland/handlers/toplevel_info.rs b/src/wayland/handlers/toplevel_info.rs index 18b16a07..37033f4f 100644 --- a/src/wayland/handlers/toplevel_info.rs +++ b/src/wayland/handlers/toplevel_info.rs @@ -48,7 +48,11 @@ impl Window for CosmicSurface { } fn is_sticky(&self) -> bool { - CosmicSurface::is_sticky(&self) + CosmicSurface::is_sticky(self) + } + + fn is_resizing(&self) -> bool { + CosmicSurface::is_resizing(self, true).unwrap_or(false) } fn global_geometry(&self) -> Option> { diff --git a/src/wayland/protocols/toplevel_info.rs b/src/wayland/protocols/toplevel_info.rs index 0fe1ec1b..b52ae354 100644 --- a/src/wayland/protocols/toplevel_info.rs +++ b/src/wayland/protocols/toplevel_info.rs @@ -33,6 +33,7 @@ pub trait Window: IsAlive + Clone + PartialEq + Send { fn is_fullscreen(&self) -> bool; fn is_minimized(&self) -> bool; fn is_sticky(&self) -> bool; + fn is_resizing(&self) -> bool; fn global_geometry(&self) -> Option>; fn user_data(&self) -> &UserDataMap; } @@ -536,12 +537,14 @@ where changed = true; } - let geometry = window.global_geometry(); let mut geometry_changed = false; - if handle_state.geometry != geometry { - handle_state.geometry = geometry; - changed = true; - geometry_changed = true; + if !window.is_resizing() { + let geometry = window.global_geometry(); + if handle_state.geometry != geometry { + handle_state.geometry = geometry; + changed = true; + geometry_changed = true; + } } if let Ok(client) = dh.get_client(instance.id()) {