diff --git a/Cargo.toml b/Cargo.toml index 4ffa20d3..b265cf4f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -262,6 +262,7 @@ x11rb = { version = "0.13.0", default-features = false, features = [ "dl-libxcb", "randr", "resource_manager", + "sync", "xinput", "xkb", ], optional = true } diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 5d4dc5bd..9af6bc63 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -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()`. diff --git a/src/platform_impl/linux/x11/atoms.rs b/src/platform_impl/linux/x11/atoms.rs index da98be09..59f271d5 100644 --- a/src/platform_impl/linux/x11/atoms.rs +++ b/src/platform_impl/linux/x11/atoms.rs @@ -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, diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index 424464ce..b0aadc35 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -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::(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::((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); diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index cd4b4b9e..6b95e45c 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -127,6 +127,7 @@ pub struct ActiveEventLoop { xconn: Arc, wm_delete_window: xproto::Atom, net_wm_ping: xproto::Atom, + net_wm_sync_request: xproto::Atom, ime_sender: ImeSender, control_flow: Cell, exit: Cell>, @@ -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(), diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index b5803afa..bcd9d5f4 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -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, // never changes selected_cursor: Mutex, cursor_grabbed_mode: Mutex, #[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::(&[ 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::(&[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 { + self.sync_counter_id + } + #[inline] pub fn request_redraw(&self) { self.redraw_sender.send(WindowId(self.xwindow as _));