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:
parent
73c01fff96
commit
eef2848c98
6 changed files with 67 additions and 3 deletions
|
|
@ -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()`.
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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 _));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue