use std::borrow::Cow;
use std::ffi::CString;
use std::mem::replace;
use std::num::NonZeroU32;
use std::ops::Deref;
use std::os::raw::*;
use std::path::Path;
use std::sync::{Arc, Mutex, MutexGuard};
use std::{cmp, env};

use dpi::{PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size};
use tracing::{debug, info, warn};
use winit_core::application::ApplicationHandler;
use winit_core::cursor::Cursor;
use winit_core::error::{NotSupportedError, RequestError};
use winit_core::event::{SurfaceSizeWriter, WindowEvent};
use winit_core::event_loop::AsyncRequestSerial;
use winit_core::icon::RgbaIcon;
use winit_core::monitor::{
    Fullscreen, MonitorHandle as CoreMonitorHandle, MonitorHandleProvider, VideoMode,
};
use winit_core::window::{
    CursorGrabMode, ImeCapabilities, ImeRequest as CoreImeRequest, ImeRequestError,
    ResizeDirection, Theme, UserAttentionType, Window as CoreWindow, WindowAttributes,
    WindowButtons, WindowId, WindowLevel,
};
use x11rb::connection::{Connection, RequestConnection};
use x11rb::properties::{WmHints, WmSizeHints, WmSizeHintsSpecification};
use x11rb::protocol::shape::{ConnectionExt as ShapeExt, SK, SO};
use x11rb::protocol::sync::{ConnectionExt as _, Int64};
use x11rb::protocol::xproto::{self, ClipOrdering, ConnectionExt as _, Rectangle};
use x11rb::protocol::{randr, xinput};

use crate::atoms::{
    _GTK_THEME_VARIANT, _NET_ACTIVE_WINDOW, _NET_WM_ICON, _NET_WM_MOVERESIZE, _NET_WM_NAME,
    _NET_WM_PID, _NET_WM_PING, _NET_WM_STATE, _NET_WM_STATE_ABOVE, _NET_WM_STATE_BELOW,
    _NET_WM_STATE_FULLSCREEN, _NET_WM_STATE_HIDDEN, _NET_WM_STATE_MAXIMIZED_HORZ,
    _NET_WM_STATE_MAXIMIZED_VERT, _NET_WM_SYNC_REQUEST, _NET_WM_SYNC_REQUEST_COUNTER,
    _NET_WM_WINDOW_TYPE, _XEMBED, AtomName, CARD32, UTF8_STRING, WM_CHANGE_STATE,
    WM_CLIENT_MACHINE, WM_DELETE_WINDOW, WM_PROTOCOLS, WM_STATE, XdndAware,
};
use crate::event_loop::{
    ALL_MASTER_DEVICES, ActivationItem, ActiveEventLoop, CookieResultExt, ICONIC_STATE, VoidCookie,
    WakeSender, X11Error, xinput_fp1616_to_float,
};
use crate::ime::{ImeRequest, ImeSender};
use crate::monitor::MonitorHandle as X11MonitorHandle;
use crate::util::{self, CustomCursor, SelectedCursor, rgba_to_cardinals};
use crate::xdisplay::XConnection;
use crate::{WindowAttributesX11, WindowType, ffi};

#[derive(Debug)]
pub struct Window(Arc<UnownedWindow>);

impl Deref for Window {
    type Target = UnownedWindow;

    #[inline]
    fn deref(&self) -> &UnownedWindow {
        &self.0
    }
}

impl Window {
    pub(crate) fn new(
        event_loop: &ActiveEventLoop,
        attribs: WindowAttributes,
    ) -> Result<Self, RequestError> {
        use winit_core::window::WindowType;
        match attribs.window_type() {
            WindowType::Window => {
                let window = Arc::new(UnownedWindow::new(event_loop, attribs)?);
                event_loop.windows.borrow_mut().insert(window.id(), Arc::downgrade(&window));
                Ok(Window(window))
            },
            WindowType::Popup => Err(RequestError::NotSupported(NotSupportedError::new(
                "Popups are not implemented for X11",
            ))),
            _ => Err(RequestError::NotSupported(NotSupportedError::new("Unsupported window type"))),
        }
    }
}

impl CoreWindow for Window {
    fn window_type(&self) -> winit_core::window::WindowType {
        winit_core::window::WindowType::Window
    }

    fn id(&self) -> WindowId {
        self.0.id()
    }

    fn scale_factor(&self) -> f64 {
        self.0.scale_factor()
    }

    fn request_redraw(&self) {
        self.0.request_redraw()
    }

    fn pre_present_notify(&self) {
        self.0.pre_present_notify()
    }

    fn reset_dead_keys(&self) {
        winit_common::xkb::reset_dead_keys();
    }

    fn surface_position(&self) -> PhysicalPosition<i32> {
        self.0.surface_position()
    }

    fn outer_position(&self) -> Result<PhysicalPosition<i32>, RequestError> {
        self.0.outer_position()
    }

    fn set_outer_position(&self, position: Position) {
        self.0.set_outer_position(position)
    }

    fn surface_size(&self) -> PhysicalSize<u32> {
        self.0.surface_size()
    }

    fn request_surface_size(&self, size: Size) -> Option<PhysicalSize<u32>> {
        self.0.request_surface_size(size)
    }

    fn outer_size(&self) -> PhysicalSize<u32> {
        self.0.outer_size()
    }

    fn safe_area(&self) -> PhysicalInsets<u32> {
        self.0.safe_area()
    }

    fn set_min_surface_size(&self, min_size: Option<Size>) {
        self.0.set_min_surface_size(min_size)
    }

    fn set_max_surface_size(&self, max_size: Option<Size>) {
        self.0.set_max_surface_size(max_size)
    }

    fn surface_resize_increments(&self) -> Option<PhysicalSize<u32>> {
        self.0.surface_resize_increments()
    }

    fn set_surface_resize_increments(&self, increments: Option<Size>) {
        self.0.set_surface_resize_increments(increments)
    }

    fn set_title(&self, title: &str) {
        self.0.set_title(title);
    }

    fn set_transparent(&self, transparent: bool) {
        self.0.set_transparent(transparent);
    }

    fn set_blur(&self, blur: bool) {
        self.0.set_blur(blur);
    }

    fn set_visible(&self, visible: bool) {
        self.0.set_visible(visible);
    }

    fn is_visible(&self) -> Option<bool> {
        self.0.is_visible()
    }

    fn set_resizable(&self, resizable: bool) {
        self.0.set_resizable(resizable);
    }

    fn is_resizable(&self) -> bool {
        self.0.is_resizable()
    }

    fn set_enabled_buttons(&self, buttons: WindowButtons) {
        self.0.set_enabled_buttons(buttons)
    }

    fn enabled_buttons(&self) -> WindowButtons {
        self.0.enabled_buttons()
    }

    fn set_minimized(&self, minimized: bool) {
        self.0.set_minimized(minimized)
    }

    fn is_minimized(&self) -> Option<bool> {
        self.0.is_minimized()
    }

    fn set_maximized(&self, maximized: bool) {
        self.0.set_maximized(maximized)
    }

    fn is_maximized(&self) -> bool {
        self.0.is_maximized()
    }

    fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
        self.0.set_fullscreen(fullscreen)
    }

    fn fullscreen(&self) -> Option<Fullscreen> {
        self.0.fullscreen()
    }

    fn set_decorations(&self, decorations: bool) {
        self.0.set_decorations(decorations);
    }

    fn is_decorated(&self) -> bool {
        self.0.is_decorated()
    }

    fn set_window_level(&self, level: WindowLevel) {
        self.0.set_window_level(level);
    }

    fn set_window_icon(&self, window_icon: Option<winit_core::icon::Icon>) {
        let icon = match window_icon.as_ref() {
            Some(icon) => icon.cast_ref::<RgbaIcon>(),
            None => None,
        };
        self.0.set_window_icon(icon)
    }

    fn request_ime_update(&self, action: CoreImeRequest) -> Result<(), ImeRequestError> {
        self.0.request_ime_update(action)
    }

    fn ime_capabilities(&self) -> Option<ImeCapabilities> {
        self.0.ime_capabilities()
    }

    fn focus_window(&self) {
        self.0.focus_window();
    }

    fn has_focus(&self) -> bool {
        self.0.has_focus()
    }

    fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
        self.0.request_user_attention(request_type);
    }

    fn set_theme(&self, theme: Option<Theme>) {
        self.0.set_theme(theme);
    }

    fn theme(&self) -> Option<Theme> {
        self.0.theme()
    }

    fn set_content_protected(&self, protected: bool) {
        self.0.set_content_protected(protected);
    }

    fn title(&self) -> String {
        self.0.title()
    }

    fn set_cursor(&self, cursor: Cursor) {
        self.0.set_cursor(cursor);
    }

    fn set_cursor_position(&self, position: Position) -> Result<(), RequestError> {
        self.0.set_cursor_position(position)
    }

    fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), RequestError> {
        self.0.set_cursor_grab(mode)
    }

    fn set_cursor_visible(&self, visible: bool) {
        self.0.set_cursor_visible(visible);
    }

    fn drag_window(&self) -> Result<(), RequestError> {
        self.0.drag_window()
    }

    fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), RequestError> {
        self.0.drag_resize_window(direction)
    }

    fn show_window_menu(&self, position: Position) {
        self.0.show_window_menu(position);
    }

    fn set_cursor_hittest(&self, hittest: bool) -> Result<(), RequestError> {
        self.0.set_cursor_hittest(hittest)
    }

    fn current_monitor(&self) -> Option<CoreMonitorHandle> {
        self.0.current_monitor().map(|monitor| CoreMonitorHandle(Arc::new(monitor)))
    }

    fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> {
        Box::new(
            self.0
                .available_monitors()
                .into_iter()
                .map(|monitor| CoreMonitorHandle(Arc::new(monitor))),
        )
    }

    fn primary_monitor(&self) -> Option<CoreMonitorHandle> {
        self.0.primary_monitor().map(|monitor| CoreMonitorHandle(Arc::new(monitor)))
    }

    fn rwh_06_display_handle(&self) -> &dyn rwh_06::HasDisplayHandle {
        self
    }

    fn rwh_06_window_handle(&self) -> &dyn rwh_06::HasWindowHandle {
        self
    }
}

