🔒 Repository is read-only – file editing is disabled.

PaganDE/smithay-0.7.0/src/xwayland/xwm/surface.rs main

1227 linii Raw ← Powrót
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227
use crate::{
    backend::input::KeyState,
    input::{
        keyboard::{KeyboardTarget, KeysymHandle, ModifiersState},
        pointer::{
            AxisFrame, ButtonEvent, GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent,
            GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent, GestureSwipeEndEvent,
            GestureSwipeUpdateEvent, MotionEvent, PointerTarget, RelativeMotionEvent,
        },
        touch::TouchTarget,
        Seat, SeatHandler,
    },
    utils::{user_data::UserDataMap, Client, IsAlive, Logical, Rectangle, Serial, Size},
    wayland::compositor,
};
use atomic_float::AtomicF64;
use encoding_rs::WINDOWS_1252;
use std::{
    collections::HashSet,
    sync::{atomic::Ordering, Arc, Mutex, Weak},
};
use tracing::warn;
use wayland_server::protocol::wl_surface::WlSurface;

use x11rb::{
    connection::Connection as _,
    properties::{WmClass, WmHints, WmSizeHints},
    protocol::{
        res::{query_client_ids, ClientIdSpec},
        xproto::{
            Atom, AtomEnum, ClientMessageEvent, ConfigureWindowAux, ConnectionExt as _, EventMask,
            InputFocus, PropMode, Window as X11Window,
        },
    },
    rust_connection::{ConnectionError, RustConnection},
    wrapper::ConnectionExt,
};

use super::{send_configure_notify, X11Wm, XwmId};

/// X11 window managed by an [`X11Wm`](super::X11Wm)
#[derive(Debug, Clone)]
pub struct X11Surface {
    xwm: Option<XwmId>,
    client_scale: Option<Arc<AtomicF64>>,
    window: X11Window,
    conn: Weak<RustConnection>,
    atoms: super::Atoms,
    pub(crate) state: Arc<Mutex<SharedSurfaceState>>,
    user_data: Arc<UserDataMap>,
}

const MWM_HINTS_FLAGS_FIELD: usize = 0;
const MWM_HINTS_DECORATIONS_FIELD: usize = 2;
const MWM_HINTS_DECORATIONS: u32 = 1 << 1;

#[derive(Debug)]
pub(crate) struct SharedSurfaceState {
    pub(super) alive: bool,
    pub(super) wl_surface_id: Option<u32>,
    pub(super) wl_surface_serial: Option<u64>,
    pub(super) mapped_onto: Option<X11Window>,
    pub(super) geometry: Rectangle<i32, Logical>,
    pub(super) override_redirect: bool,

    // The associated wl_surface.
    pub(crate) wl_surface: Option<WlSurface>,

    title: String,
    class: String,
    instance: String,
    startup_id: Option<String>,
    pid: Option<u32>,
    protocols: Protocols,
    hints: Option<WmHints>,
    normal_hints: Option<WmSizeHints>,
    transient_for: Option<X11Window>,
    net_state: HashSet<Atom>,
    motif_hints: Vec<u32>,
    window_type: Vec<Atom>,
}

pub(super) type Protocols = Vec<WMProtocol>;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(super) enum WMProtocol {
    TakeFocus,
    DeleteWindow,
}

/// https://x.org/releases/X11R7.6/doc/xorg-docs/specs/ICCCM/icccm.html#input_focus
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum InputMode {
    None,
    Passive,
    LocallyActive,
    GloballyActive,
}

impl PartialEq for X11Surface {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        let self_alive = self.state.lock().unwrap().alive;
        let other_alive = other.state.lock().unwrap().alive;
        self.xwm == other.xwm && self.window == other.window && self_alive && other_alive
    }
}

/// Errors that can happen for operations on an [`X11Surface`]
#[derive(Debug, thiserror::Error)]
pub enum X11SurfaceError {
    /// Error on the underlying X11 Connection
    #[error(transparent)]
    Connection(#[from] ConnectionError),
    /// Operation was unsupported for an override_redirect window
    #[error("Operation was unsupported for an override_redirect window")]
    UnsupportedForOverrideRedirect,
}

/// Window types of [`X11Surface`]s
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[allow(missing_docs)]
pub enum WmWindowType {
    DropdownMenu,
    Dialog,
    Menu,
    Notification,
    Normal,
    PopupMenu,
    Splash,
    Toolbar,
    Tooltip,
    Utility,
}

/// Window properties of [`X11Surface`]s
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[allow(missing_docs)]
pub enum WmWindowProperty {
    Title,
    Class,
    Protocols,
    Hints,
    NormalHints,
    TransientFor,
    WindowType,
    MotifHints,
    StartupId,
    Pid,
}

impl X11Surface {
    /// Create a new [`X11Surface`] usually handled by an [`X11Wm`](super::X11Wm)
    ///
    /// ## Arguments
    ///
    /// - `window` X11 window id
    /// - `override_redirect` set if the X11 window has the override redirect flag set
    /// - `conn` Weak reference on the X11 connection
    /// - `atoms` Atoms struct as defined by the [xwm module](super).
    /// - `geometry` Initial geometry of the window
    pub fn new(
        xwm: Option<&X11Wm>,
        window: u32,
        override_redirect: bool,
        conn: Weak<RustConnection>,
        atoms: super::Atoms,
        geometry: Rectangle<i32, Logical>,
    ) -> X11Surface {
        X11Surface {
            xwm: xwm.map(|wm| wm.id),
            client_scale: xwm.map(|wm| wm.client_scale.clone()),
            window,
            conn,
            atoms,
            state: Arc::new(Mutex::new(SharedSurfaceState {
                alive: true,
                wl_surface_id: None,
                wl_surface_serial: None,
                wl_surface: None,
                mapped_onto: None,
                geometry,
                override_redirect,
                title: String::from(""),
                class: String::from(""),
                instance: String::from(""),
                startup_id: None,
                pid: None,
                protocols: Vec::new(),
                hints: None,
                normal_hints: None,
                transient_for: None,
                net_state: HashSet::new(),
                motif_hints: vec![0; 5],
                window_type: Vec::new(),
            })),
            user_data: Arc::new(UserDataMap::new()),
        }
    }

    /// Returns the id of the X11Wm responsible for this surface, if any
    pub fn xwm_id(&self) -> Option<XwmId> {
        self.xwm
    }

    /// X11 protocol id of the underlying window
    pub fn window_id(&self) -> X11Window {
        self.window
    }

    /// X11 protocol id of the reparented window, if any
    pub fn mapped_window_id(&self) -> Option<X11Window> {
        self.state.lock().unwrap().mapped_onto
    }

    /// Set the X11 windows as mapped/unmapped affecting its visibility.
    ///
    /// It is an error to call this function on override redirect windows
    pub fn set_mapped(&self, mapped: bool) -> Result<(), X11SurfaceError> {
        if self.is_override_redirect() {
            return Err(X11SurfaceError::UnsupportedForOverrideRedirect);
        }

        if let Some(conn) = self.conn.upgrade() {
            if let Some(frame) = self.state.lock().unwrap().mapped_onto {
                if mapped {
                    let property = [1u32 /*NormalState*/, 0 /*WINDOW_NONE*/];
                    conn.change_property32(
                        PropMode::REPLACE,
                        self.window,
                        self.atoms.WM_STATE,
                        self.atoms.WM_STATE,
                        &property,
                    )?;
                    conn.map_window(frame)?;
                } else {
                    let property = [3u32 /*IconicState*/, 0 /*WINDOW_NONE*/];
                    conn.change_property32(
                        PropMode::REPLACE,
                        self.window,
                        self.atoms.WM_STATE,
                        self.atoms.WM_STATE,
                        &property,
                    )?;
                    conn.unmap_window(frame)?;
                }
                conn.flush()?;
            }
        }
        Ok(())
    }

    /// Returns if this window has the override redirect flag set or not
    pub fn is_override_redirect(&self) -> bool {
        self.state.lock().unwrap().override_redirect
    }

    /// Returns if the window is currently mapped or not
    pub fn is_mapped(&self) -> bool {
        let state = self.state.lock().unwrap();
        state.wl_surface.is_some()
    }

    /// Returns if the window is still alive
    #[inline]
    pub fn alive(&self) -> bool {
        self.state.lock().unwrap().alive && self.conn.strong_count() != 0
    }

    /// Send a configure to this window.
    ///
    /// If `rect` is provided the new state will be send to the window.
    /// If `rect` is `None` a synthetic configure event with the existing state will be send.
    pub fn configure(&self, rect: impl Into<Option<Rectangle<i32, Logical>>>) -> Result<(), X11SurfaceError> {
        let rect = rect.into();
        if self.is_override_redirect() && rect.is_some() {
            return Err(X11SurfaceError::UnsupportedForOverrideRedirect);
        }

        if let Some(conn) = self.conn.upgrade() {
            let mut state = self.state.lock().unwrap();
            let client_scale = self
                .client_scale
                .as_ref()
                .map(|s| s.load(Ordering::Acquire))
                .unwrap_or(1.);
            let logical_rect = rect.unwrap_or(state.geometry);
            let rect = logical_rect.to_client_precise_round(client_scale);
            let aux = ConfigureWindowAux::default()
                .x(rect.loc.x)
                .y(rect.loc.y)
                .width(rect.size.w as u32)
                .height(rect.size.h as u32)
                .border_width(0);
            if let Some(frame) = state.mapped_onto {
                let win_aux = ConfigureWindowAux::default()
                    .width(rect.size.w as u32)
                    .height(rect.size.h as u32)
                    .border_width(0);
                conn.configure_window(frame, &aux)?;
                conn.configure_window(self.window, &win_aux)?;
                send_configure_notify(&conn, &self.window, rect, false)?;
            } else {
                conn.configure_window(self.window, &aux)?;
            }
            conn.flush()?;

            state.geometry = logical_rect;
        }
        Ok(())
    }

    /// Returns the associated wl_surface.
    ///
    /// This will only return `Some` once:
    ///   - The `WL_SURFACE_SERIAL` has been set on the x11 window, and
    ///   - The wl_surface has been assigned the same serial using the [xwayland
    ///     shell](crate::wayland::xwayland_shell) protocol on the wayland side,
    ///     and then committed.
    #[inline]
    pub fn wl_surface(&self) -> Option<WlSurface> {
        self.state.lock().unwrap().wl_surface.clone()
    }

    /// Returns the associated `wl_surface` id, once it has been set by
    /// xwayland.
    ///
    /// Note that XWayland will only set this if it was unable to bind the
    /// [xwayland shell](crate::wayland::xwayland_shell) protocol on the wayland
    /// side.
    #[deprecated = "Since XWayland 23.1, the recommended approach is to use [wl_surface_serial] and the [xwayland shell](crate::wayland::xwayland_shell) protocol on the wayland side to match X11 windows."]
    pub fn wl_surface_id(&self) -> Option<u32> {
        self.state.lock().unwrap().wl_surface_id
    }

    /// Returns the associated `wl_surface` serial, once it has been set by
    /// xwayland.
    ///
    /// XWayland will set this if it has bound the [xwayland
    /// shell](crate::wayland::xwayland_shell) protocol on the wayland side.
    /// Otherwise, it will set [`wl_surface_id`][Self::wl_surface_id] instead.
    pub fn wl_surface_serial(&self) -> Option<u64> {
        self.state.lock().unwrap().wl_surface_serial
    }

    /// Returns the current geometry of the underlying X11 window
    pub fn geometry(&self) -> Rectangle<i32, Logical> {
        self.state.lock().unwrap().geometry
    }

    /// Returns the current title of the underlying X11 window
    pub fn title(&self) -> String {
        self.state.lock().unwrap().title.clone()
    }

    /// Returns the current window class of the underlying X11 window
    pub fn class(&self) -> String {
        self.state.lock().unwrap().class.clone()
    }

    /// Returns the current window instance of the underlying X11 window
    pub fn instance(&self) -> String {
        self.state.lock().unwrap().instance.clone()
    }

    /// Returns the startup id of the underlying X11 window
    pub fn startup_id(&self) -> Option<String> {
        self.state.lock().unwrap().startup_id.clone()
    }

    /// Returns the PID of the underlying X11 window
    pub fn pid(&self) -> Option<u32> {
        self.state.lock().unwrap().pid
    }

    /// Returns if the window is considered to be a popup.
    ///
    /// Corresponds to the internal `_NET_WM_STATE_MODAL` state of the underlying X11 window.
    pub fn is_popup(&self) -> bool {
        let state = self.state.lock().unwrap();
        state.net_state.contains(&self.atoms._NET_WM_STATE_MODAL)
    }

    /// Returns if the underlying window is transient to another window.
    ///
    /// This might be used as a hint to manage windows in a group.
    pub fn is_transient_for(&self) -> Option<X11Window> {
        self.state.lock().unwrap().transient_for
    }

    /// Returns the hints for the underlying X11 window
    pub fn hints(&self) -> Option<WmHints> {
        self.state.lock().unwrap().hints
    }

    /// Returns the size hints for the underlying X11 window
    pub fn size_hints(&self) -> Option<WmSizeHints> {
        self.state.lock().unwrap().normal_hints
    }

    /// Returns the suggested minimum size of the underlying X11 window
    pub fn min_size(&self) -> Option<Size<i32, Logical>> {
        let client_scale = self
            .client_scale
            .as_ref()
            .map(|s| s.load(Ordering::Acquire))
            .unwrap_or(1.);
        let state = self.state.lock().unwrap();
        state
            .normal_hints
            .as_ref()
            .and_then(|hints| hints.min_size)
            .map(Size::<i32, Client>::from)
            .map(|s| s.to_f64().to_logical(client_scale).to_i32_round())
    }

    /// Returns the suggested minimum size of the underlying X11 window
    pub fn max_size(&self) -> Option<Size<i32, Logical>> {
        let client_scale = self
            .client_scale
            .as_ref()
            .map(|s| s.load(Ordering::Acquire))
            .unwrap_or(1.);
        let state = self.state.lock().unwrap();
        state
            .normal_hints
            .as_ref()
            .and_then(|hints| hints.max_size)
            .map(Size::<i32, Client>::from)
            .map(|s| s.to_f64().to_logical(client_scale).to_i32_round())
    }

    /// Returns the suggested base size of the underlying X11 window
    pub fn base_size(&self) -> Option<Size<i32, Logical>> {
        let client_scale = self
            .client_scale
            .as_ref()
            .map(|s| s.load(Ordering::Acquire))
            .unwrap_or(1.);
        let state = self.state.lock().unwrap();
        let res = state
            .normal_hints
            .as_ref()
            .and_then(|hints| hints.base_size)
            .map(Size::<i32, Client>::from)
            .map(|s| s.to_f64().to_logical(client_scale).to_i32_round());
        std::mem::drop(state);
        res.or_else(|| self.min_size())
    }

    /// Returns if the window is in the maximized state
    pub fn is_maximized(&self) -> bool {
        let state = self.state.lock().unwrap();
        state.net_state.contains(&self.atoms._NET_WM_STATE_MAXIMIZED_HORZ)
            && state.net_state.contains(&self.atoms._NET_WM_STATE_MAXIMIZED_VERT)
    }

    /// Returns if the window is in the fullscreen state
    pub fn is_fullscreen(&self) -> bool {
        self.state
            .lock()
            .unwrap()
            .net_state
            .contains(&self.atoms._NET_WM_STATE_FULLSCREEN)
    }

    /// Returns if the window is in the minimized state
    pub fn is_minimized(&self) -> bool {
        self.state
            .lock()
            .unwrap()
            .net_state
            .contains(&self.atoms._NET_WM_STATE_HIDDEN)
    }

    /// Returns if the window is in the activated state
    pub fn is_activated(&self) -> bool {
        self.state
            .lock()
            .unwrap()
            .net_state
            .contains(&self.atoms._NET_WM_STATE_FOCUSED)
    }

