feat: Implement smooth resizing on X11 with _NET_WM_SYNC_REQUEST

Without smooth resizing, the window will appear to jitter when it is being
resized. This is because X11 completes the resize before the client gets a
chance to draw a new frame.

This is fixed by using the "sync" extension to ensure that the resize of the
X11 window is synchronized with the server.

Closes #2153
This commit is contained in:
Speykious 2024-07-21 18:39:43 +02:00 committed by GitHub
parent 73c01fff96
commit eef2848c98
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 67 additions and 3 deletions

View file

@ -262,6 +262,7 @@ x11rb = { version = "0.13.0", default-features = false, features = [
"dl-libxcb", "dl-libxcb",
"randr", "randr",
"resource_manager", "resource_manager",
"sync",
"xinput", "xinput",
"xkb", "xkb",
], optional = true } ], optional = true }

View file

@ -57,6 +57,7 @@ changelog entry.
to send specific data to be processed on the main thread. to send specific data to be processed on the main thread.
- Changed `EventLoopProxy::send_event` to `EventLoopProxy::wake_up`, it now - Changed `EventLoopProxy::send_event` to `EventLoopProxy::wake_up`, it now
only wakes up the loop. only wakes up the loop.
- On X11, implement smooth resizing through the sync extension API.
- `ApplicationHandler::create|destroy_surfaces()` was split off from - `ApplicationHandler::create|destroy_surfaces()` was split off from
`ApplicationHandler::resumed/suspended()`. `ApplicationHandler::resumed/suspended()`.

View file