impl rwh_06::HasDisplayHandle for Window {
    fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
        let raw = self.0.raw_display_handle_rwh_06()?;
        unsafe { Ok(rwh_06::DisplayHandle::borrow_raw(raw)) }
    }
}

impl rwh_06::HasWindowHandle for Window {
    fn window_handle(&self) -> Result<rwh_06::WindowHandle<'_>, rwh_06::HandleError> {
        let raw = self.0.raw_window_handle_rwh_06()?;
        unsafe { Ok(rwh_06::WindowHandle::borrow_raw(raw)) }
    }
}

impl Drop for Window {
    fn drop(&mut self) {
        let window = &self.0;
        let xconn = &window.xconn;

        // Restore the video mode on drop.
        if let Some(Fullscreen::Exclusive(..)) = window.fullscreen() {
            window.set_fullscreen(None);
        }

        if let Ok(c) =
            xconn.xcb_connection().destroy_window(window.id().into_raw() as xproto::Window)
        {
            c.ignore_error();
        }
    }
}

#[derive(Debug)]
pub struct SharedState {
    pub cursor_pos: Option<(f64, f64)>,
    pub size: Option<(u32, u32)>,
    pub position: Option<(i32, i32)>,
    pub inner_position: Option<(i32, i32)>,
    pub inner_position_rel_parent: Option<(i32, i32)>,
    pub is_resizable: bool,
    pub is_decorated: bool,
    pub ime_capabilities: Option<ImeCapabilities>,
    pub last_monitor: X11MonitorHandle,
    pub dpi_adjusted: Option<(u32, u32)>,
    pub(crate) fullscreen: Option<Fullscreen>,
    // Set when application calls `set_fullscreen` when window is not visible
    pub(crate) desired_fullscreen: Option<Option<Fullscreen>>,
    // Used to restore position after exiting fullscreen
    pub restore_position: Option<(i32, i32)>,
    // Used to restore video mode after exiting fullscreen
    pub desktop_video_mode: Option<(randr::Crtc, randr::Mode)>,
    pub frame_extents: Option<util::FrameExtentsHeuristic>,
    pub min_surface_size: Option<Size>,
    pub max_surface_size: Option<Size>,
    pub surface_resize_increments: Option<Size>,
    pub base_size: Option<Size>,
    pub visibility: Visibility,
    pub has_focus: bool,
    // Use `Option` to not apply hittest logic when it was never requested.
    pub cursor_hittest: Option<bool>,
}

#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Visibility {
    No,
    Yes,
    // Waiting for VisibilityNotify
    YesWait,
}

impl SharedState {
    fn new(last_monitor: X11MonitorHandle, window_attributes: &WindowAttributes) -> Mutex<Self> {
        let visibility =
            if window_attributes.visible { Visibility::YesWait } else { Visibility::No };

        Mutex::new(SharedState {
            last_monitor,
            visibility,

            is_resizable: window_attributes.resizable,
            is_decorated: window_attributes.decorations,
            cursor_pos: None,
            size: None,
            position: None,
            inner_position: None,
            ime_capabilities: None,
            inner_position_rel_parent: None,
            dpi_adjusted: None,
            fullscreen: None,
            desired_fullscreen: None,
            restore_position: None,
            desktop_video_mode: None,
            frame_extents: None,
            min_surface_size: None,
            max_surface_size: None,
            surface_resize_increments: None,
            base_size: None,
            has_focus: false,
            cursor_hittest: None,
        })
    }
}

unsafe impl Send for UnownedWindow {}
unsafe impl Sync for UnownedWindow {}

#[derive(Debug)]
pub struct UnownedWindow {
    pub(crate) xconn: Arc<XConnection>, // never changes
    xwindow: xproto::Window,            // never changes
    #[allow(dead_code)]
    visual: u32, // never changes
    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)]
    cursor_visible: Mutex<bool>,
    ime_sender: Mutex<ImeSender>,
    pub shared_state: Mutex<SharedState>,
    redraw_sender: WakeSender<WindowId>,
    activation_sender: WakeSender<ActivationItem>,
}
macro_rules! leap {
    ($e:expr) => {
        $e.map_err(|err| os_error!(err))?
    };
}