    /// Returns true if the window is client-side decorated
    pub fn is_decorated(&self) -> bool {
        let state = self.state.lock().unwrap();
        if (state.motif_hints[MWM_HINTS_FLAGS_FIELD] & MWM_HINTS_DECORATIONS) != 0 {
            return state.motif_hints[MWM_HINTS_DECORATIONS_FIELD] == 0;
        }
        false
    }

    /// Sets the window as maximized or not.
    ///
    /// Allows the client to reflect this state in their UI.
    pub fn set_maximized(&self, maximized: bool) -> Result<(), ConnectionError> {
        if maximized {
            self.change_net_state(
                &[
                    self.atoms._NET_WM_STATE_MAXIMIZED_HORZ,
                    self.atoms._NET_WM_STATE_MAXIMIZED_VERT,
                ],
                &[],
            )?;
        } else {
            self.change_net_state(
                &[],
                &[
                    self.atoms._NET_WM_STATE_MAXIMIZED_HORZ,
                    self.atoms._NET_WM_STATE_MAXIMIZED_VERT,
                ],
            )?;
        }
        Ok(())
    }

    /// Sets the window as fullscreen or not.
    ///
    /// Allows the client to reflect this state in their UI.
    pub fn set_fullscreen(&self, fullscreen: bool) -> Result<(), ConnectionError> {
        if fullscreen {
            self.change_net_state(&[self.atoms._NET_WM_STATE_FULLSCREEN], &[])?;
        } else {
            self.change_net_state(&[], &[self.atoms._NET_WM_STATE_FULLSCREEN])?;
        }
        Ok(())
    }

    /// Sets the window as suspended/hidden or not.
    ///
    /// Allows the client to e.g. stop rendering.
    pub fn set_suspended(&self, suspended: bool) -> Result<(), ConnectionError> {
        if suspended {
            self.change_net_state(&[self.atoms._NET_WM_STATE_HIDDEN], &[])?;
        } else {
            self.change_net_state(&[], &[self.atoms._NET_WM_STATE_HIDDEN])?;
        }
        Ok(())
    }

    /// Sets the window as activated or not.
    ///
    /// Allows the client to reflect this state in their UI.
    pub fn set_activated(&self, activated: bool) -> Result<(), ConnectionError> {
        if activated {
            self.change_net_state(&[self.atoms._NET_WM_STATE_FOCUSED], &[])?;
        } else {
            self.change_net_state(&[], &[self.atoms._NET_WM_STATE_FOCUSED])?;
        }
        Ok(())
    }

    /// Returns the reported window type of the underlying X11 window if set.
    ///
    /// Windows without a window type set should be considered to be of type `Normal` for
    /// backwards compatibility.
    pub fn window_type(&self) -> Option<WmWindowType> {
        self.state
            .lock()
            .unwrap()
            .window_type
            .iter()
            .find_map(|atom| match atom {
                x if *x == self.atoms._NET_WM_WINDOW_TYPE_DROPDOWN_MENU => Some(WmWindowType::DropdownMenu),
                x if *x == self.atoms._NET_WM_WINDOW_TYPE_DIALOG => Some(WmWindowType::Dialog),
                x if *x == self.atoms._NET_WM_WINDOW_TYPE_MENU => Some(WmWindowType::Menu),
                x if *x == self.atoms._NET_WM_WINDOW_TYPE_NOTIFICATION => Some(WmWindowType::Notification),
                x if *x == self.atoms._NET_WM_WINDOW_TYPE_NORMAL => Some(WmWindowType::Normal),
                x if *x == self.atoms._NET_WM_WINDOW_TYPE_POPUP_MENU => Some(WmWindowType::PopupMenu),
                x if *x == self.atoms._NET_WM_WINDOW_TYPE_SPLASH => Some(WmWindowType::Splash),
                x if *x == self.atoms._NET_WM_WINDOW_TYPE_TOOLBAR => Some(WmWindowType::Toolbar),
                x if *x == self.atoms._NET_WM_WINDOW_TYPE_TOOLTIP => Some(WmWindowType::Tooltip),
                x if *x == self.atoms._NET_WM_WINDOW_TYPE_UTILITY => Some(WmWindowType::Utility),
                _ => None,
            })
    }

    fn change_net_state(&self, added: &[Atom], removed: &[Atom]) -> Result<(), ConnectionError> {
        let mut state = self.state.lock().unwrap();

        let mut changed = false;
        for atom in removed {
            changed |= state.net_state.remove(atom);
        }
        for atom in added {
            changed |= state.net_state.insert(*atom);
        }

        if changed {
            let new_props = Vec::from_iter(state.net_state.iter().copied());

            let conn = self.conn.upgrade().ok_or(ConnectionError::UnknownError)?;
            conn.grab_server()?;
            let _guard = scopeguard::guard((), |_| {
                let _ = conn.ungrab_server();
                let _ = conn.flush();
            });

            conn.change_property32(
                PropMode::REPLACE,
                self.window,
                self.atoms._NET_WM_STATE,
                AtomEnum::ATOM,
                &new_props,
            )?;
        }

        Ok(())
    }

    fn input_mode(&self) -> InputMode {
        let state = self.state.lock().unwrap();
        match (
            state.hints.as_ref().and_then(|hints| hints.input).unwrap_or(true),
            state.protocols.contains(&WMProtocol::TakeFocus),
        ) {
            (false, false) => InputMode::None,
            (true, false) => InputMode::Passive, // the default
            (true, true) => InputMode::LocallyActive,
            (false, true) => InputMode::GloballyActive,
        }
    }

