diff --git a/src/xwayland/xwm/mod.rs b/src/xwayland/xwm/mod.rs index 0d3cf3028f3a..e2120f65e6a8 100644 --- a/src/xwayland/xwm/mod.rs +++ b/src/xwayland/xwm/mod.rs @@ -136,7 +136,7 @@ use crate::{ }, }; use atomic_float::AtomicF64; -use calloop::{Interest, LoopHandle, Mode, PostAction, generic::Generic}; +use calloop::{Interest, LoopHandle, Mode, PostAction, generic::Generic, ping}; use rustix::fs::OFlags; use std::{ cell::RefCell, @@ -147,7 +147,10 @@ use std::{ io::{AsFd, OwnedFd}, net::UnixStream, }, - sync::{Arc, Weak, atomic::Ordering}, + sync::{ + Arc, Weak, + atomic::{AtomicBool, Ordering}, + }, }; use tracing::{debug, debug_span, info, trace, warn}; use wayland_server::{DisplayHandle, Resource}; @@ -165,8 +168,9 @@ use x11rb::{ xproto::{ AtomEnum, CONFIGURE_NOTIFY_EVENT, ChangeWindowAttributesAux, Colormap, ColormapAlloc, ConfigWindow, ConfigureNotifyEvent, ConfigureWindowAux, ConnectionExt, CreateGCAux, - CreateWindowAux, CursorWrapper, EventMask, FontWrapper, GcontextWrapper, ImageFormat, - PixmapWrapper, PropMode, Property, QueryExtensionReply, Screen, StackMode, Visualid, WindowClass, + CreateWindowAux, CursorWrapper, EventMask, FontWrapper, GcontextWrapper, ImageFormat, InputFocus, + NotifyDetail, PixmapWrapper, PropMode, Property, QueryExtensionReply, Screen, StackMode, + Visualid, WindowClass, }, }, rust_connection::{ConnectionError, DefaultStream, RustConnection}, @@ -567,6 +571,8 @@ pub struct X11Wm { is_showing_desktop: bool, + pub(super) focus_release: FocusReleaseHandle, + span: tracing::Span, } @@ -576,6 +582,47 @@ impl Drop for X11Wm { } } +#[derive(Debug, Clone)] +pub(super) struct FocusReleaseHandle { + pending: Arc, + signal: ping::Ping, + conn: Weak, +} + +impl FocusReleaseHandle { + fn new(conn: &Arc) -> std::io::Result<(Self, ping::PingSource)> { + let (signal, source) = ping::make_ping()?; + Ok(( + Self { + pending: Arc::new(AtomicBool::new(false)), + signal, + conn: Arc::downgrade(conn), + }, + source, + )) + } + + fn schedule(&self) { + self.pending.store(true, Ordering::Release); + self.signal.ping(); + } + + fn cancel(&self) { + self.pending.store(false, Ordering::Release); + } + + fn dispatch(&self) { + if !self.pending.swap(false, Ordering::AcqRel) { + return; + } + let Some(conn) = self.conn.upgrade() else { return }; + if let Err(err) = conn.set_input_focus(InputFocus::NONE, x11rb::NONE, x11rb::CURRENT_TIME) { + warn!("Unable to release X11 keyboard focus: {}", err); + } + let _ = conn.flush(); + } +} + /// Edge values for resizing /// // These values are used to indicate which edge of a surface is being dragged in a resize operation. @@ -890,6 +937,12 @@ impl X11Wm { let dnd = XWmDnd::new(&conn, &screen, &atoms)?; let wm_window = OwnedX11Window::new(win, &conn); + let (focus_release, focus_release_source) = FocusReleaseHandle::new(&conn)?; + { + let release = focus_release.clone(); + handle.insert_source(focus_release_source, move |_, _, _| release.dispatch())?; + } + drop(_guard); let wm = Self { id, @@ -911,6 +964,7 @@ impl X11Wm { client_list: Vec::new(), client_list_stacking: Vec::new(), is_showing_desktop: false, + focus_release, span, }; @@ -2227,7 +2281,7 @@ where )?; } } - Event::FocusOut(n) => { + Event::FocusOut(n) if n.detail == NotifyDetail::NONE => { if xwm.windows.iter().any(|x| x.window_id() == n.event) { conn.change_property32( PropMode::REPLACE, diff --git a/src/xwayland/xwm/surface.rs b/src/xwayland/xwm/surface.rs index 10f7f7d8c480..9e66cebbbafb 100644 --- a/src/xwayland/xwm/surface.rs +++ b/src/xwayland/xwm/surface.rs @@ -58,6 +58,7 @@ pub struct X11Surface { xwm: Option, client_scale: Option>, window: X11Window, + focus_release: Option, pub(super) conn: Weak, pub(super) atoms: super::Atoms, pub(crate) state: Arc>, @@ -269,6 +270,7 @@ impl X11Surface { pending_ping_timestamp: None, })), xdnd_active, + focus_release: xwm.map(|wm| wm.focus_release.clone()), user_data: Arc::new(UserDataMap::new()), } } @@ -1375,6 +1377,10 @@ impl KeyboardTarget for X11Surface { WmInputModel::GloballyActive => (false, true), }; + if let Some(release) = &self.focus_release { + release.cancel(); + } + if let Some(conn) = self.conn.upgrade() { if set_input_focus { if let Err(err) = conn.set_input_focus(InputFocus::NONE, self.window, x11rb::CURRENT_TIME) { @@ -1417,11 +1423,10 @@ impl KeyboardTarget for X11Surface { fn leave(&self, seat: &Seat, data: &mut D, serial: Serial) { if self.input_model() == WmInputModel::None { return; - } else if let Some(conn) = self.conn.upgrade() { - if let Err(err) = conn.set_input_focus(InputFocus::NONE, x11rb::NONE, x11rb::CURRENT_TIME) { - warn!("Unable to unfocus X11Surface ({:?}): {}", self.window, err); - } - let _ = conn.flush(); + } + + if let Some(release) = &self.focus_release { + release.schedule(); } let mut state = self.state.lock().unwrap();