mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-23 02:05:33 +07:00
Implement window resize throttling
This commit is contained in:
@@ -1368,6 +1368,8 @@ pub struct DebugConfig {
|
|||||||
pub render_drm_device: Option<PathBuf>,
|
pub render_drm_device: Option<PathBuf>,
|
||||||
#[knuffel(child)]
|
#[knuffel(child)]
|
||||||
pub emulate_zero_presentation_time: bool,
|
pub emulate_zero_presentation_time: bool,
|
||||||
|
#[knuffel(child)]
|
||||||
|
pub disable_resize_throttling: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(knuffel::DecodeScalar, Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(knuffel::DecodeScalar, Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ use std::cmp::{max, min};
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use niri::layout::{
|
use niri::layout::{
|
||||||
InteractiveResizeData, LayoutElement, LayoutElementRenderElement, LayoutElementRenderSnapshot,
|
ConfigureIntent, InteractiveResizeData, LayoutElement, LayoutElementRenderElement,
|
||||||
|
LayoutElementRenderSnapshot,
|
||||||
};
|
};
|
||||||
use niri::render_helpers::renderer::NiriRenderer;
|
use niri::render_helpers::renderer::NiriRenderer;
|
||||||
use niri::render_helpers::solid_color::{SolidColorBuffer, SolidColorRenderElement};
|
use niri::render_helpers::solid_color::{SolidColorBuffer, SolidColorRenderElement};
|
||||||
@@ -215,6 +216,10 @@ impl LayoutElement for TestWindow {
|
|||||||
|
|
||||||
fn set_bounds(&self, _bounds: Size<i32, Logical>) {}
|
fn set_bounds(&self, _bounds: Size<i32, Logical>) {}
|
||||||
|
|
||||||
|
fn configure_intent(&self) -> ConfigureIntent {
|
||||||
|
ConfigureIntent::CanSend
|
||||||
|
}
|
||||||
|
|
||||||
fn send_pending_configure(&mut self) {}
|
fn send_pending_configure(&mut self) {}
|
||||||
|
|
||||||
fn is_fullscreen(&self) -> bool {
|
fn is_fullscreen(&self) -> bool {
|
||||||
|
|||||||
@@ -80,6 +80,19 @@ pub struct InteractiveResizeData {
|
|||||||
pub edges: ResizeEdge,
|
pub edges: ResizeEdge,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum ConfigureIntent {
|
||||||
|
/// A configure is not needed (no changes to server pending state).
|
||||||
|
NotNeeded,
|
||||||
|
/// A configure is throttled (due to resizing too fast for example).
|
||||||
|
Throttled,
|
||||||
|
/// Can send the configure if it isn't throttled externally (only size changed).
|
||||||
|
CanSend,
|
||||||
|
/// Should send the configure regardless of external throttling (something other than size
|
||||||
|
/// changed).
|
||||||
|
ShouldSend,
|
||||||
|
}
|
||||||
|
|
||||||
pub trait LayoutElement {
|
pub trait LayoutElement {
|
||||||
/// Type that can be used as a unique ID of this element.
|
/// Type that can be used as a unique ID of this element.
|
||||||
type Id: PartialEq + std::fmt::Debug;
|
type Id: PartialEq + std::fmt::Debug;
|
||||||
@@ -154,6 +167,7 @@ pub trait LayoutElement {
|
|||||||
fn set_active_in_column(&mut self, active: bool);
|
fn set_active_in_column(&mut self, active: bool);
|
||||||
fn set_bounds(&self, bounds: Size<i32, Logical>);
|
fn set_bounds(&self, bounds: Size<i32, Logical>);
|
||||||
|
|
||||||
|
fn configure_intent(&self) -> ConfigureIntent;
|
||||||
fn send_pending_configure(&mut self);
|
fn send_pending_configure(&mut self);
|
||||||
|
|
||||||
/// Whether the element is currently fullscreen.
|
/// Whether the element is currently fullscreen.
|
||||||
@@ -220,6 +234,9 @@ pub struct Options {
|
|||||||
/// Initial width for new columns.
|
/// Initial width for new columns.
|
||||||
pub default_width: Option<ColumnWidth>,
|
pub default_width: Option<ColumnWidth>,
|
||||||
pub animations: niri_config::Animations,
|
pub animations: niri_config::Animations,
|
||||||
|
|
||||||
|
// Debug flags.
|
||||||
|
pub disable_resize_throttling: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Options {
|
impl Default for Options {
|
||||||
@@ -237,6 +254,7 @@ impl Default for Options {
|
|||||||
],
|
],
|
||||||
default_width: None,
|
default_width: None,
|
||||||
animations: Default::default(),
|
animations: Default::default(),
|
||||||
|
disable_resize_throttling: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -273,6 +291,7 @@ impl Options {
|
|||||||
preset_widths,
|
preset_widths,
|
||||||
default_width,
|
default_width,
|
||||||
animations: config.animations.clone(),
|
animations: config.animations.clone(),
|
||||||
|
disable_resize_throttling: config.debug.disable_resize_throttling,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2654,6 +2673,10 @@ mod tests {
|
|||||||
|
|
||||||
fn set_bounds(&self, _bounds: Size<i32, Logical>) {}
|
fn set_bounds(&self, _bounds: Size<i32, Logical>) {}
|
||||||
|
|
||||||
|
fn configure_intent(&self) -> ConfigureIntent {
|
||||||
|
ConfigureIntent::CanSend
|
||||||
|
}
|
||||||
|
|
||||||
fn send_pending_configure(&mut self) {}
|
fn send_pending_configure(&mut self) {}
|
||||||
|
|
||||||
fn set_active_in_column(&mut self, _active: bool) {}
|
fn set_active_in_column(&mut self, _active: bool) {}
|
||||||
|
|||||||
+10
-2
@@ -15,7 +15,7 @@ use smithay::utils::{Logical, Point, Rectangle, Scale, Serial, Size, Transform};
|
|||||||
|
|
||||||
use super::closing_window::{ClosingWindow, ClosingWindowRenderElement};
|
use super::closing_window::{ClosingWindow, ClosingWindowRenderElement};
|
||||||
use super::tile::{Tile, TileRenderElement};
|
use super::tile::{Tile, TileRenderElement};
|
||||||
use super::{InteractiveResizeData, LayoutElement, Options};
|
use super::{ConfigureIntent, InteractiveResizeData, LayoutElement, Options};
|
||||||
use crate::animation::Animation;
|
use crate::animation::Animation;
|
||||||
use crate::input::swipe_tracker::SwipeTracker;
|
use crate::input::swipe_tracker::SwipeTracker;
|
||||||
use crate::niri_render_elements;
|
use crate::niri_render_elements;
|
||||||
@@ -2759,7 +2759,15 @@ impl<W: LayoutElement> Workspace<W> {
|
|||||||
);
|
);
|
||||||
win.set_bounds(bounds);
|
win.set_bounds(bounds);
|
||||||
|
|
||||||
win.send_pending_configure();
|
let intent = win.configure_intent();
|
||||||
|
|
||||||
|
if matches!(
|
||||||
|
intent,
|
||||||
|
ConfigureIntent::CanSend | ConfigureIntent::ShouldSend
|
||||||
|
) {
|
||||||
|
win.send_pending_configure();
|
||||||
|
}
|
||||||
|
|
||||||
win.refresh();
|
win.refresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+60
-2
@@ -12,14 +12,16 @@ use smithay::output::{self, Output};
|
|||||||
use smithay::reexports::wayland_protocols::xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1;
|
use smithay::reexports::wayland_protocols::xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1;
|
||||||
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel;
|
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel;
|
||||||
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
||||||
|
use smithay::reexports::wayland_server::Resource as _;
|
||||||
use smithay::utils::{Logical, Point, Rectangle, Scale, Serial, Size, Transform};
|
use smithay::utils::{Logical, Point, Rectangle, Scale, Serial, Size, Transform};
|
||||||
use smithay::wayland::compositor::{remove_pre_commit_hook, with_states, HookId};
|
use smithay::wayland::compositor::{remove_pre_commit_hook, with_states, HookId};
|
||||||
use smithay::wayland::shell::xdg::{SurfaceCachedState, ToplevelSurface};
|
use smithay::wayland::shell::xdg::{SurfaceCachedState, ToplevelSurface, XdgToplevelSurfaceData};
|
||||||
|
|
||||||
use super::{ResolvedWindowRules, WindowRef};
|
use super::{ResolvedWindowRules, WindowRef};
|
||||||
use crate::handlers::KdeDecorationsModeState;
|
use crate::handlers::KdeDecorationsModeState;
|
||||||
use crate::layout::{
|
use crate::layout::{
|
||||||
InteractiveResizeData, LayoutElement, LayoutElementRenderElement, LayoutElementRenderSnapshot,
|
ConfigureIntent, InteractiveResizeData, LayoutElement, LayoutElementRenderElement,
|
||||||
|
LayoutElementRenderSnapshot,
|
||||||
};
|
};
|
||||||
use crate::niri::WindowOffscreenId;
|
use crate::niri::WindowOffscreenId;
|
||||||
use crate::niri_render_elements;
|
use crate::niri_render_elements;
|
||||||
@@ -568,6 +570,62 @@ impl LayoutElement for Mapped {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn configure_intent(&self) -> ConfigureIntent {
|
||||||
|
let _span =
|
||||||
|
trace_span!("configure_intent", surface = ?self.toplevel().wl_surface().id()).entered();
|
||||||
|
|
||||||
|
with_states(self.toplevel().wl_surface(), |states| {
|
||||||
|
let attributes = states
|
||||||
|
.data_map
|
||||||
|
.get::<XdgToplevelSurfaceData>()
|
||||||
|
.unwrap()
|
||||||
|
.lock()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
if let Some(server_pending) = &attributes.server_pending {
|
||||||
|
let current_server = attributes.current_server_state();
|
||||||
|
if server_pending != current_server {
|
||||||
|
// Something changed. Check if the only difference is the size, and if the
|
||||||
|
// current server size matches the current committed size.
|
||||||
|
let mut current_server_same_size = current_server.clone();
|
||||||
|
current_server_same_size.size = server_pending.size;
|
||||||
|
if current_server_same_size == *server_pending {
|
||||||
|
// Only the size changed. Check if the window committed our previous size
|
||||||
|
// request.
|
||||||
|
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
|
||||||
|
);
|
||||||
|
ConfigureIntent::CanSend
|
||||||
|
} else {
|
||||||
|
// The window had not committed for our previous size change yet. Since
|
||||||
|
// nothing else changed, do not send the new size request 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");
|
||||||
|
ConfigureIntent::Throttled
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Something else changed other than the size; send it.
|
||||||
|
trace!("something changed other than the size");
|
||||||
|
ConfigureIntent::ShouldSend
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Nothing changed since the last configure.
|
||||||
|
ConfigureIntent::NotNeeded
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Nothing changed since the last configure.
|
||||||
|
ConfigureIntent::NotNeeded
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn send_pending_configure(&mut self) {
|
fn send_pending_configure(&mut self) {
|
||||||
if let Some(serial) = self.toplevel().send_pending_configure() {
|
if let Some(serial) = self.toplevel().send_pending_configure() {
|
||||||
if self.animate_next_configure {
|
if self.animate_next_configure {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ debug {
|
|||||||
dbus-interfaces-in-non-session-instances
|
dbus-interfaces-in-non-session-instances
|
||||||
wait-for-frame-completion-before-queueing
|
wait-for-frame-completion-before-queueing
|
||||||
emulate-zero-presentation-time
|
emulate-zero-presentation-time
|
||||||
|
disable-resize-throttling
|
||||||
}
|
}
|
||||||
|
|
||||||
binds {
|
binds {
|
||||||
@@ -128,6 +129,23 @@ debug {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### `disable-resize-throttling`
|
||||||
|
|
||||||
|
<sup>Since: 0.1.9</sup>
|
||||||
|
|
||||||
|
Disable throttling resize events sent to windows.
|
||||||
|
|
||||||
|
By default, when resizing quickly (e.g. interactively), a window will only receive the next size once it has made a commit for the previously requested size.
|
||||||
|
This is required for resize transactions to work properly, and it also helps certain clients which don't batch incoming resizes from the compositor.
|
||||||
|
|
||||||
|
Disabling resize throttling will send resizes to windows as fast as possible, which is potentially very fast (for example, on a 1000 Hz mouse).
|
||||||
|
|
||||||
|
```kdl
|
||||||
|
debug {
|
||||||
|
disable-resize-throttling
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Key Bindings
|
### Key Bindings
|
||||||
|
|
||||||
These are not debug options, but rather key bindings.
|
These are not debug options, but rather key bindings.
|
||||||
|
|||||||
Reference in New Issue
Block a user