    pub(super) fn update_properties(&self) -> Result<(), ConnectionError> {
        self.update_title()?;
        self.update_class()?;
        self.update_protocols()?;
        self.update_hints()?;
        self.update_normal_hints()?;
        self.update_transient_for()?;
        // NET_WM_STATE is managed by the WM, we don't need to update it unless explicitly asked to
        self.update_net_window_type()?;
        self.update_motif_hints()?;
        self.update_startup_id()?;
        self.update_pid()?;
        Ok(())
    }

    pub(super) fn update_property(&self, atom: Atom) -> Result<Option<WmWindowProperty>, ConnectionError> {
        match atom {
            atom if atom == self.atoms._NET_WM_NAME || atom == AtomEnum::WM_NAME.into() => {
                self.update_title()?;
                Ok(Some(WmWindowProperty::Title))
            }
            atom if atom == AtomEnum::WM_CLASS.into() => {
                self.update_class()?;
                Ok(Some(WmWindowProperty::Class))
            }
            atom if atom == self.atoms.WM_PROTOCOLS => {
                self.update_protocols()?;
                Ok(Some(WmWindowProperty::Protocols))
            }
            atom if atom == self.atoms.WM_HINTS => {
                self.update_hints()?;
                Ok(Some(WmWindowProperty::Hints))
            }
            atom if atom == AtomEnum::WM_NORMAL_HINTS.into() => {
                self.update_normal_hints()?;
                Ok(Some(WmWindowProperty::NormalHints))
            }
            atom if atom == AtomEnum::WM_TRANSIENT_FOR.into() => {
                self.update_transient_for()?;
                Ok(Some(WmWindowProperty::TransientFor))
            }
            atom if atom == self.atoms._NET_WM_WINDOW_TYPE => {
                self.update_net_window_type()?;
                Ok(Some(WmWindowProperty::WindowType))
            }
            atom if atom == self.atoms._MOTIF_WM_HINTS => {
                self.update_motif_hints()?;
                Ok(Some(WmWindowProperty::MotifHints))
            }
            atom if atom == self.atoms._NET_STARTUP_ID => {
                self.update_startup_id()?;
                Ok(Some(WmWindowProperty::StartupId))
            }
            atom if atom == self.atoms._NET_WM_PID => {
                self.update_pid()?;
                Ok(Some(WmWindowProperty::Pid))
            }

            _ => Ok(None), // unknown
        }
    }

    fn update_class(&self) -> Result<(), ConnectionError> {
        let conn = self.conn.upgrade().ok_or(ConnectionError::UnknownError)?;
        let (class, instance) = match WmClass::get(&*conn, self.window)?.reply_unchecked() {
            Ok(Some(wm_class)) => (
                WINDOWS_1252.decode(wm_class.class()).0.to_string(),
                WINDOWS_1252.decode(wm_class.instance()).0.to_string(),
            ),
            Ok(None) | Err(ConnectionError::ParseError(_)) => (Default::default(), Default::default()), // Getting the property failed
            Err(err) => return Err(err),
        };

        let mut state = self.state.lock().unwrap();
        state.class = class;
        state.instance = instance;

        Ok(())
    }

    fn update_hints(&self) -> Result<(), ConnectionError> {
        let conn = self.conn.upgrade().ok_or(ConnectionError::UnknownError)?;
        let mut state = self.state.lock().unwrap();
        state.hints = match WmHints::get(&*conn, self.window)?.reply_unchecked() {
            Ok(hints) => hints,
            Err(ConnectionError::ParseError(_)) => None,
            Err(err) => return Err(err),
        };
        Ok(())
    }

    fn update_normal_hints(&self) -> Result<(), ConnectionError> {
        let conn = self.conn.upgrade().ok_or(ConnectionError::UnknownError)?;
        let mut state = self.state.lock().unwrap();
        state.normal_hints = match WmSizeHints::get_normal_hints(&*conn, self.window)?.reply_unchecked() {
            Ok(hints) => hints,
            Err(ConnectionError::ParseError(_)) => None,
            Err(err) => return Err(err),
        };
        Ok(())
    }

    fn update_motif_hints(&self) -> Result<(), ConnectionError> {
        let conn = self.conn.upgrade().ok_or(ConnectionError::UnknownError)?;
        let Some(hints) = (match conn
            .get_property(
                false,
                self.window,
                self.atoms._MOTIF_WM_HINTS,
                AtomEnum::ANY,
                0,
                2048,
            )?
            .reply_unchecked()
        {
            Ok(Some(reply)) => reply.value32().map(|vals| vals.collect::<Vec<_>>()),
            Ok(None) | Err(ConnectionError::ParseError(_)) => return Ok(()),
            Err(err) => return Err(err),
        }) else {
            return Ok(());
        };

        if hints.len() < 5 {
            return Ok(());
        }

        let mut state = self.state.lock().unwrap();
        state.motif_hints = hints;
        Ok(())
    }

    fn update_startup_id(&self) -> Result<(), ConnectionError> {
        if let Some(startup_id) = self.read_window_property_string(self.atoms._NET_STARTUP_ID)? {
            let mut state = self.state.lock().unwrap();
            state.startup_id = Some(startup_id);
        }
        Ok(())
    }

    fn update_pid(&self) -> Result<(), ConnectionError> {
        if let Some(pid) = self.read_window_property_u32(self.atoms._NET_WM_PID)? {
            let mut state = self.state.lock().unwrap();
            state.pid = Some(pid);
        }
        Ok(())
    }