@ -48,6 +48,8 @@ atom_manager! {
_NET_WM_NAME, _NET_WM_NAME,
_NET_WM_PID, _NET_WM_PID,
_NET_WM_PING, _NET_WM_PING,
_NET_WM_SYNC_REQUEST,
_NET_WM_SYNC_REQUEST_COUNTER,
_NET_WM_STATE, _NET_WM_STATE,
_NET_WM_STATE_ABOVE, _NET_WM_STATE_ABOVE,
_NET_WM_STATE_BELOW, _NET_WM_STATE_BELOW,

View file

@ -13,6 +13,7 @@ use x11_dl::xlib::{
XDestroyWindowEvent, XEvent, XExposeEvent, XKeyEvent, XMapEvent, XPropertyEvent, XDestroyWindowEvent, XEvent, XExposeEvent, XKeyEvent, XMapEvent, XPropertyEvent,
XReparentEvent, XSelectionEvent, XVisibilityEvent, XkbAnyEvent, XkbStateRec, XReparentEvent, XSelectionEvent, XVisibilityEvent, XkbAnyEvent, XkbStateRec,
}; };
use x11rb::protocol::sync::{ConnectionExt, Int64};
use x11rb::protocol::xinput; use x11rb::protocol::xinput;
use x11rb::protocol::xkb::ID as XkbId; use x11rb::protocol::xkb::ID as XkbId;
use x11rb::protocol::xproto::{self, ConnectionExt as _, ModMask}; use x11rb::protocol::xproto::{self, ConnectionExt as _, ModMask};
@ -429,6 +430,31 @@ impl EventProcessor {
return; return;
} }
if xev.data.get_long(0) as xproto::Atom == wt.net_wm_sync_request {
let sync_counter_id = match self
.with_window(xev.window as xproto::Window, |window| window.sync_counter_id())
{
Some(Some(sync_counter_id)) => sync_counter_id.get(),
_ => return,
};
#[cfg(target_pointer_width = "32")]
let (lo, hi) =
(bytemuck::cast::<c_long, u32>(xev.data.get_long(2)), xev.data.get_long(3));
#[cfg(not(target_pointer_width = "32"))]
let (lo, hi) = (
(xev.data.get_long(2) & 0xffffffff) as u32,
bytemuck::cast::<u32, i32>((xev.data.get_long(3) & 0xffffffff) as u32),
);
wt.xconn
.xcb_connection()
.sync_set_counter(sync_counter_id, Int64 { lo, hi })
.expect_then_ignore_error("Failed to set XSync counter.");
return;
}
if xev.message_type == atoms[XdndEnter] as c_ulong { if xev.message_type == atoms[XdndEnter] as c_ulong {
let source_window = xev.data.get_long(0) as xproto::Window; let source_window = xev.data.get_long(0) as xproto::Window;
let flags = xev.data.get_long(1); let flags = xev.data.get_long(1);

View file

@ -127,6 +127,7 @@ pub struct ActiveEventLoop {
xconn: Arc<XConnection>, xconn: Arc<XConnection>,
wm_delete_window: xproto::Atom, wm_delete_window: xproto::Atom,
net_wm_ping: xproto::Atom, net_wm_ping: xproto::Atom,
net_wm_sync_request: xproto::Atom,
ime_sender: ImeSender, ime_sender: ImeSender,
control_flow: Cell<ControlFlow>, control_flow: Cell<ControlFlow>,
exit: Cell<Option<i32>>, exit: Cell<Option<i32>>,
@ -167,6 +168,7 @@ impl EventLoop {
let wm_delete_window = atoms[WM_DELETE_WINDOW]; let wm_delete_window = atoms[WM_DELETE_WINDOW];
let net_wm_ping = atoms[_NET_WM_PING]; let net_wm_ping = atoms[_NET_WM_PING];
let net_wm_sync_request = atoms[_NET_WM_SYNC_REQUEST];
let dnd = Dnd::new(Arc::clone(&xconn)) let dnd = Dnd::new(Arc::clone(&xconn))
.expect("Failed to call XInternAtoms when initializing drag and drop"); .expect("Failed to call XInternAtoms when initializing drag and drop");
@ -292,6 +294,7 @@ impl EventLoop {
xconn, xconn,
wm_delete_window, wm_delete_window,
net_wm_ping, net_wm_ping,
net_wm_sync_request,
redraw_sender: WakeSender { redraw_sender: WakeSender {
sender: redraw_sender, // not used again so no clone sender: redraw_sender, // not used again so no clone
waker: waker.clone(), waker: waker.clone(),

View file

@ -1,14 +1,16 @@
use std::ffi::CString; use std::ffi::CString;
use std::mem::replace; use std::mem::replace;
use std::num::NonZeroU32;
use std::os::raw::*; use std::os::raw::*;
use std::path::Path; use std::path::Path;
use std::sync::{Arc, Mutex, MutexGuard}; use std::sync::{Arc, Mutex, MutexGuard};
use std::{cmp, env}; use std::{cmp, env};
use tracing::{debug, info, warn}; use tracing::{debug, info, warn};
use x11rb::connection::Connection; use x11rb::connection::{Connection, RequestConnection};
use x11rb::properties::{WmHints, WmSizeHints, WmSizeHintsSpecification}; use x11rb::properties::{WmHints, WmSizeHints, WmSizeHintsSpecification};
use x11rb::protocol::shape::SK; use x11rb::protocol::shape::SK;
use x11rb::protocol::sync::{ConnectionExt as _, Int64};
use x11rb::protocol::xfixes::{ConnectionExt, RegionWrapper}; use x11rb::protocol::xfixes::{ConnectionExt, RegionWrapper};
use x11rb::protocol::xproto::{self, ConnectionExt as _, Rectangle}; use x11rb::protocol::xproto::{self, ConnectionExt as _, Rectangle};
use x11rb::protocol::{randr, xinput}; use x11rb::protocol::{randr, xinput};
@ -116,6 +118,7 @@ pub struct UnownedWindow {
root: xproto::Window, // never changes root: xproto::Window, // never changes
#[allow(dead_code)] #[allow(dead_code)]
screen_id: i32, // never changes screen_id: i32, // never changes
sync_counter_id: Option<NonZeroU32>, // never changes
selected_cursor: Mutex<SelectedCursor>, selected_cursor: Mutex<SelectedCursor>,
cursor_grabbed_mode: Mutex<CursorGrabMode>, cursor_grabbed_mode: Mutex<CursorGrabMode>,
#[allow(clippy::mutex_atomic)] #[allow(clippy::mutex_atomic)]
@ -338,6 +341,7 @@ impl UnownedWindow {
visual, visual,
root, root,
screen_id, screen_id,
sync_counter_id: None,
selected_cursor: Default::default(), selected_cursor: Default::default(),
cursor_grabbed_mode: Mutex::new(CursorGrabMode::None), cursor_grabbed_mode: Mutex::new(CursorGrabMode::None),
cursor_visible: Mutex::new(true), cursor_visible: Mutex::new(true),
@ -468,21 +472,44 @@ impl UnownedWindow {
leap!(window.set_icon_inner(icon.inner)).ignore_error(); leap!(window.set_icon_inner(icon.inner)).ignore_error();
} }
// Opt into handling window close // Opt into handling window close and resize synchronization
let result = xconn.xcb_connection().change_property( let result = xconn.xcb_connection().change_property(
xproto::PropMode::REPLACE, xproto::PropMode::REPLACE,
window.xwindow, window.xwindow,
atoms[WM_PROTOCOLS], atoms[WM_PROTOCOLS],
xproto::AtomEnum::ATOM, xproto::AtomEnum::ATOM,
32, 32,
2, 3,
bytemuck::cast_slice::<xproto::Atom, u8>(&[ bytemuck::cast_slice::<xproto::Atom, u8>(&[
atoms[WM_DELETE_WINDOW], atoms[WM_DELETE_WINDOW],
atoms[_NET_WM_PING], atoms[_NET_WM_PING],
atoms[_NET_WM_SYNC_REQUEST],
]), ]),
); );
leap!(result).ignore_error(); leap!(result).ignore_error();
// Create a sync request counter
if leap!(xconn.xcb_connection().extension_information("SYNC")).is_some() {
let sync_counter_id = leap!(xconn.xcb_connection().generate_id());
window.sync_counter_id = NonZeroU32::new(sync_counter_id);
leap!(xconn
.xcb_connection()
.sync_create_counter(sync_counter_id, Int64::default()))
.ignore_error();
let result = xconn.xcb_connection().change_property(
xproto::PropMode::REPLACE,
window.xwindow,
atoms[_NET_WM_SYNC_REQUEST_COUNTER],
xproto::AtomEnum::CARDINAL,
32,
1,
bytemuck::cast_slice::<u32, u8>(&[sync_counter_id]),
);
leap!(result).ignore_error();
}
// Set visibility (map window) // Set visibility (map window)
if window_attrs.visible { if window_attrs.visible {
leap!(xconn.xcb_connection().map_window(window.xwindow)).ignore_error(); leap!(xconn.xcb_connection().map_window(window.xwindow)).ignore_error();
@ -1813,6 +1840,10 @@ impl UnownedWindow {
WindowId(self.xwindow as _) WindowId(self.xwindow as _)
} }
pub(super) fn sync_counter_id(&self) -> Option<NonZeroU32> {
self.sync_counter_id
}
#[inline] #[inline]
pub fn request_redraw(&self) { pub fn request_redraw(&self) {
self.redraw_sender.send(WindowId(self.xwindow as _)); self.redraw_sender.send(WindowId(self.xwindow as _));