impl UnownedWindow {
    #[allow(clippy::unnecessary_cast)]
    pub(crate) fn new(
        event_loop: &ActiveEventLoop,
        mut window_attrs: WindowAttributes,
    ) -> Result<UnownedWindow, RequestError> {
        let xconn = &event_loop.xconn;
        let atoms = xconn.atoms();

        let x11_attributes = window_attrs
            .platform
            .take()
            .and_then(|attrs| attrs.cast::<WindowAttributesX11>().ok())
            .unwrap_or_default();

        let screen_id = match x11_attributes.screen_id {
            Some(id) => id,
            None => xconn.default_screen_index() as c_int,
        };

        let screen = {
            let screen_id_usize = usize::try_from(screen_id)
                .map_err(|_| NotSupportedError::new("screen id must be non-negative"))?;
            xconn.xcb_connection().setup().roots.get(screen_id_usize).ok_or(
                NotSupportedError::new("requested screen id not present in server's response"),
            )?
        };

        let root = match window_attrs.parent_window() {
            Some(rwh_06::RawWindowHandle::Xlib(handle)) => handle.window as xproto::Window,
            Some(rwh_06::RawWindowHandle::Xcb(handle)) => handle.window.get(),
            Some(raw) => unreachable!("Invalid raw window handle {raw:?} on X11"),
            None => screen.root,
        };

        let mut monitors = leap!(xconn.available_monitors());
        let guessed_monitor = if monitors.is_empty() {
            X11MonitorHandle::dummy()
        } else {
            xconn
                .query_pointer(root, util::VIRTUAL_CORE_POINTER)
                .ok()
                .and_then(|pointer_state| {
                    let (x, y) = (pointer_state.root_x as i64, pointer_state.root_y as i64);

                    for i in 0..monitors.len() {
                        if monitors[i].rect.contains_point(x, y) {
                            return Some(monitors.swap_remove(i));
                        }
                    }

                    None
                })
                .unwrap_or_else(|| monitors.swap_remove(0))
        };
        let scale_factor = guessed_monitor.scale_factor();

        info!("Guessed window scale factor: {}", scale_factor);

        let max_surface_size: Option<(u32, u32)> =
            window_attrs.max_surface_size.map(|size| size.to_physical::<u32>(scale_factor).into());
        let min_surface_size: Option<(u32, u32)> =
            window_attrs.min_surface_size.map(|size| size.to_physical::<u32>(scale_factor).into());

        let position =
            window_attrs.position.map(|position| position.to_physical::<i32>(scale_factor));

        let dimensions = {
            // x11 only applies constraints when the window is actively resized
            // by the user, so we have to manually apply the initial constraints
            let mut dimensions: (u32, u32) = window_attrs
                .surface_size
                .map(|size| size.to_physical::<u32>(scale_factor))
                .or_else(|| Some((800, 600).into()))
                .map(Into::into)
                .unwrap();
            if let Some(max) = max_surface_size {
                dimensions.0 = cmp::min(dimensions.0, max.0);
                dimensions.1 = cmp::min(dimensions.1, max.1);
            }
            if let Some(min) = min_surface_size {
                dimensions.0 = cmp::max(dimensions.0, min.0);
                dimensions.1 = cmp::max(dimensions.1, min.1);
            }
            debug!("Calculated physical dimensions: {}x{}", dimensions.0, dimensions.1);
            dimensions
        };

        // An iterator over the visuals matching screen id combined with their depths.
        let mut all_visuals = screen
            .allowed_depths
            .iter()
            .flat_map(|depth| depth.visuals.iter().map(move |visual| (visual, depth.depth)));

        // creating
        let (visualtype, depth, require_colormap) = match x11_attributes.visual_id {
            Some(vi) => {
                // Find this specific visual.
                let (visualtype, depth) = all_visuals
                    .find(|(visual, _)| visual.visual_id == vi)
                    .ok_or_else(|| os_error!(X11Error::NoSuchVisual(vi)))?;

                (Some(visualtype), depth, true)
            },
            None if window_attrs.transparent => {
                // Find a suitable visual, true color with 32 bits of depth.
                all_visuals
                    .find_map(|(visual, depth)| {
                        (depth == 32 && visual.class == xproto::VisualClass::TRUE_COLOR)
                            .then_some((Some(visual), depth, true))
                    })
                    .unwrap_or_else(|| {
                        debug!(
                            "Could not set transparency, because XMatchVisualInfo returned zero \
                             for the required parameters"
                        );
                        (None as _, x11rb::COPY_FROM_PARENT as _, false)
                    })
            },
            _ => (None, x11rb::COPY_FROM_PARENT as _, false),
        };
        let mut visual = visualtype.map_or(x11rb::COPY_FROM_PARENT, |v| v.visual_id);

        let window_attributes = {
            use xproto::EventMask;

            let mut aux = xproto::CreateWindowAux::new();
            let event_mask = EventMask::EXPOSURE
                | EventMask::STRUCTURE_NOTIFY
                | EventMask::VISIBILITY_CHANGE
                | EventMask::KEY_PRESS
                | EventMask::KEY_RELEASE
                | EventMask::KEYMAP_STATE
                | EventMask::BUTTON_PRESS
                | EventMask::BUTTON_RELEASE
                | EventMask::POINTER_MOTION
                | EventMask::PROPERTY_CHANGE;

            aux = aux.event_mask(event_mask).border_pixel(0);

            if x11_attributes.override_redirect {
                aux = aux.override_redirect(true as u32);
            }

            // Add a colormap if needed.
            let colormap_visual = match x11_attributes.visual_id {
                Some(vi) => Some(vi),
                None if require_colormap => Some(visual),
                _ => None,
            };

            if let Some(visual) = colormap_visual {
                let colormap = leap!(xconn.xcb_connection().generate_id());
                leap!(xconn.xcb_connection().create_colormap(
                    xproto::ColormapAlloc::NONE,
                    colormap,
                    root,
                    visual,
                ));
                aux = aux.colormap(colormap);
            } else {
                aux = aux.colormap(0);
            }

            aux
        };

        // Figure out the window's parent.
        let parent = x11_attributes.embed_window.unwrap_or(root);

        // finally creating the window
        let xwindow = {
            let (x, y) = position.map_or((0, 0), Into::into);
            let wid = leap!(xconn.xcb_connection().generate_id());
            let result = xconn.xcb_connection().create_window(
                depth,
                wid,
                parent,
                x,
                y,
                dimensions.0.try_into().unwrap(),
                dimensions.1.try_into().unwrap(),
                0,
                xproto::WindowClass::INPUT_OUTPUT,
                visual,
                &window_attributes,
            );
            leap!(leap!(result).check());

            wid
        };

        // The COPY_FROM_PARENT is a special value for the visual used to copy
        // the visual from the parent window, thus we have to query the visual
        // we've got when we built the window above.
        if visual == x11rb::COPY_FROM_PARENT {
            visual = leap!(
                leap!(xconn.xcb_connection().get_window_attributes(xwindow as xproto::Window))
                    .reply()
            )
            .visual;
        }

        #[allow(clippy::mutex_atomic)]
        let mut window = UnownedWindow {
            xconn: Arc::clone(xconn),
            xwindow: xwindow as xproto::Window,
            visual,
            root,
            screen_id,
            sync_counter_id: None,
            selected_cursor: Default::default(),
            cursor_grabbed_mode: Mutex::new(CursorGrabMode::None),
            cursor_visible: Mutex::new(true),
            ime_sender: Mutex::new(event_loop.ime_sender.clone()),
            shared_state: SharedState::new(guessed_monitor, &window_attrs),
            redraw_sender: event_loop.redraw_sender.clone(),
            activation_sender: event_loop.activation_sender.clone(),
        };

        // Title must be set before mapping. Some tiling window managers (i.e. i3) use the window
        // title to determine placement/etc., so doing this after mapping would cause the WM to
        // act on the wrong title state.
        leap!(window.set_title_inner(&window_attrs.title)).ignore_error();
        leap!(window.set_decorations_inner(window_attrs.decorations)).ignore_error();

        if let Some(theme) = window_attrs.preferred_theme {
            leap!(window.set_theme_inner(Some(theme))).ignore_error();
        }

        // Embed the window if needed.
        if x11_attributes.embed_window.is_some() {
            window.embed_window()?;
        }

        {
            // Enable drag and drop (TODO: extend API to make this toggleable)
            {
                let dnd_aware_atom = atoms[XdndAware];
                let version = &[5u32]; // Latest version; hasn't changed since 2002
                leap!(xconn.change_property(
                    window.xwindow,
                    dnd_aware_atom,
                    u32::from(xproto::AtomEnum::ATOM),
                    xproto::PropMode::REPLACE,
                    version,
                ))
                .ignore_error();
            }

            // WM_CLASS must be set *before* mapping the window, as per ICCCM!
            {
                let (instance, class) = if let Some(name) = x11_attributes.name {
                    (name.instance, name.general)
                } else {
                    let class = env::args_os()
                        .next()
                        .as_ref()
                        // Default to the name of the binary (via argv[0])
                        .and_then(|path| Path::new(path).file_name())
                        .and_then(|bin_name| bin_name.to_str())
                        .map(|bin_name| bin_name.to_owned())
                        .unwrap_or_else(|| window_attrs.title.clone());
                    // This environment variable is extraordinarily unlikely to actually be used...
                    let instance = env::var("RESOURCE_NAME").ok().unwrap_or_else(|| class.clone());
                    (instance, class)
                };

                let class = format!("{instance}\0{class}\0");
                leap!(xconn.change_property(
                    window.xwindow,
                    xproto::Atom::from(xproto::AtomEnum::WM_CLASS),
                    xproto::Atom::from(xproto::AtomEnum::STRING),
                    xproto::PropMode::REPLACE,
                    class.as_bytes(),
                ))
                .ignore_error();
            }

            if let Some(flusher) = leap!(window.set_pid()) {
                flusher.ignore_error()
            }

            leap!(window.set_window_types(x11_attributes.x11_window_types)).ignore_error();

            // Set size hints.
            let mut min_surface_size =
                window_attrs.min_surface_size.map(|size| size.to_physical::<u32>(scale_factor));
            let mut max_surface_size =
                window_attrs.max_surface_size.map(|size| size.to_physical::<u32>(scale_factor));

            if !window_attrs.resizable {
                if util::wm_name_is_one_of(&["Xfwm4"]) {
                    warn!("To avoid a WM bug, disabling resizing has no effect on Xfwm4");
                } else {
                    max_surface_size = Some(dimensions.into());
                    min_surface_size = Some(dimensions.into());
                }
            }

            let shared_state = window.shared_state.get_mut().unwrap();
            shared_state.min_surface_size = min_surface_size.map(Into::into);
            shared_state.max_surface_size = max_surface_size.map(Into::into);
            shared_state.surface_resize_increments = window_attrs.surface_resize_increments;
            shared_state.base_size = x11_attributes.base_size;

            let normal_hints = WmSizeHints {
                position: position.map(|PhysicalPosition { x, y }| {
                    (WmSizeHintsSpecification::UserSpecified, x, y)
                }),
                size: Some((
                    WmSizeHintsSpecification::UserSpecified,
                    cast_dimension_to_hint(dimensions.0),
                    cast_dimension_to_hint(dimensions.1),
                )),
                max_size: max_surface_size.map(cast_physical_size_to_hint),
                min_size: min_surface_size.map(cast_physical_size_to_hint),
                size_increment: window_attrs
                    .surface_resize_increments
                    .map(|size| cast_size_to_hint(size, scale_factor)),
                base_size: x11_attributes
                    .base_size
                    .map(|size| cast_size_to_hint(size, scale_factor)),
                aspect: None,
                win_gravity: None,
            };
            leap!(
                leap!(normal_hints.set(
                    xconn.xcb_connection(),
                    window.xwindow as xproto::Window,
                    xproto::AtomEnum::WM_NORMAL_HINTS,
                ))
                .check()
            );

            // Set window icons
            if let Some(icon) =
                window_attrs.window_icon.as_ref().and_then(|icon| icon.cast_ref::<RgbaIcon>())
            {
                leap!(window.set_icon_inner(icon)).ignore_error();
            }

            // 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,
                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();
            }

            // Select XInput2 events
            let mask = xinput::XIEventMask::MOTION
                | xinput::XIEventMask::BUTTON_PRESS
                | xinput::XIEventMask::BUTTON_RELEASE
                | xinput::XIEventMask::ENTER
                | xinput::XIEventMask::LEAVE
                | xinput::XIEventMask::FOCUS_IN
                | xinput::XIEventMask::FOCUS_OUT
                | xinput::XIEventMask::TOUCH_BEGIN
                | xinput::XIEventMask::TOUCH_UPDATE
                | xinput::XIEventMask::TOUCH_END;
            leap!(xconn.select_xinput_events(window.xwindow, ALL_MASTER_DEVICES, mask))
                .ignore_error();

            // Set visibility (map window)
            if window_attrs.visible {
                leap!(xconn.xcb_connection().map_window(window.xwindow)).ignore_error();
                leap!(xconn.xcb_connection().configure_window(
                    xwindow,
                    &xproto::ConfigureWindowAux::new().stack_mode(xproto::StackMode::ABOVE)
                ))
                .ignore_error();
            }

            // Attempt to make keyboard input repeat detectable
            unsafe {
                let mut supported_ptr = ffi::False;
                (xconn.xlib.XkbSetDetectableAutoRepeat)(
                    xconn.display,
                    ffi::True,
                    &mut supported_ptr,
                );
                if supported_ptr == ffi::False {
                    return Err(os_error!("`XkbSetDetectableAutoRepeat` failed").into());
                }
            }

            // Try to create input context for the window.
            if let Some(ime) = event_loop.ime.as_ref() {
                ime.borrow_mut()
                    .create_context(window.xwindow as ffi::Window, false)
                    .map_err(|err| os_error!(err))?;
            }

            // These properties must be set after mapping
            if window_attrs.maximized {
                leap!(window.set_maximized_inner(window_attrs.maximized)).ignore_error();
            }

            if window_attrs.fullscreen.is_some() {
                if let Some(flusher) =
                    leap!(window.set_fullscreen_inner(window_attrs.fullscreen.clone()))
                {
                    flusher.ignore_error()
                }

                if let Some(PhysicalPosition { x, y }) = position {
                    let shared_state = window.shared_state.get_mut().unwrap();

                    shared_state.restore_position = Some((x, y));
                }
            }

            leap!(window.set_window_level_inner(window_attrs.window_level)).ignore_error();
        }

        window.set_cursor(window_attrs.cursor);

        // Remove the startup notification if we have one.
        if let Some(startup) = x11_attributes.activation_token.as_ref() {
            leap!(xconn.remove_activation_token(xwindow, startup.as_raw()));
        }

        // We never want to give the user a broken window, since by then, it's too late to handle.
        let window = leap!(xconn.sync_with_server().map(|_| window));