    fn update_protocols(&self) -> Result<(), ConnectionError> {
        let conn = self.conn.upgrade().ok_or(ConnectionError::UnknownError)?;
        let Some(protocols) = (match conn
            .get_property(
                false,
                self.window,
                self.atoms.WM_PROTOCOLS,
                AtomEnum::ATOM,
                0,
                2048,
            )?
            .reply_unchecked()
        {
            Ok(Some(reply)) => reply.value32().map(|vals| vals.collect::<Vec<_>>()),
            Ok(None) | Err(ConnectionError::ParseError(_)) => return Ok(()),
            Err(err) => return Err(err),
        }) else {
            return Ok(());
        };

        let mut state = self.state.lock().unwrap();
        state.protocols = protocols
            .into_iter()
            .filter_map(|atom| match atom {
                x if x == self.atoms.WM_TAKE_FOCUS => Some(WMProtocol::TakeFocus),
                x if x == self.atoms.WM_DELETE_WINDOW => Some(WMProtocol::DeleteWindow),
                _ => None,
            })
            .collect::<Vec<_>>();
        Ok(())
    }

    fn update_transient_for(&self) -> Result<(), ConnectionError> {
        let conn = self.conn.upgrade().ok_or(ConnectionError::UnknownError)?;
        let reply = match conn
            .get_property(
                false,
                self.window,
                AtomEnum::WM_TRANSIENT_FOR,
                AtomEnum::WINDOW,
                0,
                2048,
            )?
            .reply_unchecked()
        {
            Ok(Some(reply)) => reply,
            Ok(None) | Err(ConnectionError::ParseError(_)) => return Ok(()),
            Err(err) => return Err(err),
        };
        let window = reply
            .value32()
            .and_then(|mut iter| iter.next())
            .filter(|w| *w != 0);

        let mut state = self.state.lock().unwrap();
        state.transient_for = window;
        Ok(())
    }

    fn update_title(&self) -> Result<(), ConnectionError> {
        let title = self
            .read_window_property_string(self.atoms._NET_WM_NAME)?
            .or(self.read_window_property_string(AtomEnum::WM_NAME)?)
            .unwrap_or_default();

        let mut state = self.state.lock().unwrap();
        state.title = title;
        Ok(())
    }

    fn update_net_window_type(&self) -> Result<(), ConnectionError> {
        let conn = self.conn.upgrade().ok_or(ConnectionError::UnknownError)?;
        let atoms = match conn
            .get_property(
                false,
                self.window,
                self.atoms._NET_WM_WINDOW_TYPE,
                AtomEnum::ATOM,
                0,
                1024,
            )?
            .reply_unchecked()
        {
            Ok(atoms) => atoms,
            Err(ConnectionError::ParseError(_)) => return Ok(()),
            Err(err) => return Err(err),
        };

        let mut state = self.state.lock().unwrap();
        state.window_type = atoms
            .and_then(|atoms| Some(atoms.value32()?.collect::<Vec<_>>()))
            .unwrap_or_default();
        Ok(())
    }

    fn read_window_property_string(&self, atom: impl Into<Atom>) -> Result<Option<String>, ConnectionError> {
        let conn = self.conn.upgrade().ok_or(ConnectionError::UnknownError)?;
        let reply = match conn
            .get_property(false, self.window, atom, AtomEnum::ANY, 0, 2048)?
            .reply_unchecked()
        {
            Ok(Some(reply)) => reply,
            Ok(None) | Err(ConnectionError::ParseError(_)) => return Ok(None),
            Err(err) => return Err(err),
        };
        let Some(bytes) = reply.value8() else {
            return Ok(None);
        };
        let bytes = bytes.collect::<Vec<u8>>();

        match reply.type_ {
            x if x == AtomEnum::STRING.into() => Ok(Some(WINDOWS_1252.decode(&bytes).0.to_string())),
            x if x == self.atoms.UTF8_STRING => Ok(String::from_utf8(bytes).ok()),
            _ => Ok(None),
        }
    }

    fn read_window_property_u32(&self, atom: impl Into<Atom>) -> Result<Option<u32>, ConnectionError> {
        let conn = self.conn.upgrade().ok_or(ConnectionError::UnknownError)?;
        let reply = match conn
            .get_property(false, self.window, atom, AtomEnum::CARDINAL, 0, 1)?
            .reply_unchecked()
        {
            Ok(Some(reply)) => reply,
            Ok(None) | Err(ConnectionError::ParseError(_)) => return Ok(None),
            Err(err) => return Err(err),
        };

        if let Some(mut value32) = reply.value32() {
            if let Some(value) = value32.next() {
                return Ok(Some(value));
            }
        }

        Ok(None)
    }

    /// Retrieve user_data associated with this X11 window
    pub fn user_data(&self) -> &UserDataMap {
        &self.user_data
    }

    /// Send a close request to this window.
    ///
    /// Will outright destroy windows that don't support the `NET_DELETE_WINDOW` protocol.
    pub fn close(&self) -> Result<(), ConnectionError> {
        let conn = self.conn.upgrade().ok_or(ConnectionError::UnknownError)?;
        let state = self.state.lock().unwrap();
        if state.protocols.contains(&WMProtocol::DeleteWindow) {
            let event = ClientMessageEvent::new(
                32,
                self.window,
                self.atoms.WM_PROTOCOLS,
                [self.atoms.WM_DELETE_WINDOW, 0, 0, 0, 0],
            );
            conn.send_event(false, self.window, EventMask::NO_EVENT, event)?;
        } else {
            conn.destroy_window(self.window)?;
        }
        conn.flush()
    }

