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",
"randr",
"resource_manager",
"sync",
"xinput",
"xkb",
], optional = true }

View file

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

View file

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

View file

@ -13,6 +13,7 @@ use x11_dl::xlib::{
XDestroyWindowEvent, XEvent, XExposeEvent, XKeyEvent, XMapEvent, XPropertyEvent,
XReparentEvent, XSelectionEvent, XVisibilityEvent, XkbAnyEvent, XkbStateRec,
};
use x11rb::protocol::sync::{ConnectionExt, Int64};
use x11rb::protocol::xinput;
use x11rb::protocol::xkb::ID as XkbId;
use x11rb::protocol::xproto::{self, ConnectionExt as _, ModMask};
@ -429,6 +430,31 @@ impl EventProcessor {
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 {
let source_window = xev.data.get_long(0) as xproto::Window;
let flags = xev.data.get_long(1);

View file

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

View file

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