        Ok(window)
    }

    /// Embed this window into a parent window.
    pub(super) fn embed_window(&self) -> Result<(), RequestError> {
        let atoms = self.xconn.atoms();
        leap!(
            leap!(self.xconn.change_property(
                self.xwindow,
                atoms[_XEMBED],
                atoms[_XEMBED],
                xproto::PropMode::REPLACE,
                &[0u32, 1u32],
            ))
            .check()
        );

        Ok(())
    }

    pub(super) fn shared_state_lock(&self) -> MutexGuard<'_, SharedState> {
        self.shared_state.lock().unwrap()
    }

    fn set_pid(&self) -> Result<Option<VoidCookie<'_>>, X11Error> {
        let atoms = self.xconn.atoms();
        let pid_atom = atoms[_NET_WM_PID];
        let client_machine_atom = atoms[WM_CLIENT_MACHINE];

        // Get the hostname and the PID.
        let uname = rustix::system::uname();
        let pid = rustix::process::getpid();

        self.xconn
            .change_property(
                self.xwindow,
                pid_atom,
                xproto::Atom::from(xproto::AtomEnum::CARDINAL),
                xproto::PropMode::REPLACE,
                &[pid.as_raw_nonzero().get() as util::Cardinal],
            )?
            .ignore_error();
        let flusher = self.xconn.change_property(
            self.xwindow,
            client_machine_atom,
            xproto::Atom::from(xproto::AtomEnum::STRING),
            xproto::PropMode::REPLACE,
            uname.nodename().to_bytes(),
        );
        flusher.map(Some)
    }

    fn set_window_types(&self, window_types: Vec<WindowType>) -> Result<VoidCookie<'_>, X11Error> {
        let atoms = self.xconn.atoms();
        let hint_atom = atoms[_NET_WM_WINDOW_TYPE];
        let atoms: Vec<_> = window_types.iter().map(|t| t.as_atom(&self.xconn)).collect();

        self.xconn.change_property(
            self.xwindow,
            hint_atom,
            xproto::Atom::from(xproto::AtomEnum::ATOM),
            xproto::PropMode::REPLACE,
            &atoms,
        )
    }

    pub fn set_theme_inner(&self, theme: Option<Theme>) -> Result<VoidCookie<'_>, X11Error> {
        let atoms = self.xconn.atoms();
        let hint_atom = atoms[_GTK_THEME_VARIANT];
        let utf8_atom = atoms[UTF8_STRING];
        let variant = match theme {
            Some(Theme::Dark) => "dark",
            Some(Theme::Light) => "light",
            None => "dark",
        };
        let variant = CString::new(variant).expect("`_GTK_THEME_VARIANT` contained null byte");
        self.xconn.change_property(
            self.xwindow,
            hint_atom,
            utf8_atom,
            xproto::PropMode::REPLACE,
            variant.as_bytes(),
        )
    }

    #[inline]
    pub fn set_theme(&self, theme: Option<Theme>) {
        self.set_theme_inner(theme).expect("Failed to change window theme").ignore_error();

        self.xconn.flush_requests().expect("Failed to change window theme");
    }

    fn set_netwm(
        &self,
        operation: util::StateOperation,
        properties: (u32, u32, u32, u32),
    ) -> Result<VoidCookie<'_>, X11Error> {
        let atoms = self.xconn.atoms();
        let state_atom = atoms[_NET_WM_STATE];
        self.xconn.send_client_msg(
            self.xwindow,
            self.root,
            state_atom,
            Some(xproto::EventMask::SUBSTRUCTURE_REDIRECT | xproto::EventMask::SUBSTRUCTURE_NOTIFY),
            [operation as u32, properties.0, properties.1, properties.2, properties.3],
        )
    }

    fn set_fullscreen_hint(&self, fullscreen: bool) -> Result<VoidCookie<'_>, X11Error> {
        let atoms = self.xconn.atoms();
        let fullscreen_atom = atoms[_NET_WM_STATE_FULLSCREEN];
        let flusher = self.set_netwm(fullscreen.into(), (fullscreen_atom, 0, 0, 0));

        if fullscreen {
            // Ensure that the fullscreen window receives input focus to prevent
            // locking up the user's display.
            self.xconn
                .xcb_connection()
                .set_input_focus(xproto::InputFocus::PARENT, self.xwindow, x11rb::CURRENT_TIME)?
                .ignore_error();
        }

        flusher
    }

    fn set_fullscreen_inner(
        &self,
        fullscreen: Option<Fullscreen>,
    ) -> Result<Option<VoidCookie<'_>>, X11Error> {
        let mut shared_state_lock = self.shared_state_lock();

        match shared_state_lock.visibility {
            // Setting fullscreen on a window that is not visible will generate an error.
            Visibility::No | Visibility::YesWait => {
                shared_state_lock.desired_fullscreen = Some(fullscreen);
                return Ok(None);
            },
            Visibility::Yes => (),
        }

        let old_fullscreen = shared_state_lock.fullscreen.clone();
        if old_fullscreen == fullscreen {
            return Ok(None);
        }
        shared_state_lock.fullscreen.clone_from(&fullscreen);

        match (&old_fullscreen, &fullscreen) {
            // Store the desktop video mode before entering exclusive
            // fullscreen, so we can restore it upon exit, as XRandR does not
            // provide a mechanism to set this per app-session or restore this
            // to the desktop video mode as macOS and Windows do
            (&None, &Some(Fullscreen::Exclusive(ref monitor, _)))
            | (&Some(Fullscreen::Borderless(_)), &Some(Fullscreen::Exclusive(ref monitor, _))) => {
                let id = monitor.native_id() as _;
                shared_state_lock.desktop_video_mode = Some((
                    id,
                    self.xconn.get_crtc_mode(id).expect("Failed to get desktop video mode"),
                ));
            },
            // Restore desktop video mode upon exiting exclusive fullscreen
            (&Some(Fullscreen::Exclusive(..)), &None)
            | (&Some(Fullscreen::Exclusive(..)), &Some(Fullscreen::Borderless(_))) => {
                let (monitor_id, mode_id) = shared_state_lock.desktop_video_mode.take().unwrap();
                self.xconn
                    .set_crtc_config(monitor_id, mode_id)
                    .expect("failed to restore desktop video mode");
            },
            _ => (),
        }

        drop(shared_state_lock);

        match fullscreen {
            None => {
                let flusher = self.set_fullscreen_hint(false);
                let mut shared_state_lock = self.shared_state_lock();
                if let Some(position) = shared_state_lock.restore_position.take() {
                    drop(shared_state_lock);
                    self.set_position_inner(position.0, position.1)
                        .expect_then_ignore_error("Failed to restore window position");
                }
                flusher.map(Some)
            },
            Some(fullscreen) => {
                let (monitor, video_mode): (Cow<'_, X11MonitorHandle>, Option<&VideoMode>) =
                    match &fullscreen {
                        Fullscreen::Exclusive(monitor, video_mode) => {
                            let monitor = monitor.cast_ref::<X11MonitorHandle>().unwrap();
                            (Cow::Borrowed(monitor), Some(video_mode))
                        },
                        Fullscreen::Borderless(Some(monitor)) => {
                            let monitor = monitor.cast_ref::<X11MonitorHandle>().unwrap();
                            (Cow::Borrowed(monitor), None)
                        },
                        _ => (Cow::Owned(self.shared_state_lock().last_monitor.clone()), None),
                    };

                // Don't set fullscreen on an invalid dummy monitor handle
                if monitor.is_dummy() {
                    return Ok(None);
                }

                if let Some(native_mode) = video_mode.and_then(|requested| {
                    monitor.video_modes.iter().find_map(|mode| {
                        if &mode.mode == requested { Some(mode.native_mode) } else { None }
                    })
                }) {
                    // FIXME: this is actually not correct if we're setting the
                    // video mode to a resolution higher than the current
                    // desktop resolution, because XRandR does not automatically
                    // reposition the monitors to the right and below this
                    // monitor.
                    //
                    // What ends up happening is we will get the fullscreen
                    // window showing up on those monitors as well, because
                    // their virtual position now overlaps with the monitor that
                    // we just made larger..
                    //
                    // It'd be quite a bit of work to handle this correctly (and
                    // nobody else seems to bother doing this correctly either),
                    // so we're just leaving this broken. Fixing this would
                    // involve storing all CRTCs upon entering fullscreen,
                    // restoring them upon exit, and after entering fullscreen,
                    // repositioning displays to the right and below this
                    // display. I think there would still be edge cases that are
                    // difficult or impossible to handle correctly, e.g. what if
                    // a new monitor was plugged in while in fullscreen?
                    //
                    // I think we might just want to disallow setting the video
                    // mode higher than the current desktop video mode (I'm sure
                    // this will make someone unhappy, but it's very unusual for
                    // games to want to do this anyway).
                    self.xconn
                        .set_crtc_config(monitor.native_id() as _, native_mode)
                        .expect("failed to set video mode");
                }

                let window_position = self.outer_position_physical();
                self.shared_state_lock().restore_position = Some(window_position);
                let monitor_origin: (i32, i32) = monitor.position;
                self.set_position_inner(monitor_origin.0, monitor_origin.1)
                    .expect_then_ignore_error("Failed to set window position");
                self.set_fullscreen_hint(true).map(Some)
            },
        }
    }

    #[inline]
    pub(crate) fn fullscreen(&self) -> Option<Fullscreen> {
        let shared_state = self.shared_state_lock();

        shared_state.desired_fullscreen.clone().unwrap_or_else(|| shared_state.fullscreen.clone())
    }

    #[inline]
    pub(crate) fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
        if let Some(flusher) =
            self.set_fullscreen_inner(fullscreen).expect("Failed to change window fullscreen state")
        {
            flusher.check().expect("Failed to change window fullscreen state");
            self.invalidate_cached_frame_extents();
        }
    }

    // Called by EventProcessor when a VisibilityNotify event is received
    pub(crate) fn visibility_notify(&self) {
        let mut shared_state = self.shared_state_lock();

        match shared_state.visibility {
            Visibility::No => self
                .xconn
                .xcb_connection()
                .unmap_window(self.xwindow)
                .expect_then_ignore_error("Failed to unmap window"),
            Visibility::Yes => (),
            Visibility::YesWait => {
                shared_state.visibility = Visibility::Yes;

                if let Some(fullscreen) = shared_state.desired_fullscreen.take() {
                    drop(shared_state);
                    self.set_fullscreen(fullscreen);
                }
            },
        }
    }

    pub fn current_monitor(&self) -> Option<X11MonitorHandle> {
        Some(self.shared_state_lock().last_monitor.clone())
    }

    pub fn available_monitors(&self) -> Vec<X11MonitorHandle> {
        self.xconn.available_monitors().expect("Failed to get available monitors")
    }

    pub fn primary_monitor(&self) -> Option<X11MonitorHandle> {
        Some(self.xconn.primary_monitor().expect("Failed to get primary monitor"))
    }

    #[inline]
    pub fn is_minimized(&self) -> Option<bool> {
        let atoms = self.xconn.atoms();
        let state_atom = atoms[_NET_WM_STATE];
        let state = self.xconn.get_property(
            self.xwindow,
            state_atom,
            xproto::Atom::from(xproto::AtomEnum::ATOM),
        );
        let hidden_atom = atoms[_NET_WM_STATE_HIDDEN];

        Some(match state {
            Ok(atoms) => {
                atoms.iter().any(|atom: &xproto::Atom| *atom as xproto::Atom == hidden_atom)
            },
            _ => false,
        })
    }

    /// Refresh the API for the given monitor.
    #[inline]
    pub(super) fn refresh_dpi_for_monitor(
        &self,
        new_monitor: &X11MonitorHandle,
        maybe_prev_scale_factor: Option<f64>,
        app: &mut dyn ApplicationHandler,
        event_loop: &ActiveEventLoop,
    ) {
        // Check if the self is on this monitor
        let monitor = self.shared_state_lock().last_monitor.clone();
        if monitor.name == new_monitor.name {
            let (width, height) = self.surface_size_physical();
            let (new_width, new_height) = self.adjust_for_dpi(
                // If we couldn't determine the previous scale
                // factor (e.g., because all monitors were closed
                // before), just pick whatever the current monitor
                // has set as a baseline.
                maybe_prev_scale_factor.unwrap_or(monitor.scale_factor),
                new_monitor.scale_factor,
                width,
                height,
                &self.shared_state_lock(),
            );

            let old_surface_size = PhysicalSize::new(width, height);
            let surface_size = Arc::new(Mutex::new(PhysicalSize::new(new_width, new_height)));
            app.window_event(event_loop, self.id(), WindowEvent::ScaleFactorChanged {
                scale_factor: new_monitor.scale_factor,
                surface_size_writer: SurfaceSizeWriter::new(Arc::downgrade(&surface_size)),
            });

            let new_surface_size = *surface_size.lock().unwrap();
            drop(surface_size);

            if new_surface_size != old_surface_size {
                let (new_width, new_height) = new_surface_size.into();
                self.request_surface_size_physical(new_width, new_height);
            }
        }
    }

    fn set_minimized_inner(&self, minimized: bool) -> Result<VoidCookie<'_>, X11Error> {
        let atoms = self.xconn.atoms();

        if minimized {
            let root_window = self.xconn.default_root().root;

            self.xconn.send_client_msg(
                self.xwindow,
                root_window,
                atoms[WM_CHANGE_STATE],
                Some(
                    xproto::EventMask::SUBSTRUCTURE_REDIRECT
                        | xproto::EventMask::SUBSTRUCTURE_NOTIFY,
                ),
                [3u32, 0, 0, 0, 0],
            )
        } else {
            self.xconn.send_client_msg(
                self.xwindow,
                self.root,
                atoms[_NET_ACTIVE_WINDOW],
                Some(
                    xproto::EventMask::SUBSTRUCTURE_REDIRECT
                        | xproto::EventMask::SUBSTRUCTURE_NOTIFY,
                ),
                [1, x11rb::CURRENT_TIME, 0, 0, 0],
            )
        }
    }

    #[inline]
    pub fn set_minimized(&self, minimized: bool) {
        self.set_minimized_inner(minimized)
            .expect_then_ignore_error("Failed to change window minimization");

        self.xconn.flush_requests().expect("Failed to change window minimization");
    }

    #[inline]
    pub fn is_maximized(&self) -> bool {
        let atoms = self.xconn.atoms();
        let state_atom = atoms[_NET_WM_STATE];
        let state = self.xconn.get_property(
            self.xwindow,
            state_atom,
            xproto::Atom::from(xproto::AtomEnum::ATOM),
        );
        let horz_atom = atoms[_NET_WM_STATE_MAXIMIZED_HORZ];
        let vert_atom = atoms[_NET_WM_STATE_MAXIMIZED_VERT];
        match state {
            Ok(atoms) => {
                let horz_maximized = atoms.contains(&horz_atom);
                let vert_maximized = atoms.contains(&vert_atom);
                horz_maximized && vert_maximized
            },
            _ => false,
        }
    }

    fn set_maximized_inner(&self, maximized: bool) -> Result<VoidCookie<'_>, X11Error> {
        let atoms = self.xconn.atoms();
        let horz_atom = atoms[_NET_WM_STATE_MAXIMIZED_HORZ];
        let vert_atom = atoms[_NET_WM_STATE_MAXIMIZED_VERT];

        self.set_netwm(maximized.into(), (horz_atom, vert_atom, 0, 0))
    }

    #[inline]
    pub fn set_maximized(&self, maximized: bool) {
        self.set_maximized_inner(maximized)
            .expect_then_ignore_error("Failed to change window maximization");
        self.xconn.flush_requests().expect("Failed to change window maximization");
        self.invalidate_cached_frame_extents();
    }

    fn set_title_inner(&self, title: &str) -> Result<VoidCookie<'_>, X11Error> {
        let atoms = self.xconn.atoms();

        let title = CString::new(title).expect("Window title contained null byte");
        self.xconn
            .change_property(
                self.xwindow,
                xproto::Atom::from(xproto::AtomEnum::WM_NAME),
                xproto::Atom::from(xproto::AtomEnum::STRING),
                xproto::PropMode::REPLACE,
                title.as_bytes(),
            )?
            .ignore_error();
        self.xconn.change_property(
            self.xwindow,
            atoms[_NET_WM_NAME],
            atoms[UTF8_STRING],
            xproto::PropMode::REPLACE,
            title.as_bytes(),
        )
    }

    #[inline]
    pub fn set_title(&self, title: &str) {
        self.set_title_inner(title).expect_then_ignore_error("Failed to set window title");

        self.xconn.flush_requests().expect("Failed to set window title");
    }

    #[inline]
    pub fn set_transparent(&self, _transparent: bool) {}

    #[inline]
    pub fn set_blur(&self, _blur: bool) {}

    fn set_decorations_inner(&self, decorations: bool) -> Result<VoidCookie<'_>, X11Error> {
        self.shared_state_lock().is_decorated = decorations;
        let mut hints = self.xconn.get_motif_hints(self.xwindow);

        hints.set_decorations(decorations);

        self.xconn.set_motif_hints(self.xwindow, &hints)
    }

    #[inline]
    pub fn set_decorations(&self, decorations: bool) {
        self.set_decorations_inner(decorations)
            .expect_then_ignore_error("Failed to set decoration state");
        self.xconn.flush_requests().expect("Failed to set decoration state");
        self.invalidate_cached_frame_extents();
    }

    #[inline]
    pub fn is_decorated(&self) -> bool {
        self.shared_state_lock().is_decorated
    }

    fn set_maximizable_inner(&self, maximizable: bool) -> Result<VoidCookie<'_>, X11Error> {
        let mut hints = self.xconn.get_motif_hints(self.xwindow);

        hints.set_maximizable(maximizable);

        self.xconn.set_motif_hints(self.xwindow, &hints)
    }

    fn toggle_atom(&self, atom_name: AtomName, enable: bool) -> Result<VoidCookie<'_>, X11Error> {
        let atoms = self.xconn.atoms();
        let atom = atoms[atom_name];
        self.set_netwm(enable.into(), (atom, 0, 0, 0))
    }

    fn set_window_level_inner(&self, level: WindowLevel) -> Result<VoidCookie<'_>, X11Error> {
        self.toggle_atom(_NET_WM_STATE_ABOVE, level == WindowLevel::AlwaysOnTop)?.ignore_error();
        self.toggle_atom(_NET_WM_STATE_BELOW, level == WindowLevel::AlwaysOnBottom)
    }

    #[inline]
    pub fn set_window_level(&self, level: WindowLevel) {
        self.set_window_level_inner(level)
            .expect_then_ignore_error("Failed to set window-level state");
        self.xconn.flush_requests().expect("Failed to set window-level state");
    }

    fn set_icon_inner(&self, icon: &RgbaIcon) -> Result<VoidCookie<'_>, X11Error> {
        let atoms = self.xconn.atoms();
        let icon_atom = atoms[_NET_WM_ICON];
        let data = rgba_to_cardinals(icon);
        self.xconn.change_property(
            self.xwindow,
            icon_atom,
            xproto::Atom::from(xproto::AtomEnum::CARDINAL),
            xproto::PropMode::REPLACE,
            data.as_slice(),
        )
    }

    fn unset_icon_inner(&self) -> Result<VoidCookie<'_>, X11Error> {
        let atoms = self.xconn.atoms();
        let icon_atom = atoms[_NET_WM_ICON];
        let empty_data: [util::Cardinal; 0] = [];
        self.xconn.change_property(
            self.xwindow,
            icon_atom,
            xproto::Atom::from(xproto::AtomEnum::CARDINAL),
            xproto::PropMode::REPLACE,
            &empty_data,
        )
    }

    #[inline]
    pub(crate) fn set_window_icon(&self, icon: Option<&RgbaIcon>) {
        match icon {
            Some(icon) => self.set_icon_inner(icon),
            None => self.unset_icon_inner(),
        }
        .expect_then_ignore_error("Failed to set icons");

        self.xconn.flush_requests().expect("Failed to set icons");
    }

    #[inline]
    pub fn set_visible(&self, visible: bool) {
        let mut shared_state = self.shared_state_lock();

        match (visible, shared_state.visibility) {
            (true, Visibility::Yes) | (true, Visibility::YesWait) | (false, Visibility::No) => {
                return;
            },
            _ => (),
        }

        if visible {
            self.xconn
                .xcb_connection()
                .map_window(self.xwindow)
                .expect_then_ignore_error("Failed to call `xcb_map_window`");
            self.xconn
                .xcb_connection()
                .configure_window(
                    self.xwindow,
                    &xproto::ConfigureWindowAux::new().stack_mode(xproto::StackMode::ABOVE),
                )
                .expect_then_ignore_error("Failed to call `xcb_configure_window`");
            self.xconn.flush_requests().expect("Failed to call XMapRaised");
            shared_state.visibility = Visibility::YesWait;
        } else {
            self.xconn
                .xcb_connection()
                .unmap_window(self.xwindow)
                .expect_then_ignore_error("Failed to call `xcb_unmap_window`");
            self.xconn.flush_requests().expect("Failed to call XUnmapWindow");
            shared_state.visibility = Visibility::No;
        }
    }

    #[inline]
    pub fn is_visible(&self) -> Option<bool> {
        Some(self.shared_state_lock().visibility == Visibility::Yes)
    }

    fn update_cached_frame_extents(&self) {
        let extents = self.xconn.get_frame_extents_heuristic(self.xwindow, self.root);
        self.shared_state_lock().frame_extents = Some(extents);
    }

    pub(crate) fn invalidate_cached_frame_extents(&self) {
        self.shared_state_lock().frame_extents.take();
    }

    pub(crate) fn outer_position_physical(&self) -> (i32, i32) {
        let extents = self.shared_state_lock().frame_extents.clone();
        if let Some(extents) = extents {
            let (x, y) = self.inner_position_physical();
            extents.inner_pos_to_outer(x, y)
        } else {
            self.update_cached_frame_extents();
            self.outer_position_physical()
        }
    }

    #[inline]
    pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, RequestError> {
        let extents = self.shared_state_lock().frame_extents.clone();
        if let Some(extents) = extents {
            let (x, y) = self.inner_position_physical();
            Ok(extents.inner_pos_to_outer(x, y).into())
        } else {
            self.update_cached_frame_extents();
            self.outer_position()
        }
    }

    fn inner_position_physical(&self) -> (i32, i32) {
        // This should be okay to unwrap since the only error XTranslateCoordinates can return
        // is BadWindow, and if the window handle is bad we have bigger problems.
        self.xconn
            .translate_coords_root(self.xwindow, self.root)
            .map(|coords| (coords.dst_x.into(), coords.dst_y.into()))
            .unwrap()
    }

    #[inline]
    pub fn surface_position(&self) -> PhysicalPosition<i32> {
        let extents = self.shared_state_lock().frame_extents.clone();
        if let Some(extents) = extents {
            extents.surface_position().into()
        } else {
            self.update_cached_frame_extents();
            self.surface_position()
        }
    }

    pub(crate) fn set_position_inner(
        &self,
        mut x: i32,
        mut y: i32,
    ) -> Result<VoidCookie<'_>, X11Error> {
        // There are a few WMs that set client area position rather than window position, so
        // we'll translate for consistency.
        if util::wm_name_is_one_of(&["Enlightenment", "FVWM"]) {
            let extents = self.shared_state_lock().frame_extents.clone();
            if let Some(extents) = extents {
                x += cast_dimension_to_hint(extents.frame_extents.left);
                y += cast_dimension_to_hint(extents.frame_extents.top);
            } else {
                self.update_cached_frame_extents();
                return self.set_position_inner(x, y);
            }
        }

        self.xconn
            .xcb_connection()
            .configure_window(self.xwindow, &xproto::ConfigureWindowAux::new().x(x).y(y))
            .map_err(Into::into)
    }

    pub(crate) fn set_position_physical(&self, x: i32, y: i32) {
        self.set_position_inner(x, y).expect_then_ignore_error("Failed to call `XMoveWindow`");
    }

    #[inline]
    pub fn set_outer_position(&self, position: Position) {
        let (x, y) = position.to_physical::<i32>(self.scale_factor()).into();
        self.set_position_physical(x, y);
    }

    pub(crate) fn surface_size_physical(&self) -> (u32, u32) {
        // This should be okay to unwrap since the only error XGetGeometry can return
        // is BadWindow, and if the window handle is bad we have bigger problems.
        self.xconn
            .get_geometry(self.xwindow)
            .map(|geo| (geo.width.into(), geo.height.into()))
            .unwrap()
    }

    #[inline]
    pub fn surface_size(&self) -> PhysicalSize<u32> {
        self.surface_size_physical().into()
    }

    #[inline]
    pub fn outer_size(&self) -> PhysicalSize<u32> {
        let extents = self.shared_state_lock().frame_extents.clone();
        if let Some(extents) = extents {
            let (width, height) = self.surface_size_physical();
            extents.surface_size_to_outer(width, height).into()
        } else {
            self.update_cached_frame_extents();
            self.outer_size()
        }
    }

    fn safe_area(&self) -> PhysicalInsets<u32> {
        PhysicalInsets::new(0, 0, 0, 0)
    }

    pub(crate) fn request_surface_size_physical(&self, width: u32, height: u32) {
        self.xconn
            .xcb_connection()
            .configure_window(
                self.xwindow,
                &xproto::ConfigureWindowAux::new().width(width).height(height),
            )
            .expect_then_ignore_error("Failed to call `xcb_configure_window`");
        self.xconn.flush_requests().expect("Failed to call XResizeWindow");
        // cursor_hittest needs to be reapplied after each window resize.
        if self.shared_state_lock().cursor_hittest.unwrap_or(false) {
            let _ = self.set_cursor_hittest(true);
        }
    }

    #[inline]
    pub fn request_surface_size(&self, size: Size) -> Option<PhysicalSize<u32>> {
        let scale_factor = self.scale_factor();
        let size = size.to_physical::<u32>(scale_factor).into();
        if !self.shared_state_lock().is_resizable {
            self.update_normal_hints(|normal_hints| {
                normal_hints.min_size = Some(size);
                normal_hints.max_size = Some(size);
            })
            .expect("Failed to call `XSetWMNormalHints`");
        }
        self.request_surface_size_physical(size.0 as u32, size.1 as u32);

        None
    }

    fn update_normal_hints<F>(&self, callback: F) -> Result<(), X11Error>
    where
        F: FnOnce(&mut WmSizeHints),
    {
        let mut normal_hints = WmSizeHints::get(
            self.xconn.xcb_connection(),
            self.xwindow as xproto::Window,
            xproto::AtomEnum::WM_NORMAL_HINTS,
        )?
        .reply()?
        .unwrap_or_default();
        callback(&mut normal_hints);
        normal_hints
            .set(
                self.xconn.xcb_connection(),
                self.xwindow as xproto::Window,
                xproto::AtomEnum::WM_NORMAL_HINTS,
            )?
            .ignore_error();
        Ok(())
    }

    pub(crate) fn set_min_surface_size_physical(&self, dimensions: Option<(u32, u32)>) {
        self.update_normal_hints(|normal_hints| {
            normal_hints.min_size =
                dimensions.map(|(w, h)| (cast_dimension_to_hint(w), cast_dimension_to_hint(h)))
        })
        .expect("Failed to call `XSetWMNormalHints`");
    }

    #[inline]
    pub fn set_min_surface_size(&self, dimensions: Option<Size>) {
        self.shared_state_lock().min_surface_size = dimensions;
        let physical_dimensions =
            dimensions.map(|dimensions| dimensions.to_physical::<u32>(self.scale_factor()).into());
        self.set_min_surface_size_physical(physical_dimensions);
    }

    pub(crate) fn set_max_surface_size_physical(&self, dimensions: Option<(u32, u32)>) {
        self.update_normal_hints(|normal_hints| {
            normal_hints.max_size =
                dimensions.map(|(w, h)| (cast_dimension_to_hint(w), cast_dimension_to_hint(h)))
        })
        .expect("Failed to call `XSetWMNormalHints`");
    }

    #[inline]
    pub fn set_max_surface_size(&self, dimensions: Option<Size>) {
        self.shared_state_lock().max_surface_size = dimensions;
        let physical_dimensions =
            dimensions.map(|dimensions| dimensions.to_physical::<u32>(self.scale_factor()).into());
        self.set_max_surface_size_physical(physical_dimensions);
    }

    #[inline]
    pub fn surface_resize_increments(&self) -> Option<PhysicalSize<u32>> {
        WmSizeHints::get(
            self.xconn.xcb_connection(),
            self.xwindow as xproto::Window,
            xproto::AtomEnum::WM_NORMAL_HINTS,
        )
        .ok()
        .and_then(|cookie| cookie.reply().ok())
        .flatten()
        .and_then(|hints| hints.size_increment)
        .map(|(width, height)| (width as u32, height as u32).into())
    }

    #[inline]
    pub fn set_surface_resize_increments(&self, increments: Option<Size>) {
        self.shared_state_lock().surface_resize_increments = increments;
        let physical_increments =
            increments.map(|increments| cast_size_to_hint(increments, self.scale_factor()));
        self.update_normal_hints(|hints| hints.size_increment = physical_increments)
            .expect("Failed to call `XSetWMNormalHints`");
    }

    pub(crate) fn adjust_for_dpi(
        &self,
        old_scale_factor: f64,
        new_scale_factor: f64,
        width: u32,
        height: u32,
        shared_state: &SharedState,
    ) -> (u32, u32) {
        let scale_factor = new_scale_factor / old_scale_factor;
        self.update_normal_hints(|normal_hints| {
            let dpi_adjuster = |size: Size| -> (i32, i32) { cast_size_to_hint(size, scale_factor) };
            let max_size = shared_state.max_surface_size.map(dpi_adjuster);
            let min_size = shared_state.min_surface_size.map(dpi_adjuster);
            let surface_resize_increments =
                shared_state.surface_resize_increments.map(dpi_adjuster);
            let base_size = shared_state.base_size.map(dpi_adjuster);

            normal_hints.max_size = max_size;
            normal_hints.min_size = min_size;
            normal_hints.size_increment = surface_resize_increments;
            normal_hints.base_size = base_size;
        })
        .expect("Failed to update normal hints");

        let new_width = (width as f64 * scale_factor).round() as u32;
        let new_height = (height as f64 * scale_factor).round() as u32;

        (new_width, new_height)
    }

    pub fn set_resizable(&self, resizable: bool) {
        if util::wm_name_is_one_of(&["Xfwm4"]) {
            // Making the window unresizable on Xfwm prevents further changes to `WM_NORMAL_HINTS`
            // from being detected. This makes it impossible for resizing to be
            // re-enabled, and also breaks DPI scaling. As such, we choose the lesser of
            // two evils and do nothing.
            warn!("To avoid a WM bug, disabling resizing has no effect on Xfwm4");
            return;
        }

        let (min_size, max_size) = if resizable {
            let shared_state_lock = self.shared_state_lock();
            (shared_state_lock.min_surface_size, shared_state_lock.max_surface_size)
        } else {
            let window_size = Some(Size::from(self.surface_size()));
            (window_size, window_size)
        };
        self.shared_state_lock().is_resizable = resizable;

        self.set_maximizable_inner(resizable)
            .expect_then_ignore_error("Failed to call `XSetWMNormalHints`");

        let scale_factor = self.scale_factor();
        let min_surface_size = min_size.map(|size| cast_size_to_hint(size, scale_factor));
        let max_surface_size = max_size.map(|size| cast_size_to_hint(size, scale_factor));
        self.update_normal_hints(|normal_hints| {
            normal_hints.min_size = min_surface_size;
            normal_hints.max_size = max_surface_size;
        })
        .expect("Failed to call `XSetWMNormalHints`");
    }

    #[inline]
    pub fn is_resizable(&self) -> bool {
        self.shared_state_lock().is_resizable
    }

    #[inline]
    pub fn set_enabled_buttons(&self, _buttons: WindowButtons) {}

    #[inline]
    pub fn enabled_buttons(&self) -> WindowButtons {
        WindowButtons::all()
    }

    #[allow(dead_code)]
    #[inline]
    pub fn xlib_display(&self) -> *mut c_void {
        self.xconn.display as _
    }

    #[allow(dead_code)]
    #[inline]
    pub fn xlib_window(&self) -> c_ulong {
        self.xwindow as ffi::Window
    }

    #[inline]
    pub fn set_cursor(&self, cursor: Cursor) {
        match cursor {
            Cursor::Icon(icon) => {
                let old_cursor = replace(
                    &mut *self.selected_cursor.lock().unwrap(),
                    SelectedCursor::Named(icon),
                );

                #[allow(clippy::mutex_atomic)]
                if SelectedCursor::Named(icon) != old_cursor && *self.cursor_visible.lock().unwrap()
                {
                    if let Err(err) = self.xconn.set_cursor_icon(self.xwindow, Some(icon)) {
                        tracing::error!("failed to set cursor icon: {err}");
                    }
                }
            },
            Cursor::Custom(cursor) => {
                let cursor = match cursor.cast_ref::<CustomCursor>() {
                    Some(cursor) => cursor,
                    None => {
                        tracing::error!("unrecognized cursor passed to X11 backend");
                        return;
                    },
                };

                #[allow(clippy::mutex_atomic)]
                if *self.cursor_visible.lock().unwrap() {
                    if let Err(err) = self.xconn.set_custom_cursor(self.xwindow, cursor) {
                        tracing::error!("failed to set window icon: {err}");
                    }
                }

                *self.selected_cursor.lock().unwrap() = SelectedCursor::Custom(cursor.clone());
            },
        }
    }

    #[inline]
    pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), RequestError> {
        // We don't support the locked cursor yet, so ignore it early on.
        if mode == CursorGrabMode::Locked {
            return Err(NotSupportedError::new("locked cursor is not implemented on X11").into());
        }

        let mut grabbed_lock = self.cursor_grabbed_mode.lock().unwrap();
        if mode == *grabbed_lock {
            return Ok(());
        }

        // We ungrab before grabbing to prevent passive grabs from causing `AlreadyGrabbed`.
        // Therefore, this is common to both codepaths.
        self.xconn
            .xcb_connection()
            .ungrab_pointer(x11rb::CURRENT_TIME)
            .expect_then_ignore_error("Failed to call `xcb_ungrab_pointer`");
        *grabbed_lock = CursorGrabMode::None;

        let result = match mode {
            CursorGrabMode::None => self
                .xconn
                .flush_requests()
                .map_err(|err| RequestError::Os(os_error!(X11Error::Xlib(err)))),
            CursorGrabMode::Confined => {
                let result = self
                    .xconn
                    .xcb_connection()
                    .grab_pointer(
                        true as _,
                        self.xwindow,
                        xproto::EventMask::BUTTON_PRESS
                            | xproto::EventMask::BUTTON_RELEASE
                            | xproto::EventMask::ENTER_WINDOW
                            | xproto::EventMask::LEAVE_WINDOW
                            | xproto::EventMask::POINTER_MOTION
                            | xproto::EventMask::POINTER_MOTION_HINT
                            | xproto::EventMask::BUTTON1_MOTION
                            | xproto::EventMask::BUTTON2_MOTION
                            | xproto::EventMask::BUTTON3_MOTION
                            | xproto::EventMask::BUTTON4_MOTION
                            | xproto::EventMask::BUTTON5_MOTION
                            | xproto::EventMask::KEYMAP_STATE,
                        xproto::GrabMode::ASYNC,
                        xproto::GrabMode::ASYNC,
                        self.xwindow,
                        0u32,
                        x11rb::CURRENT_TIME,
                    )
                    .expect("Failed to call `grab_pointer`")
                    .reply()
                    .expect("Failed to receive reply from `grab_pointer`");

                match result.status {
                    xproto::GrabStatus::SUCCESS => Ok(()),
                    xproto::GrabStatus::ALREADY_GRABBED => {
                        Err("Cursor could not be confined: already confined by another client")
                    },
                    xproto::GrabStatus::INVALID_TIME => {
                        Err("Cursor could not be confined: invalid time")
                    },
                    xproto::GrabStatus::NOT_VIEWABLE => {
                        Err("Cursor could not be confined: confine location not viewable")
                    },
                    xproto::GrabStatus::FROZEN => {
                        Err("Cursor could not be confined: frozen by another client")
                    },
                    _ => unreachable!(),
                }
                .map_err(|err| RequestError::Os(os_error!(err)))
            },
            CursorGrabMode::Locked => return Ok(()),
        };

        if result.is_ok() {
            *grabbed_lock = mode;
        }

        result
    }

    #[inline]
    pub fn set_cursor_visible(&self, visible: bool) {
        #[allow(clippy::mutex_atomic)]
        let mut visible_lock = self.cursor_visible.lock().unwrap();
        if visible == *visible_lock {
            return;
        }
        let cursor =
            if visible { Some((*self.selected_cursor.lock().unwrap()).clone()) } else { None };
        *visible_lock = visible;
        drop(visible_lock);
        let result = match cursor {
            Some(SelectedCursor::Custom(cursor)) => {
                self.xconn.set_custom_cursor(self.xwindow, &cursor)
            },
            Some(SelectedCursor::Named(cursor)) => {
                self.xconn.set_cursor_icon(self.xwindow, Some(cursor))
            },
            None => self.xconn.set_cursor_icon(self.xwindow, None),
        };

        if let Err(err) = result {
            tracing::error!("failed to set cursor icon: {err}");
        }
    }

    #[inline]
    pub fn scale_factor(&self) -> f64 {
        self.shared_state_lock().last_monitor.scale_factor
    }

    pub fn set_cursor_position_physical(&self, x: i32, y: i32) -> Result<(), RequestError> {
        self.xconn
            .xcb_connection()
            .warp_pointer(x11rb::NONE, self.xwindow, 0, 0, 0, 0, x as _, y as _)
            .map_err(|err| os_error!(X11Error::from(err)))?;
        self.xconn.flush_requests().map_err(|err| os_error!(X11Error::Xlib(err)))?;
        Ok(())
    }

    #[inline]
    pub fn set_cursor_position(&self, position: Position) -> Result<(), RequestError> {
        let (x, y) = position.to_physical::<i32>(self.scale_factor()).into();
        self.set_cursor_position_physical(x, y)
    }

    #[inline]
    pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), RequestError> {
        // Implement cursor hittest for X11 by either setting an empty or full window input shape.

        // In X11, every window has two "shapes":
        //   * Bounding shape: defines the visible outline of the window.
        //   * Input shape: defines the region of the window that receives pointer/keyboard events.
        // If the input shape is the full window rectangle, the window behaves normally.
        // If the input shape is empty, the window is completely click‑through.
        // Here, we implement hit test by mapping `hittest = true` to "restore a full input shape"
        // and `hittest = false` to "clear the input shape" (empty list of rectangles).
        let mut rectangles: Vec<Rectangle> = Vec::new();
        if hittest {
            let size = self.surface_size();
            rectangles.push(Rectangle {
                x: 0,
                y: 0,
                width: size.width as u16,
                height: size.height as u16,
            })
        }
        self.xconn
            .xcb_connection()
            .shape_rectangles(
                SO::SET,
                SK::INPUT,
                ClipOrdering::UNSORTED,
                self.xwindow,
                0,
                0,
                &rectangles,
            )
            .map_err(|_e| RequestError::Ignored)?;
        self.shared_state_lock().cursor_hittest = Some(hittest);
        Ok(())
    }

    /// Moves the window while it is being dragged.
    pub fn drag_window(&self) -> Result<(), RequestError> {
        self.drag_initiate(util::MOVERESIZE_MOVE)
    }

    #[inline]
    pub fn show_window_menu(&self, _position: Position) {}

    /// Resizes the window while it is being dragged.
    pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), RequestError> {
        self.drag_initiate(match direction {
            ResizeDirection::East => util::MOVERESIZE_RIGHT,
            ResizeDirection::North => util::MOVERESIZE_TOP,
            ResizeDirection::NorthEast => util::MOVERESIZE_TOPRIGHT,
            ResizeDirection::NorthWest => util::MOVERESIZE_TOPLEFT,
            ResizeDirection::South => util::MOVERESIZE_BOTTOM,
            ResizeDirection::SouthEast => util::MOVERESIZE_BOTTOMRIGHT,
            ResizeDirection::SouthWest => util::MOVERESIZE_BOTTOMLEFT,
            ResizeDirection::West => util::MOVERESIZE_LEFT,
        })
    }

    /// Initiates a drag operation while the left mouse button is pressed.
    fn drag_initiate(&self, action: isize) -> Result<(), RequestError> {
        let pointer = self
            .xconn
            .query_pointer(self.xwindow, util::VIRTUAL_CORE_POINTER)
            .map_err(|err| os_error!(err))?;

        let window_position = self.inner_position_physical();

        let atoms = self.xconn.atoms();
        let message = atoms[_NET_WM_MOVERESIZE];

        // we can't use `set_cursor_grab(false)` here because it doesn't run `XUngrabPointer`
        // if the cursor isn't currently grabbed
        let mut grabbed_lock = self.cursor_grabbed_mode.lock().unwrap();
        self.xconn
            .xcb_connection()
            .ungrab_pointer(x11rb::CURRENT_TIME)
            .map_err(|err| os_error!(X11Error::from(err)))?
            .ignore_error();
        self.xconn.flush_requests().map_err(|err| os_error!(X11Error::Xlib(err)))?;
        *grabbed_lock = CursorGrabMode::None;

        // we keep the lock until we are done
        self.xconn
            .send_client_msg(
                self.xwindow,
                self.root,
                message,
                Some(
                    xproto::EventMask::SUBSTRUCTURE_REDIRECT
                        | xproto::EventMask::SUBSTRUCTURE_NOTIFY,
                ),
                [
                    (window_position.0 + xinput_fp1616_to_float(pointer.win_x) as i32) as u32,
                    (window_position.1 + xinput_fp1616_to_float(pointer.win_y) as i32) as u32,
                    action.try_into().unwrap(),
                    1, // Button 1
                    1,
                ],
            )
            .map_err(|err| os_error!(err))?;

        self.xconn.flush_requests().map_err(|err| os_error!(X11Error::Xlib(err)))?;

        Ok(())
    }

    #[inline]
    pub fn set_ime_cursor_area(&self, spot: Position, size: Size) {
        let PhysicalPosition { x, y } = spot.to_physical::<i16>(self.scale_factor());
        let PhysicalSize { width, height } = size.to_physical::<u16>(self.scale_factor());
        let _ = self.ime_sender.lock().unwrap().send(ImeRequest::Area(
            self.xwindow as ffi::Window,
            x,
            y,
            width,
            height,
        ));
    }

    #[inline]
    pub fn set_ime_allowed(&self, allowed: bool) {
        let _ = self
            .ime_sender
            .lock()
            .unwrap()
            .send(ImeRequest::Allow(self.xwindow as ffi::Window, allowed));
    }

    #[inline]
    pub fn request_ime_update(&self, request: CoreImeRequest) -> Result<(), ImeRequestError> {
        let mut shared_state = self.shared_state_lock();
        let (capabilities, state) = match request {
            CoreImeRequest::Enable(enable) => {
                let (capabilities, request_data) = enable.into_raw();

                if shared_state.ime_capabilities.is_some() {
                    return Err(ImeRequestError::AlreadyEnabled);
                }

                shared_state.ime_capabilities = Some(capabilities);
                drop(shared_state);
                self.set_ime_allowed(true);
                (capabilities, request_data)
            },
            CoreImeRequest::Update(state) => {
                if let Some(capabilities) = shared_state.ime_capabilities {
                    drop(shared_state);
                    (capabilities, state)
                } else {
                    // The IME was not yet enabled, so discard the update.
                    return Err(ImeRequestError::NotEnabled);
                }
            },
            CoreImeRequest::Disable => {
                shared_state.ime_capabilities = None;
                drop(shared_state);
                self.set_ime_allowed(false);
                return Ok(());
            },
            _ => return Err(ImeRequestError::NotSupported),
        };

        if let Some((position, size)) = state.cursor_area {
            if capabilities.cursor_area() {
                self.set_ime_cursor_area(position, size);
            } else {
                warn!("discarding IME cursor area update without capability enabled.");
            }
        }

        // Pretend that there is always some input method available.
        // Better to make an application think it has an input method and send more events when it
        // doesn't than think there is no input method and not send any IME events.
        Ok(())
    }

    #[inline]
    pub fn ime_capabilities(&self) -> Option<ImeCapabilities> {
        self.shared_state_lock().ime_capabilities
    }

    #[inline]
    pub fn focus_window(&self) {
        let atoms = self.xconn.atoms();
        let state_atom = atoms[WM_STATE];
        let state_type_atom = atoms[CARD32];
        let is_minimized = if let Ok(state) =
            self.xconn.get_property::<u32>(self.xwindow, state_atom, state_type_atom)
        {
            state.contains(&ICONIC_STATE)
        } else {
            false
        };
        let is_visible = match self.shared_state_lock().visibility {
            Visibility::Yes => true,
            Visibility::YesWait | Visibility::No => false,
        };

        if is_visible && !is_minimized {
            self.xconn
                .send_client_msg(
                    self.xwindow,
                    self.root,
                    atoms[_NET_ACTIVE_WINDOW],
                    Some(
                        xproto::EventMask::SUBSTRUCTURE_REDIRECT
                            | xproto::EventMask::SUBSTRUCTURE_NOTIFY,
                    ),
                    [1, x11rb::CURRENT_TIME, 0, 0, 0],
                )
                .expect_then_ignore_error("Failed to send client message");
            if let Err(e) = self.xconn.flush_requests() {
                tracing::error!(
                    "`flush` returned an error when focusing the window. Error was: {}",
                    e
                );
            }
        }
    }

    #[inline]
    pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
        let mut wm_hints =
            WmHints::get(self.xconn.xcb_connection(), self.xwindow as xproto::Window)
                .ok()
                .and_then(|cookie| cookie.reply().ok())
                .flatten()
                .unwrap_or_default();

        wm_hints.urgent = request_type.is_some();
        wm_hints
            .set(self.xconn.xcb_connection(), self.xwindow as xproto::Window)
            .expect_then_ignore_error("Failed to set WM hints");
    }

    #[inline]
    pub(crate) fn generate_activation_token(&self) -> Result<String, X11Error> {
        // Get the title from the WM_NAME property.
        let atoms = self.xconn.atoms();
        let title = {
            let title_bytes = self
                .xconn
                .get_property(self.xwindow, atoms[_NET_WM_NAME], atoms[UTF8_STRING])
                .expect("Failed to get title");

            String::from_utf8(title_bytes).expect("Bad title")
        };

        // Get the activation token and then put it in the event queue.
        let token = self.xconn.request_activation_token(&title)?;

        Ok(token)
    }

    #[inline]
    pub fn request_activation_token(&self) -> Result<AsyncRequestSerial, RequestError> {
        let serial = AsyncRequestSerial::get();
        self.activation_sender.send((self.id(), serial));
        Ok(serial)
    }

    #[inline]
    pub fn id(&self) -> WindowId {
        WindowId::from_raw(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::from_raw(self.xwindow as _));
    }

    #[inline]
    pub fn pre_present_notify(&self) {
        // TODO timer
    }

    #[inline]
    pub fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
        let mut window_handle = rwh_06::XlibWindowHandle::new(self.xlib_window());
        window_handle.visual_id = self.visual as c_ulong;
        Ok(window_handle.into())
    }

    #[inline]
    pub fn raw_display_handle_rwh_06(
        &self,
    ) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
        Ok(rwh_06::XlibDisplayHandle::new(
            Some(
                std::ptr::NonNull::new(self.xlib_display())
                    .expect("display pointer should never be null"),
            ),
            self.screen_id,
        )
        .into())
    }

    #[inline]
    pub fn theme(&self) -> Option<Theme> {
        None
    }

    pub fn set_content_protected(&self, _protected: bool) {}

    #[inline]
    pub fn has_focus(&self) -> bool {
        self.shared_state_lock().has_focus
    }

    pub fn title(&self) -> String {
        String::new()
    }
}

/// Cast a dimension value into a hinted dimension for `WmSizeHints`, clamping if too large.
fn cast_dimension_to_hint(val: u32) -> i32 {
    val.try_into().unwrap_or(i32::MAX)
}

/// Use the above strategy to cast a physical size into a hinted size.
fn cast_physical_size_to_hint(size: PhysicalSize<u32>) -> (i32, i32) {
    let PhysicalSize { width, height } = size;
    (cast_dimension_to_hint(width), cast_dimension_to_hint(height))
}

/// Use the above strategy to cast a size into a hinted size.
fn cast_size_to_hint(size: Size, scale_factor: f64) -> (i32, i32) {
    match size {
        Size::Physical(size) => cast_physical_size_to_hint(size),
        Size::Logical(size) => size.to_physical::<i32>(scale_factor).into(),
    }
}