    /// Get the client PID associated with the X11 window.
    pub fn get_client_pid(&self) -> Result<u32, Box<dyn std::error::Error>> {
        if let Some(connection) = self.conn.upgrade() {
            let window = self.window;

            match query_client_ids(
                &connection,
                &[ClientIdSpec {
                    client: window,
                    mask: x11rb::protocol::res::ClientIdMask::LOCAL_CLIENT_PID,
                }],
            ) {
                Ok(cookie) => {
                    let reply = cookie.reply()?;

                    if let Some(id_value) = reply.ids.first() {
                        if let Some(pid) = id_value.value.first().copied() {
                            return Ok(pid);
                        } else {
                            return Err(Box::new(std::io::Error::new(
                                std::io::ErrorKind::NotFound,
                                "No matching client ID found",
                            )));
                        }
                    }
                }
                Err(_) => {
                    return Ok(0);
                }
            }
        }
        Ok(0)
    }
}

/// Trait for objects, that represent an x11 window in some shape or form
/// and can be tested for equality.
pub trait X11Relatable {
    /// Returns if this object is considered to represent the same underlying x11 window as provided
    fn is_window(&self, window: &X11Surface) -> bool;
}

impl X11Relatable for X11Surface {
    fn is_window(&self, window: &X11Surface) -> bool {
        self == window
    }
}

impl X11Relatable for WlSurface {
    fn is_window(&self, window: &X11Surface) -> bool {
        let serial = compositor::with_states(self, |states| {
            states
                .cached_state
                .get::<crate::wayland::xwayland_shell::XWaylandShellCachedState>()
                .current()
                .serial
        });

        window.wl_surface_serial() == serial
    }
}

impl IsAlive for X11Surface {
    #[inline]
    fn alive(&self) -> bool {
        self.state.lock().unwrap().alive
    }
}

impl<D: SeatHandler + 'static> KeyboardTarget<D> for X11Surface {
    fn enter(&self, seat: &Seat<D>, data: &mut D, keys: Vec<KeysymHandle<'_>>, serial: Serial) {
        let (set_input_focus, send_take_focus) = match self.input_mode() {
            InputMode::None => return,
            InputMode::Passive => (true, false),
            InputMode::LocallyActive => (true, true),
            InputMode::GloballyActive => (false, true),
        };

        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) {
                    warn!("Unable to set focus for X11Surface ({:?}): {}", self.window, err);
                }
            }

            if send_take_focus {
                let event = ClientMessageEvent::new(
                    32,
                    self.window,
                    self.atoms.WM_PROTOCOLS,
                    [self.atoms.WM_TAKE_FOCUS, x11rb::CURRENT_TIME, 0, 0, 0],
                );
                if let Err(err) = conn.send_event(false, self.window, EventMask::NO_EVENT, event) {
                    warn!(
                        "Unable to send take focus event for X11Surface ({:?}): {}",
                        self.window, err
                    );
                }
                let _ = conn.flush();
            }

            let _ = conn.flush();
        }

        if let Some(surface) = self.state.lock().unwrap().wl_surface.as_ref() {
            KeyboardTarget::enter(surface, seat, data, keys, serial);
        }
    }

    fn leave(&self, seat: &Seat<D>, data: &mut D, serial: Serial) {
        if self.input_mode() == InputMode::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(surface) = self.state.lock().unwrap().wl_surface.as_ref() {
            KeyboardTarget::leave(surface, seat, data, serial);
        }
    }

    fn key(
        &self,
        seat: &Seat<D>,
        data: &mut D,
        key: KeysymHandle<'_>,
        state: KeyState,
        serial: Serial,
        time: u32,
    ) {
        if self.input_mode() == InputMode::None {
            return;
        }

        if let Some(surface) = self.state.lock().unwrap().wl_surface.as_ref() {
            KeyboardTarget::key(surface, seat, data, key, state, serial, time)
        }
    }

    fn modifiers(&self, seat: &Seat<D>, data: &mut D, modifiers: ModifiersState, serial: Serial) {
        if self.input_mode() == InputMode::None {
            return;
        }

        if let Some(surface) = self.state.lock().unwrap().wl_surface.as_ref() {
            KeyboardTarget::modifiers(surface, seat, data, modifiers, serial);
        }
    }
}

