Skip to content

Commit

Permalink
feat: Implement smooth resizing on X11 with _NET_WM_SYNC_REQUEST
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Speykious authored Jul 21, 2024
1 parent 73c01ff commit eef2848
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 3 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ x11rb = { version = "0.13.0", default-features = false, features = [
"dl-libxcb",
"randr",
"resource_manager",
"sync",
"xinput",
"xkb",
], optional = true }
Expand Down
1 change: 1 addition & 0 deletions src/changelog/unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -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()`.

Expand Down
2 changes: 2 additions & 0 deletions src/platform_impl/linux/x11/atoms.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
26 changes: 26 additions & 0 deletions src/platform_impl/linux/x11/event_processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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);
Expand Down
3 changes: 3 additions & 0 deletions src/platform_impl/linux/x11/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>>,
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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(),
Expand Down
37 changes: 34 additions & 3 deletions src/platform_impl/linux/x11/window.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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 _));
Expand Down

0 comments on commit eef2848

Please sign in to comment.