impl<D: SeatHandler + 'static> PointerTarget<D> for X11Surface {
    fn enter(&self, seat: &Seat<D>, data: &mut D, event: &MotionEvent) {
        if let Some(surface) = self.state.lock().unwrap().wl_surface.as_ref() {
            PointerTarget::enter(surface, seat, data, event);
        }
    }

    fn motion(&self, seat: &Seat<D>, data: &mut D, event: &MotionEvent) {
        if let Some(surface) = self.state.lock().unwrap().wl_surface.as_ref() {
            PointerTarget::motion(surface, seat, data, event);
        }
    }

    fn relative_motion(&self, seat: &Seat<D>, data: &mut D, event: &RelativeMotionEvent) {
        if let Some(surface) = self.state.lock().unwrap().wl_surface.as_ref() {
            PointerTarget::relative_motion(surface, seat, data, event);
        }
    }

    fn button(&self, seat: &Seat<D>, data: &mut D, event: &ButtonEvent) {
        if let Some(surface) = self.state.lock().unwrap().wl_surface.as_ref() {
            PointerTarget::button(surface, seat, data, event);
        }
    }

    fn axis(&self, seat: &Seat<D>, data: &mut D, frame: AxisFrame) {
        if let Some(surface) = self.state.lock().unwrap().wl_surface.as_ref() {
            PointerTarget::axis(surface, seat, data, frame);
        }
    }

    fn frame(&self, seat: &Seat<D>, data: &mut D) {
        if let Some(surface) = self.state.lock().unwrap().wl_surface.as_ref() {
            PointerTarget::frame(surface, seat, data);
        }
    }

    fn leave(&self, seat: &Seat<D>, data: &mut D, serial: Serial, time: u32) {
        if let Some(surface) = self.state.lock().unwrap().wl_surface.as_ref() {
            PointerTarget::leave(surface, seat, data, serial, time);
        }
    }

    fn gesture_swipe_begin(&self, seat: &Seat<D>, data: &mut D, event: &GestureSwipeBeginEvent) {
        if let Some(surface) = self.state.lock().unwrap().wl_surface.as_ref() {
            PointerTarget::gesture_swipe_begin(surface, seat, data, event);
        }
    }

    fn gesture_swipe_update(&self, seat: &Seat<D>, data: &mut D, event: &GestureSwipeUpdateEvent) {
        if let Some(surface) = self.state.lock().unwrap().wl_surface.as_ref() {
            PointerTarget::gesture_swipe_update(surface, seat, data, event);
        }
    }

    fn gesture_swipe_end(&self, seat: &Seat<D>, data: &mut D, event: &GestureSwipeEndEvent) {
        if let Some(surface) = self.state.lock().unwrap().wl_surface.as_ref() {
            PointerTarget::gesture_swipe_end(surface, seat, data, event);
        }
    }

    fn gesture_pinch_begin(&self, seat: &Seat<D>, data: &mut D, event: &GesturePinchBeginEvent) {
        if let Some(surface) = self.state.lock().unwrap().wl_surface.as_ref() {
            PointerTarget::gesture_pinch_begin(surface, seat, data, event)
        }
    }

    fn gesture_pinch_update(&self, seat: &Seat<D>, data: &mut D, event: &GesturePinchUpdateEvent) {
        if let Some(surface) = self.state.lock().unwrap().wl_surface.as_ref() {
            PointerTarget::gesture_pinch_update(surface, seat, data, event)
        }
    }

    fn gesture_pinch_end(&self, seat: &Seat<D>, data: &mut D, event: &GesturePinchEndEvent) {
        if let Some(surface) = self.state.lock().unwrap().wl_surface.as_ref() {
            PointerTarget::gesture_pinch_end(surface, seat, data, event)
        }
    }

    fn gesture_hold_begin(&self, seat: &Seat<D>, data: &mut D, event: &GestureHoldBeginEvent) {
        if let Some(surface) = self.state.lock().unwrap().wl_surface.as_ref() {
            PointerTarget::gesture_hold_begin(surface, seat, data, event)
        }
    }

    fn gesture_hold_end(&self, seat: &Seat<D>, data: &mut D, event: &GestureHoldEndEvent) {
        if let Some(surface) = self.state.lock().unwrap().wl_surface.as_ref() {
            PointerTarget::gesture_hold_end(surface, seat, data, event)
        }
    }
}

impl<D: SeatHandler + 'static> TouchTarget<D> for X11Surface {
    fn down(&self, seat: &Seat<D>, data: &mut D, event: &crate::input::touch::DownEvent, seq: Serial) {
        if let Some(surface) = self.state.lock().unwrap().wl_surface.as_ref() {
            TouchTarget::down(surface, seat, data, event, seq)
        }
    }

    fn up(&self, seat: &Seat<D>, data: &mut D, event: &crate::input::touch::UpEvent, seq: Serial) {
        if let Some(surface) = self.state.lock().unwrap().wl_surface.as_ref() {
            TouchTarget::up(surface, seat, data, event, seq)
        }
    }

    fn motion(&self, seat: &Seat<D>, data: &mut D, event: &crate::input::touch::MotionEvent, seq: Serial) {
        if let Some(surface) = self.state.lock().unwrap().wl_surface.as_ref() {
            TouchTarget::motion(surface, seat, data, event, seq)
        }
    }

    fn frame(&self, seat: &Seat<D>, data: &mut D, seq: Serial) {
        if let Some(surface) = self.state.lock().unwrap().wl_surface.as_ref() {
            TouchTarget::frame(surface, seat, data, seq)
        }
    }

    fn cancel(&self, seat: &Seat<D>, data: &mut D, seq: Serial) {
        if let Some(surface) = self.state.lock().unwrap().wl_surface.as_ref() {
            TouchTarget::cancel(surface, seat, data, seq)
        }
    }

    fn shape(&self, seat: &Seat<D>, data: &mut D, event: &crate::input::touch::ShapeEvent, seq: Serial) {
        if let Some(surface) = self.state.lock().unwrap().wl_surface.as_ref() {
            TouchTarget::shape(surface, seat, data, event, seq)
        }
    }

    fn orientation(
        &self,
        seat: &Seat<D>,
        data: &mut D,
        event: &crate::input::touch::OrientationEvent,
        seq: Serial,
    ) {
        if let Some(surface) = self.state.lock().unwrap().wl_surface.as_ref() {
            TouchTarget::orientation(surface, seat, data, event, seq)
        }
    }
}