window.rs

   1use anyhow::{anyhow, Context as _};
   2
   3use crate::platform::blade::{BladeContext, BladeRenderer, BladeSurfaceConfig};
   4use crate::{
   5    px, size, AnyWindowHandle, Bounds, Decorations, DevicePixels, ForegroundExecutor, GpuSpecs,
   6    Modifiers, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler,
   7    PlatformWindow, Point, PromptLevel, RequestFrameOptions, ResizeEdge, ScaledPixels, Scene, Size,
   8    Tiling, WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowDecorations,
   9    WindowKind, WindowParams, X11ClientStatePtr,
  10};
  11
  12use blade_graphics as gpu;
  13use raw_window_handle as rwh;
  14use util::{maybe, ResultExt};
  15use x11rb::{
  16    connection::Connection,
  17    cookie::{Cookie, VoidCookie},
  18    errors::ConnectionError,
  19    properties::WmSizeHints,
  20    protocol::{
  21        sync,
  22        xinput::{self, ConnectionExt as _},
  23        xproto::{self, ClientMessageEvent, ConnectionExt, EventMask, TranslateCoordinatesReply},
  24    },
  25    wrapper::ConnectionExt as _,
  26    xcb_ffi::XCBConnection,
  27};
  28
  29use std::{
  30    cell::RefCell, ffi::c_void, fmt::Display, num::NonZeroU32, ops::Div, ptr::NonNull, rc::Rc,
  31    sync::Arc,
  32};
  33
  34use super::{X11Display, XINPUT_ALL_DEVICES, XINPUT_ALL_DEVICE_GROUPS};
  35x11rb::atom_manager! {
  36    pub XcbAtoms: AtomsCookie {
  37        XA_ATOM,
  38        XdndAware,
  39        XdndStatus,
  40        XdndEnter,
  41        XdndLeave,
  42        XdndPosition,
  43        XdndSelection,
  44        XdndDrop,
  45        XdndFinished,
  46        XdndTypeList,
  47        XdndActionCopy,
  48        TextUriList: b"text/uri-list",
  49        UTF8_STRING,
  50        TEXT,
  51        STRING,
  52        TEXT_PLAIN_UTF8: b"text/plain;charset=utf-8",
  53        TEXT_PLAIN: b"text/plain",
  54        XDND_DATA,
  55        WM_PROTOCOLS,
  56        WM_DELETE_WINDOW,
  57        WM_CHANGE_STATE,
  58        _NET_WM_PID,
  59        _NET_WM_NAME,
  60        _NET_WM_STATE,
  61        _NET_WM_STATE_MAXIMIZED_VERT,
  62        _NET_WM_STATE_MAXIMIZED_HORZ,
  63        _NET_WM_STATE_FULLSCREEN,
  64        _NET_WM_STATE_HIDDEN,
  65        _NET_WM_STATE_FOCUSED,
  66        _NET_ACTIVE_WINDOW,
  67        _NET_WM_SYNC_REQUEST,
  68        _NET_WM_SYNC_REQUEST_COUNTER,
  69        _NET_WM_BYPASS_COMPOSITOR,
  70        _NET_WM_MOVERESIZE,
  71        _NET_WM_WINDOW_TYPE,
  72        _NET_WM_WINDOW_TYPE_NOTIFICATION,
  73        _NET_WM_SYNC,
  74        _NET_SUPPORTED,
  75        _MOTIF_WM_HINTS,
  76        _GTK_SHOW_WINDOW_MENU,
  77        _GTK_FRAME_EXTENTS,
  78        _GTK_EDGE_CONSTRAINTS,
  79        _NET_CLIENT_LIST_STACKING,
  80    }
  81}
  82
  83fn query_render_extent(
  84    xcb: &Rc<XCBConnection>,
  85    x_window: xproto::Window,
  86) -> anyhow::Result<gpu::Extent> {
  87    let reply = get_reply(|| "X11 GetGeometry failed.", xcb.get_geometry(x_window))?;
  88    Ok(gpu::Extent {
  89        width: reply.width as u32,
  90        height: reply.height as u32,
  91        depth: 1,
  92    })
  93}
  94
  95impl ResizeEdge {
  96    fn to_moveresize(&self) -> u32 {
  97        match self {
  98            ResizeEdge::TopLeft => 0,
  99            ResizeEdge::Top => 1,
 100            ResizeEdge::TopRight => 2,
 101            ResizeEdge::Right => 3,
 102            ResizeEdge::BottomRight => 4,
 103            ResizeEdge::Bottom => 5,
 104            ResizeEdge::BottomLeft => 6,
 105            ResizeEdge::Left => 7,
 106        }
 107    }
 108}
 109
 110#[derive(Debug)]
 111struct EdgeConstraints {
 112    top_tiled: bool,
 113    #[allow(dead_code)]
 114    top_resizable: bool,
 115
 116    right_tiled: bool,
 117    #[allow(dead_code)]
 118    right_resizable: bool,
 119
 120    bottom_tiled: bool,
 121    #[allow(dead_code)]
 122    bottom_resizable: bool,
 123
 124    left_tiled: bool,
 125    #[allow(dead_code)]
 126    left_resizable: bool,
 127}
 128
 129impl EdgeConstraints {
 130    fn from_atom(atom: u32) -> Self {
 131        EdgeConstraints {
 132            top_tiled: (atom & (1 << 0)) != 0,
 133            top_resizable: (atom & (1 << 1)) != 0,
 134            right_tiled: (atom & (1 << 2)) != 0,
 135            right_resizable: (atom & (1 << 3)) != 0,
 136            bottom_tiled: (atom & (1 << 4)) != 0,
 137            bottom_resizable: (atom & (1 << 5)) != 0,
 138            left_tiled: (atom & (1 << 6)) != 0,
 139            left_resizable: (atom & (1 << 7)) != 0,
 140        }
 141    }
 142
 143    fn to_tiling(&self) -> Tiling {
 144        Tiling {
 145            top: self.top_tiled,
 146            right: self.right_tiled,
 147            bottom: self.bottom_tiled,
 148            left: self.left_tiled,
 149        }
 150    }
 151}
 152
 153#[derive(Copy, Clone, Debug)]
 154struct Visual {
 155    id: xproto::Visualid,
 156    colormap: u32,
 157    depth: u8,
 158}
 159
 160struct VisualSet {
 161    inherit: Visual,
 162    opaque: Option<Visual>,
 163    transparent: Option<Visual>,
 164    root: u32,
 165    black_pixel: u32,
 166}
 167
 168fn find_visuals(xcb: &XCBConnection, screen_index: usize) -> VisualSet {
 169    let screen = &xcb.setup().roots[screen_index];
 170    let mut set = VisualSet {
 171        inherit: Visual {
 172            id: screen.root_visual,
 173            colormap: screen.default_colormap,
 174            depth: screen.root_depth,
 175        },
 176        opaque: None,
 177        transparent: None,
 178        root: screen.root,
 179        black_pixel: screen.black_pixel,
 180    };
 181
 182    for depth_info in screen.allowed_depths.iter() {
 183        for visual_type in depth_info.visuals.iter() {
 184            let visual = Visual {
 185                id: visual_type.visual_id,
 186                colormap: 0,
 187                depth: depth_info.depth,
 188            };
 189            log::debug!("Visual id: {}, class: {:?}, depth: {}, bits_per_value: {}, masks: 0x{:x} 0x{:x} 0x{:x}",
 190                visual_type.visual_id,
 191                visual_type.class,
 192                depth_info.depth,
 193                visual_type.bits_per_rgb_value,
 194                visual_type.red_mask, visual_type.green_mask, visual_type.blue_mask,
 195            );
 196
 197            if (
 198                visual_type.red_mask,
 199                visual_type.green_mask,
 200                visual_type.blue_mask,
 201            ) != (0xFF0000, 0xFF00, 0xFF)
 202            {
 203                continue;
 204            }
 205            let color_mask = visual_type.red_mask | visual_type.green_mask | visual_type.blue_mask;
 206            let alpha_mask = color_mask as usize ^ ((1usize << depth_info.depth) - 1);
 207
 208            if alpha_mask == 0 {
 209                if set.opaque.is_none() {
 210                    set.opaque = Some(visual);
 211                }
 212            } else {
 213                if set.transparent.is_none() {
 214                    set.transparent = Some(visual);
 215                }
 216            }
 217        }
 218    }
 219
 220    set
 221}
 222
 223struct RawWindow {
 224    connection: *mut c_void,
 225    screen_id: usize,
 226    window_id: u32,
 227    visual_id: u32,
 228}
 229
 230#[derive(Default)]
 231pub struct Callbacks {
 232    request_frame: Option<Box<dyn FnMut(RequestFrameOptions)>>,
 233    input: Option<Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>>,
 234    active_status_change: Option<Box<dyn FnMut(bool)>>,
 235    hovered_status_change: Option<Box<dyn FnMut(bool)>>,
 236    resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
 237    moved: Option<Box<dyn FnMut()>>,
 238    should_close: Option<Box<dyn FnMut() -> bool>>,
 239    close: Option<Box<dyn FnOnce()>>,
 240    appearance_changed: Option<Box<dyn FnMut()>>,
 241}
 242
 243pub struct X11WindowState {
 244    pub destroyed: bool,
 245    client: X11ClientStatePtr,
 246    executor: ForegroundExecutor,
 247    atoms: XcbAtoms,
 248    x_root_window: xproto::Window,
 249    pub(crate) counter_id: sync::Counter,
 250    pub(crate) last_sync_counter: Option<sync::Int64>,
 251    bounds: Bounds<Pixels>,
 252    scale_factor: f32,
 253    renderer: BladeRenderer,
 254    display: Rc<dyn PlatformDisplay>,
 255    input_handler: Option<PlatformInputHandler>,
 256    appearance: WindowAppearance,
 257    background_appearance: WindowBackgroundAppearance,
 258    maximized_vertical: bool,
 259    maximized_horizontal: bool,
 260    hidden: bool,
 261    active: bool,
 262    hovered: bool,
 263    fullscreen: bool,
 264    client_side_decorations_supported: bool,
 265    decorations: WindowDecorations,
 266    edge_constraints: Option<EdgeConstraints>,
 267    pub handle: AnyWindowHandle,
 268    last_insets: [u32; 4],
 269}
 270
 271impl X11WindowState {
 272    fn is_transparent(&self) -> bool {
 273        self.background_appearance != WindowBackgroundAppearance::Opaque
 274    }
 275}
 276
 277#[derive(Clone)]
 278pub(crate) struct X11WindowStatePtr {
 279    pub state: Rc<RefCell<X11WindowState>>,
 280    pub(crate) callbacks: Rc<RefCell<Callbacks>>,
 281    xcb: Rc<XCBConnection>,
 282    x_window: xproto::Window,
 283}
 284
 285impl rwh::HasWindowHandle for RawWindow {
 286    fn window_handle(&self) -> Result<rwh::WindowHandle, rwh::HandleError> {
 287        let Some(non_zero) = NonZeroU32::new(self.window_id) else {
 288            log::error!("RawWindow.window_id zero when getting window handle.");
 289            return Err(rwh::HandleError::Unavailable);
 290        };
 291        let mut handle = rwh::XcbWindowHandle::new(non_zero);
 292        handle.visual_id = NonZeroU32::new(self.visual_id);
 293        Ok(unsafe { rwh::WindowHandle::borrow_raw(handle.into()) })
 294    }
 295}
 296impl rwh::HasDisplayHandle for RawWindow {
 297    fn display_handle(&self) -> Result<rwh::DisplayHandle, rwh::HandleError> {
 298        let Some(non_zero) = NonNull::new(self.connection) else {
 299            log::error!("Null RawWindow.connection when getting display handle.");
 300            return Err(rwh::HandleError::Unavailable);
 301        };
 302        let handle = rwh::XcbDisplayHandle::new(Some(non_zero), self.screen_id as i32);
 303        Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) })
 304    }
 305}
 306
 307impl rwh::HasWindowHandle for X11Window {
 308    fn window_handle(&self) -> Result<rwh::WindowHandle, rwh::HandleError> {
 309        unimplemented!()
 310    }
 311}
 312impl rwh::HasDisplayHandle for X11Window {
 313    fn display_handle(&self) -> Result<rwh::DisplayHandle, rwh::HandleError> {
 314        unimplemented!()
 315    }
 316}
 317
 318fn check_reply<C, F>(
 319    failure_context: F,
 320    result: Result<VoidCookie<'_, Rc<XCBConnection>>, ConnectionError>,
 321) -> anyhow::Result<()>
 322where
 323    C: Display + Send + Sync + 'static,
 324    F: FnOnce() -> C,
 325{
 326    result
 327        .map_err(|connection_error| anyhow!(connection_error))
 328        .and_then(|response| {
 329            response
 330                .check()
 331                .map_err(|error_response| anyhow!(error_response))
 332        })
 333        .with_context(failure_context)
 334}
 335
 336fn get_reply<C, F, O>(
 337    failure_context: F,
 338    result: Result<Cookie<'_, Rc<XCBConnection>, O>, ConnectionError>,
 339) -> anyhow::Result<O>
 340where
 341    C: Display + Send + Sync + 'static,
 342    F: FnOnce() -> C,
 343    O: x11rb::x11_utils::TryParse,
 344{
 345    result
 346        .map_err(|connection_error| anyhow!(connection_error))
 347        .and_then(|response| {
 348            response
 349                .reply()
 350                .map_err(|error_response| anyhow!(error_response))
 351        })
 352        .with_context(failure_context)
 353}
 354
 355impl X11WindowState {
 356    pub fn new(
 357        handle: AnyWindowHandle,
 358        client: X11ClientStatePtr,
 359        executor: ForegroundExecutor,
 360        gpu_context: &BladeContext,
 361        params: WindowParams,
 362        xcb: &Rc<XCBConnection>,
 363        client_side_decorations_supported: bool,
 364        x_main_screen_index: usize,
 365        x_window: xproto::Window,
 366        atoms: &XcbAtoms,
 367        scale_factor: f32,
 368        appearance: WindowAppearance,
 369    ) -> anyhow::Result<Self> {
 370        let x_screen_index = params
 371            .display_id
 372            .map_or(x_main_screen_index, |did| did.0 as usize);
 373
 374        let visual_set = find_visuals(&xcb, x_screen_index);
 375
 376        let visual = match visual_set.transparent {
 377            Some(visual) => visual,
 378            None => {
 379                log::warn!("Unable to find a transparent visual",);
 380                visual_set.inherit
 381            }
 382        };
 383        log::info!("Using {:?}", visual);
 384
 385        let colormap = if visual.colormap != 0 {
 386            visual.colormap
 387        } else {
 388            let id = xcb.generate_id()?;
 389            log::info!("Creating colormap {}", id);
 390            check_reply(
 391                || format!("X11 CreateColormap failed. id: {}", id),
 392                xcb.create_colormap(xproto::ColormapAlloc::NONE, id, visual_set.root, visual.id),
 393            )?;
 394            id
 395        };
 396
 397        let win_aux = xproto::CreateWindowAux::new()
 398            // https://stackoverflow.com/questions/43218127/x11-xlib-xcb-creating-a-window-requires-border-pixel-if-specifying-colormap-wh
 399            .border_pixel(visual_set.black_pixel)
 400            .colormap(colormap)
 401            .event_mask(
 402                xproto::EventMask::EXPOSURE
 403                    | xproto::EventMask::STRUCTURE_NOTIFY
 404                    | xproto::EventMask::FOCUS_CHANGE
 405                    | xproto::EventMask::KEY_PRESS
 406                    | xproto::EventMask::KEY_RELEASE
 407                    | EventMask::PROPERTY_CHANGE,
 408            );
 409
 410        let mut bounds = params.bounds.to_device_pixels(scale_factor);
 411        if bounds.size.width.0 == 0 || bounds.size.height.0 == 0 {
 412            log::warn!("Window bounds contain a zero value. height={}, width={}. Falling back to defaults.", bounds.size.height.0, bounds.size.width.0);
 413            bounds.size.width = 800.into();
 414            bounds.size.height = 600.into();
 415        }
 416
 417        check_reply(
 418            || {
 419                format!("X11 CreateWindow failed. depth: {}, x_window: {}, visual_set.root: {}, bounds.origin.x.0: {}, bounds.origin.y.0: {}, bounds.size.width.0: {}, bounds.size.height.0: {}",
 420                                visual.depth, x_window, visual_set.root, bounds.origin.x.0 + 2, bounds.origin.y.0, bounds.size.width.0, bounds.size.height.0)
 421            },
 422            xcb.create_window(
 423                visual.depth,
 424                x_window,
 425                visual_set.root,
 426                (bounds.origin.x.0 + 2) as i16,
 427                bounds.origin.y.0 as i16,
 428                bounds.size.width.0 as u16,
 429                bounds.size.height.0 as u16,
 430                0,
 431                xproto::WindowClass::INPUT_OUTPUT,
 432                visual.id,
 433                &win_aux,
 434            ),
 435        )?;
 436
 437        // Collect errors during setup, so that window can be destroyed on failure.
 438        let setup_result = maybe!({
 439            let pid = std::process::id();
 440            check_reply(
 441                || "X11 ChangeProperty for _NET_WM_PID failed.",
 442                xcb.change_property32(
 443                    xproto::PropMode::REPLACE,
 444                    x_window,
 445                    atoms._NET_WM_PID,
 446                    xproto::AtomEnum::CARDINAL,
 447                    &[pid],
 448                ),
 449            )?;
 450
 451            if let Some(size) = params.window_min_size {
 452                let mut size_hints = WmSizeHints::new();
 453                let min_size = (size.width.0 as i32, size.height.0 as i32);
 454                size_hints.min_size = Some(min_size);
 455                check_reply(
 456                    || {
 457                        format!(
 458                            "X11 change of WM_SIZE_HINTS failed. min_size: {:?}",
 459                            min_size
 460                        )
 461                    },
 462                    size_hints.set_normal_hints(xcb, x_window),
 463                )?;
 464            }
 465
 466            let reply = get_reply(|| "X11 GetGeometry failed.", xcb.get_geometry(x_window))?;
 467            if reply.x == 0 && reply.y == 0 {
 468                bounds.origin.x.0 += 2;
 469                // Work around a bug where our rendered content appears
 470                // outside the window bounds when opened at the default position
 471                // (14px, 49px on X + Gnome + Ubuntu 22).
 472                let x = bounds.origin.x.0;
 473                let y = bounds.origin.y.0;
 474                check_reply(
 475                    || format!("X11 ConfigureWindow failed. x: {}, y: {}", x, y),
 476                    xcb.configure_window(x_window, &xproto::ConfigureWindowAux::new().x(x).y(y)),
 477                )?;
 478            }
 479            if let Some(titlebar) = params.titlebar {
 480                if let Some(title) = titlebar.title {
 481                    check_reply(
 482                        || "X11 ChangeProperty8 on window title failed.",
 483                        xcb.change_property8(
 484                            xproto::PropMode::REPLACE,
 485                            x_window,
 486                            xproto::AtomEnum::WM_NAME,
 487                            xproto::AtomEnum::STRING,
 488                            title.as_bytes(),
 489                        ),
 490                    )?;
 491                }
 492            }
 493            if params.kind == WindowKind::PopUp {
 494                check_reply(
 495                    || "X11 ChangeProperty32 setting window type for pop-up failed.",
 496                    xcb.change_property32(
 497                        xproto::PropMode::REPLACE,
 498                        x_window,
 499                        atoms._NET_WM_WINDOW_TYPE,
 500                        xproto::AtomEnum::ATOM,
 501                        &[atoms._NET_WM_WINDOW_TYPE_NOTIFICATION],
 502                    ),
 503                )?;
 504            }
 505
 506            check_reply(
 507                || "X11 ChangeProperty32 setting protocols failed.",
 508                xcb.change_property32(
 509                    xproto::PropMode::REPLACE,
 510                    x_window,
 511                    atoms.WM_PROTOCOLS,
 512                    xproto::AtomEnum::ATOM,
 513                    &[atoms.WM_DELETE_WINDOW, atoms._NET_WM_SYNC_REQUEST],
 514                ),
 515            )?;
 516
 517            get_reply(
 518                || "X11 sync protocol initialize failed.",
 519                sync::initialize(xcb, 3, 1),
 520            )?;
 521            let sync_request_counter = xcb.generate_id()?;
 522            check_reply(
 523                || "X11 sync CreateCounter failed.",
 524                sync::create_counter(xcb, sync_request_counter, sync::Int64 { lo: 0, hi: 0 }),
 525            )?;
 526
 527            check_reply(
 528                || "X11 ChangeProperty32 setting sync request counter failed.",
 529                xcb.change_property32(
 530                    xproto::PropMode::REPLACE,
 531                    x_window,
 532                    atoms._NET_WM_SYNC_REQUEST_COUNTER,
 533                    xproto::AtomEnum::CARDINAL,
 534                    &[sync_request_counter],
 535                ),
 536            )?;
 537
 538            check_reply(
 539                || "X11 XiSelectEvents failed.",
 540                xcb.xinput_xi_select_events(
 541                    x_window,
 542                    &[xinput::EventMask {
 543                        deviceid: XINPUT_ALL_DEVICE_GROUPS,
 544                        mask: vec![
 545                            xinput::XIEventMask::MOTION
 546                                | xinput::XIEventMask::BUTTON_PRESS
 547                                | xinput::XIEventMask::BUTTON_RELEASE
 548                                | xinput::XIEventMask::ENTER
 549                                | xinput::XIEventMask::LEAVE,
 550                        ],
 551                    }],
 552                ),
 553            )?;
 554
 555            check_reply(
 556                || "X11 XiSelectEvents for device changes failed.",
 557                xcb.xinput_xi_select_events(
 558                    x_window,
 559                    &[xinput::EventMask {
 560                        deviceid: XINPUT_ALL_DEVICES,
 561                        mask: vec![
 562                            xinput::XIEventMask::HIERARCHY | xinput::XIEventMask::DEVICE_CHANGED,
 563                        ],
 564                    }],
 565                ),
 566            )?;
 567
 568            xcb.flush().with_context(|| "X11 Flush failed.")?;
 569
 570            let renderer = {
 571                let raw_window = RawWindow {
 572                    connection: as_raw_xcb_connection::AsRawXcbConnection::as_raw_xcb_connection(
 573                        xcb,
 574                    ) as *mut _,
 575                    screen_id: x_screen_index,
 576                    window_id: x_window,
 577                    visual_id: visual.id,
 578                };
 579                let config = BladeSurfaceConfig {
 580                    // Note: this has to be done after the GPU init, or otherwise
 581                    // the sizes are immediately invalidated.
 582                    size: query_render_extent(xcb, x_window)?,
 583                    // We set it to transparent by default, even if we have client-side
 584                    // decorations, since those seem to work on X11 even without `true` here.
 585                    // If the window appearance changes, then the renderer will get updated
 586                    // too
 587                    transparent: false,
 588                };
 589                BladeRenderer::new(gpu_context, &raw_window, config)?
 590            };
 591
 592            let display = Rc::new(X11Display::new(xcb, scale_factor, x_screen_index)?);
 593
 594            Ok(Self {
 595                client,
 596                executor,
 597                display,
 598                x_root_window: visual_set.root,
 599                bounds: bounds.to_pixels(scale_factor),
 600                scale_factor,
 601                renderer,
 602                atoms: *atoms,
 603                input_handler: None,
 604                active: false,
 605                hovered: false,
 606                fullscreen: false,
 607                maximized_vertical: false,
 608                maximized_horizontal: false,
 609                hidden: false,
 610                appearance,
 611                handle,
 612                background_appearance: WindowBackgroundAppearance::Opaque,
 613                destroyed: false,
 614                client_side_decorations_supported,
 615                decorations: WindowDecorations::Server,
 616                last_insets: [0, 0, 0, 0],
 617                edge_constraints: None,
 618                counter_id: sync_request_counter,
 619                last_sync_counter: None,
 620            })
 621        });
 622
 623        if setup_result.is_err() {
 624            check_reply(
 625                || "X11 DestroyWindow failed while cleaning it up after setup failure.",
 626                xcb.destroy_window(x_window),
 627            )?;
 628            xcb.flush()
 629                .with_context(|| "X11 Flush failed while cleaning it up after setup failure.")?;
 630        }
 631
 632        setup_result
 633    }
 634
 635    fn content_size(&self) -> Size<Pixels> {
 636        let size = self.renderer.viewport_size();
 637        Size {
 638            width: size.width.into(),
 639            height: size.height.into(),
 640        }
 641    }
 642}
 643
 644/// A handle to an X11 window which destroys it on Drop.
 645pub struct X11WindowHandle {
 646    id: xproto::Window,
 647    xcb: Rc<XCBConnection>,
 648}
 649
 650impl Drop for X11WindowHandle {
 651    fn drop(&mut self) {
 652        maybe!({
 653            check_reply(
 654                || "X11 DestroyWindow failed while dropping X11WindowHandle.",
 655                self.xcb.destroy_window(self.id),
 656            )?;
 657            self.xcb
 658                .flush()
 659                .with_context(|| "X11 Flush failed while dropping X11WindowHandle.")?;
 660            anyhow::Ok(())
 661        })
 662        .log_err();
 663    }
 664}
 665
 666pub(crate) struct X11Window(pub X11WindowStatePtr);
 667
 668impl Drop for X11Window {
 669    fn drop(&mut self) {
 670        let mut state = self.0.state.borrow_mut();
 671        state.renderer.destroy();
 672
 673        let destroy_x_window = maybe!({
 674            check_reply(
 675                || "X11 DestroyWindow failure.",
 676                self.0.xcb.destroy_window(self.0.x_window),
 677            )?;
 678            self.0
 679                .xcb
 680                .flush()
 681                .with_context(|| "X11 Flush failed after calling DestroyWindow.")?;
 682
 683            anyhow::Ok(())
 684        })
 685        .log_err();
 686
 687        if destroy_x_window.is_some() {
 688            // Mark window as destroyed so that we can filter out when X11 events
 689            // for it still come in.
 690            state.destroyed = true;
 691
 692            let this_ptr = self.0.clone();
 693            let client_ptr = state.client.clone();
 694            state
 695                .executor
 696                .spawn(async move {
 697                    this_ptr.close();
 698                    client_ptr.drop_window(this_ptr.x_window);
 699                })
 700                .detach();
 701        }
 702
 703        drop(state);
 704    }
 705}
 706
 707enum WmHintPropertyState {
 708    // Remove = 0,
 709    // Add = 1,
 710    Toggle = 2,
 711}
 712
 713impl X11Window {
 714    pub fn new(
 715        handle: AnyWindowHandle,
 716        client: X11ClientStatePtr,
 717        executor: ForegroundExecutor,
 718        gpu_context: &BladeContext,
 719        params: WindowParams,
 720        xcb: &Rc<XCBConnection>,
 721        client_side_decorations_supported: bool,
 722        x_main_screen_index: usize,
 723        x_window: xproto::Window,
 724        atoms: &XcbAtoms,
 725        scale_factor: f32,
 726        appearance: WindowAppearance,
 727    ) -> anyhow::Result<Self> {
 728        let ptr = X11WindowStatePtr {
 729            state: Rc::new(RefCell::new(X11WindowState::new(
 730                handle,
 731                client,
 732                executor,
 733                gpu_context,
 734                params,
 735                xcb,
 736                client_side_decorations_supported,
 737                x_main_screen_index,
 738                x_window,
 739                atoms,
 740                scale_factor,
 741                appearance,
 742            )?)),
 743            callbacks: Rc::new(RefCell::new(Callbacks::default())),
 744            xcb: xcb.clone(),
 745            x_window,
 746        };
 747
 748        let state = ptr.state.borrow_mut();
 749        ptr.set_wm_properties(state)?;
 750
 751        Ok(Self(ptr))
 752    }
 753
 754    fn set_wm_hints<C: Display + Send + Sync + 'static, F: FnOnce() -> C>(
 755        &self,
 756        failure_context: F,
 757        wm_hint_property_state: WmHintPropertyState,
 758        prop1: u32,
 759        prop2: u32,
 760    ) -> anyhow::Result<()> {
 761        let state = self.0.state.borrow();
 762        let message = ClientMessageEvent::new(
 763            32,
 764            self.0.x_window,
 765            state.atoms._NET_WM_STATE,
 766            [wm_hint_property_state as u32, prop1, prop2, 1, 0],
 767        );
 768        check_reply(
 769            failure_context,
 770            self.0.xcb.send_event(
 771                false,
 772                state.x_root_window,
 773                EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
 774                message,
 775            ),
 776        )
 777    }
 778
 779    fn get_root_position(
 780        &self,
 781        position: Point<Pixels>,
 782    ) -> anyhow::Result<TranslateCoordinatesReply> {
 783        let state = self.0.state.borrow();
 784        get_reply(
 785            || "X11 TranslateCoordinates failed.",
 786            self.0.xcb.translate_coordinates(
 787                self.0.x_window,
 788                state.x_root_window,
 789                (position.x.0 * state.scale_factor) as i16,
 790                (position.y.0 * state.scale_factor) as i16,
 791            ),
 792        )
 793    }
 794
 795    fn send_moveresize(&self, flag: u32) -> anyhow::Result<()> {
 796        let state = self.0.state.borrow();
 797
 798        check_reply(
 799            || "X11 UngrabPointer before move/resize of window ailed.",
 800            self.0.xcb.ungrab_pointer(x11rb::CURRENT_TIME),
 801        )?;
 802
 803        let pointer = get_reply(
 804            || "X11 QueryPointer before move/resize of window failed.",
 805            self.0.xcb.query_pointer(self.0.x_window),
 806        )?;
 807        let message = ClientMessageEvent::new(
 808            32,
 809            self.0.x_window,
 810            state.atoms._NET_WM_MOVERESIZE,
 811            [
 812                pointer.root_x as u32,
 813                pointer.root_y as u32,
 814                flag,
 815                0, // Left mouse button
 816                0,
 817            ],
 818        );
 819        check_reply(
 820            || "X11 SendEvent to move/resize window failed.",
 821            self.0.xcb.send_event(
 822                false,
 823                state.x_root_window,
 824                EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
 825                message,
 826            ),
 827        )?;
 828
 829        self.flush()
 830    }
 831
 832    fn flush(&self) -> anyhow::Result<()> {
 833        self.0.xcb.flush().with_context(|| "X11 Flush failed.")
 834    }
 835}
 836
 837impl X11WindowStatePtr {
 838    pub fn should_close(&self) -> bool {
 839        let mut cb = self.callbacks.borrow_mut();
 840        if let Some(mut should_close) = cb.should_close.take() {
 841            let result = (should_close)();
 842            cb.should_close = Some(should_close);
 843            result
 844        } else {
 845            true
 846        }
 847    }
 848
 849    pub fn property_notify(&self, event: xproto::PropertyNotifyEvent) -> anyhow::Result<()> {
 850        let mut state = self.state.borrow_mut();
 851        if event.atom == state.atoms._NET_WM_STATE {
 852            self.set_wm_properties(state)?;
 853        } else if event.atom == state.atoms._GTK_EDGE_CONSTRAINTS {
 854            self.set_edge_constraints(state)?;
 855        }
 856        Ok(())
 857    }
 858
 859    fn set_edge_constraints(
 860        &self,
 861        mut state: std::cell::RefMut<X11WindowState>,
 862    ) -> anyhow::Result<()> {
 863        let reply = get_reply(
 864            || "X11 GetProperty for _GTK_EDGE_CONSTRAINTS failed.",
 865            self.xcb.get_property(
 866                false,
 867                self.x_window,
 868                state.atoms._GTK_EDGE_CONSTRAINTS,
 869                xproto::AtomEnum::CARDINAL,
 870                0,
 871                4,
 872            ),
 873        )?;
 874
 875        if reply.value_len != 0 {
 876            let atom = u32::from_ne_bytes(reply.value[0..4].try_into().unwrap());
 877            let edge_constraints = EdgeConstraints::from_atom(atom);
 878            state.edge_constraints.replace(edge_constraints);
 879        }
 880
 881        Ok(())
 882    }
 883
 884    fn set_wm_properties(
 885        &self,
 886        mut state: std::cell::RefMut<X11WindowState>,
 887    ) -> anyhow::Result<()> {
 888        let reply = get_reply(
 889            || "X11 GetProperty for _NET_WM_STATE failed.",
 890            self.xcb.get_property(
 891                false,
 892                self.x_window,
 893                state.atoms._NET_WM_STATE,
 894                xproto::AtomEnum::ATOM,
 895                0,
 896                u32::MAX,
 897            ),
 898        )?;
 899
 900        let atoms = reply
 901            .value
 902            .chunks_exact(4)
 903            .map(|chunk| u32::from_ne_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]));
 904
 905        state.active = false;
 906        state.fullscreen = false;
 907        state.maximized_vertical = false;
 908        state.maximized_horizontal = false;
 909        state.hidden = true;
 910
 911        for atom in atoms {
 912            if atom == state.atoms._NET_WM_STATE_FOCUSED {
 913                state.active = true;
 914            } else if atom == state.atoms._NET_WM_STATE_FULLSCREEN {
 915                state.fullscreen = true;
 916            } else if atom == state.atoms._NET_WM_STATE_MAXIMIZED_VERT {
 917                state.maximized_vertical = true;
 918            } else if atom == state.atoms._NET_WM_STATE_MAXIMIZED_HORZ {
 919                state.maximized_horizontal = true;
 920            } else if atom == state.atoms._NET_WM_STATE_HIDDEN {
 921                state.hidden = true;
 922            }
 923        }
 924
 925        Ok(())
 926    }
 927
 928    pub fn close(&self) {
 929        let mut callbacks = self.callbacks.borrow_mut();
 930        if let Some(fun) = callbacks.close.take() {
 931            fun()
 932        }
 933    }
 934
 935    pub fn refresh(&self, request_frame_options: RequestFrameOptions) {
 936        let mut cb = self.callbacks.borrow_mut();
 937        if let Some(ref mut fun) = cb.request_frame {
 938            fun(request_frame_options);
 939        }
 940    }
 941
 942    pub fn handle_input(&self, input: PlatformInput) {
 943        if let Some(ref mut fun) = self.callbacks.borrow_mut().input {
 944            if !fun(input.clone()).propagate {
 945                return;
 946            }
 947        }
 948        if let PlatformInput::KeyDown(event) = input {
 949            let mut state = self.state.borrow_mut();
 950            if let Some(mut input_handler) = state.input_handler.take() {
 951                if let Some(key_char) = &event.keystroke.key_char {
 952                    drop(state);
 953                    input_handler.replace_text_in_range(None, key_char);
 954                    state = self.state.borrow_mut();
 955                }
 956                state.input_handler = Some(input_handler);
 957            }
 958        }
 959    }
 960
 961    pub fn handle_ime_commit(&self, text: String) {
 962        let mut state = self.state.borrow_mut();
 963        if let Some(mut input_handler) = state.input_handler.take() {
 964            drop(state);
 965            input_handler.replace_text_in_range(None, &text);
 966            let mut state = self.state.borrow_mut();
 967            state.input_handler = Some(input_handler);
 968        }
 969    }
 970
 971    pub fn handle_ime_preedit(&self, text: String) {
 972        let mut state = self.state.borrow_mut();
 973        if let Some(mut input_handler) = state.input_handler.take() {
 974            drop(state);
 975            input_handler.replace_and_mark_text_in_range(None, &text, None);
 976            let mut state = self.state.borrow_mut();
 977            state.input_handler = Some(input_handler);
 978        }
 979    }
 980
 981    pub fn handle_ime_unmark(&self) {
 982        let mut state = self.state.borrow_mut();
 983        if let Some(mut input_handler) = state.input_handler.take() {
 984            drop(state);
 985            input_handler.unmark_text();
 986            let mut state = self.state.borrow_mut();
 987            state.input_handler = Some(input_handler);
 988        }
 989    }
 990
 991    pub fn handle_ime_delete(&self) {
 992        let mut state = self.state.borrow_mut();
 993        if let Some(mut input_handler) = state.input_handler.take() {
 994            drop(state);
 995            if let Some(marked) = input_handler.marked_text_range() {
 996                input_handler.replace_text_in_range(Some(marked), "");
 997            }
 998            let mut state = self.state.borrow_mut();
 999            state.input_handler = Some(input_handler);
1000        }
1001    }
1002
1003    pub fn get_ime_area(&self) -> Option<Bounds<Pixels>> {
1004        let mut state = self.state.borrow_mut();
1005        let mut bounds: Option<Bounds<Pixels>> = None;
1006        if let Some(mut input_handler) = state.input_handler.take() {
1007            drop(state);
1008            if let Some(selection) = input_handler.selected_text_range(true) {
1009                bounds = input_handler.bounds_for_range(selection.range);
1010            }
1011            let mut state = self.state.borrow_mut();
1012            state.input_handler = Some(input_handler);
1013        };
1014        bounds
1015    }
1016
1017    pub fn configure(&self, bounds: Bounds<i32>) -> anyhow::Result<()> {
1018        let mut resize_args = None;
1019        let is_resize;
1020        {
1021            let mut state = self.state.borrow_mut();
1022            let bounds = bounds.map(|f| px(f as f32 / state.scale_factor));
1023
1024            is_resize = bounds.size.width != state.bounds.size.width
1025                || bounds.size.height != state.bounds.size.height;
1026
1027            // If it's a resize event (only width/height changed), we ignore `bounds.origin`
1028            // because it contains wrong values.
1029            if is_resize {
1030                state.bounds.size = bounds.size;
1031            } else {
1032                state.bounds = bounds;
1033            }
1034
1035            let gpu_size = query_render_extent(&self.xcb, self.x_window)?;
1036            if true {
1037                state.renderer.update_drawable_size(size(
1038                    DevicePixels(gpu_size.width as i32),
1039                    DevicePixels(gpu_size.height as i32),
1040                ));
1041                resize_args = Some((state.content_size(), state.scale_factor));
1042            }
1043            if let Some(value) = state.last_sync_counter.take() {
1044                check_reply(
1045                    || "X11 sync SetCounter failed.",
1046                    sync::set_counter(&self.xcb, state.counter_id, value),
1047                )?;
1048            }
1049        }
1050
1051        let mut callbacks = self.callbacks.borrow_mut();
1052        if let Some((content_size, scale_factor)) = resize_args {
1053            if let Some(ref mut fun) = callbacks.resize {
1054                fun(content_size, scale_factor)
1055            }
1056        }
1057        if !is_resize {
1058            if let Some(ref mut fun) = callbacks.moved {
1059                fun();
1060            }
1061        }
1062
1063        Ok(())
1064    }
1065
1066    pub fn set_active(&self, focus: bool) {
1067        if let Some(ref mut fun) = self.callbacks.borrow_mut().active_status_change {
1068            fun(focus);
1069        }
1070    }
1071
1072    pub fn set_hovered(&self, focus: bool) {
1073        if let Some(ref mut fun) = self.callbacks.borrow_mut().hovered_status_change {
1074            fun(focus);
1075        }
1076    }
1077
1078    pub fn set_appearance(&mut self, appearance: WindowAppearance) {
1079        let mut state = self.state.borrow_mut();
1080        state.appearance = appearance;
1081        let is_transparent = state.is_transparent();
1082        state.renderer.update_transparency(is_transparent);
1083        state.appearance = appearance;
1084        drop(state);
1085        let mut callbacks = self.callbacks.borrow_mut();
1086        if let Some(ref mut fun) = callbacks.appearance_changed {
1087            (fun)()
1088        }
1089    }
1090}
1091
1092impl PlatformWindow for X11Window {
1093    fn bounds(&self) -> Bounds<Pixels> {
1094        self.0.state.borrow().bounds
1095    }
1096
1097    fn is_maximized(&self) -> bool {
1098        let state = self.0.state.borrow();
1099
1100        // A maximized window that gets minimized will still retain its maximized state.
1101        !state.hidden && state.maximized_vertical && state.maximized_horizontal
1102    }
1103
1104    fn window_bounds(&self) -> WindowBounds {
1105        let state = self.0.state.borrow();
1106        if self.is_maximized() {
1107            WindowBounds::Maximized(state.bounds)
1108        } else {
1109            WindowBounds::Windowed(state.bounds)
1110        }
1111    }
1112
1113    fn inner_window_bounds(&self) -> WindowBounds {
1114        let state = self.0.state.borrow();
1115        if self.is_maximized() {
1116            WindowBounds::Maximized(state.bounds)
1117        } else {
1118            let mut bounds = state.bounds;
1119            let [left, right, top, bottom] = state.last_insets;
1120
1121            let [left, right, top, bottom] = [
1122                Pixels((left as f32) / state.scale_factor),
1123                Pixels((right as f32) / state.scale_factor),
1124                Pixels((top as f32) / state.scale_factor),
1125                Pixels((bottom as f32) / state.scale_factor),
1126            ];
1127
1128            bounds.origin.x += left;
1129            bounds.origin.y += top;
1130            bounds.size.width -= left + right;
1131            bounds.size.height -= top + bottom;
1132
1133            WindowBounds::Windowed(bounds)
1134        }
1135    }
1136
1137    fn content_size(&self) -> Size<Pixels> {
1138        // We divide by the scale factor here because this value is queried to determine how much to draw,
1139        // but it will be multiplied later by the scale to adjust for scaling.
1140        let state = self.0.state.borrow();
1141        state
1142            .content_size()
1143            .map(|size| size.div(state.scale_factor))
1144    }
1145
1146    fn resize(&mut self, size: Size<Pixels>) {
1147        let state = self.0.state.borrow();
1148        let size = size.to_device_pixels(state.scale_factor);
1149        let width = size.width.0 as u32;
1150        let height = size.height.0 as u32;
1151
1152        check_reply(
1153            || {
1154                format!(
1155                    "X11 ConfigureWindow failed. width: {}, height: {}",
1156                    width, height
1157                )
1158            },
1159            self.0.xcb.configure_window(
1160                self.0.x_window,
1161                &xproto::ConfigureWindowAux::new()
1162                    .width(width)
1163                    .height(height),
1164            ),
1165        )
1166        .log_err();
1167        self.flush().log_err();
1168    }
1169
1170    fn scale_factor(&self) -> f32 {
1171        self.0.state.borrow().scale_factor
1172    }
1173
1174    fn appearance(&self) -> WindowAppearance {
1175        self.0.state.borrow().appearance
1176    }
1177
1178    fn display(&self) -> Option<Rc<dyn PlatformDisplay>> {
1179        Some(self.0.state.borrow().display.clone())
1180    }
1181
1182    fn mouse_position(&self) -> Point<Pixels> {
1183        let reply = get_reply(
1184            || "X11 QueryPointer failed.",
1185            self.0.xcb.query_pointer(self.0.x_window),
1186        )
1187        .unwrap();
1188        Point::new((reply.root_x as u32).into(), (reply.root_y as u32).into())
1189    }
1190
1191    fn modifiers(&self) -> Modifiers {
1192        self.0
1193            .state
1194            .borrow()
1195            .client
1196            .0
1197            .upgrade()
1198            .map(|ref_cell| ref_cell.borrow().modifiers)
1199            .unwrap_or_default()
1200    }
1201
1202    fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
1203        self.0.state.borrow_mut().input_handler = Some(input_handler);
1204    }
1205
1206    fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
1207        self.0.state.borrow_mut().input_handler.take()
1208    }
1209
1210    fn prompt(
1211        &self,
1212        _level: PromptLevel,
1213        _msg: &str,
1214        _detail: Option<&str>,
1215        _answers: &[&str],
1216    ) -> Option<futures::channel::oneshot::Receiver<usize>> {
1217        None
1218    }
1219
1220    fn activate(&self) {
1221        let data = [1, xproto::Time::CURRENT_TIME.into(), 0, 0, 0];
1222        let message = xproto::ClientMessageEvent::new(
1223            32,
1224            self.0.x_window,
1225            self.0.state.borrow().atoms._NET_ACTIVE_WINDOW,
1226            data,
1227        );
1228        self.0
1229            .xcb
1230            .send_event(
1231                false,
1232                self.0.state.borrow().x_root_window,
1233                xproto::EventMask::SUBSTRUCTURE_REDIRECT | xproto::EventMask::SUBSTRUCTURE_NOTIFY,
1234                message,
1235            )
1236            .log_err();
1237        self.0
1238            .xcb
1239            .set_input_focus(
1240                xproto::InputFocus::POINTER_ROOT,
1241                self.0.x_window,
1242                xproto::Time::CURRENT_TIME,
1243            )
1244            .log_err();
1245        self.flush().unwrap();
1246    }
1247
1248    fn is_active(&self) -> bool {
1249        self.0.state.borrow().active
1250    }
1251
1252    fn is_hovered(&self) -> bool {
1253        self.0.state.borrow().hovered
1254    }
1255
1256    fn set_title(&mut self, title: &str) {
1257        check_reply(
1258            || "X11 ChangeProperty8 on WM_NAME failed.",
1259            self.0.xcb.change_property8(
1260                xproto::PropMode::REPLACE,
1261                self.0.x_window,
1262                xproto::AtomEnum::WM_NAME,
1263                xproto::AtomEnum::STRING,
1264                title.as_bytes(),
1265            ),
1266        )
1267        .log_err();
1268
1269        check_reply(
1270            || "X11 ChangeProperty8 on _NET_WM_NAME failed.",
1271            self.0.xcb.change_property8(
1272                xproto::PropMode::REPLACE,
1273                self.0.x_window,
1274                self.0.state.borrow().atoms._NET_WM_NAME,
1275                self.0.state.borrow().atoms.UTF8_STRING,
1276                title.as_bytes(),
1277            ),
1278        )
1279        .log_err();
1280        self.flush().log_err();
1281    }
1282
1283    fn set_app_id(&mut self, app_id: &str) {
1284        let mut data = Vec::with_capacity(app_id.len() * 2 + 1);
1285        data.extend(app_id.bytes()); // instance https://unix.stackexchange.com/a/494170
1286        data.push(b'\0');
1287        data.extend(app_id.bytes()); // class
1288
1289        check_reply(
1290            || "X11 ChangeProperty8 for WM_CLASS failed.",
1291            self.0.xcb.change_property8(
1292                xproto::PropMode::REPLACE,
1293                self.0.x_window,
1294                xproto::AtomEnum::WM_CLASS,
1295                xproto::AtomEnum::STRING,
1296                &data,
1297            ),
1298        )
1299        .unwrap();
1300    }
1301
1302    fn map_window(&mut self) -> anyhow::Result<()> {
1303        check_reply(
1304            || "X11 MapWindow failed.",
1305            self.0.xcb.map_window(self.0.x_window),
1306        )?;
1307        Ok(())
1308    }
1309
1310    fn set_edited(&mut self, _edited: bool) {
1311        log::info!("ignoring macOS specific set_edited");
1312    }
1313
1314    fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance) {
1315        let mut state = self.0.state.borrow_mut();
1316        state.background_appearance = background_appearance;
1317        let transparent = state.is_transparent();
1318        state.renderer.update_transparency(transparent);
1319    }
1320
1321    fn show_character_palette(&self) {
1322        log::info!("ignoring macOS specific show_character_palette");
1323    }
1324
1325    fn minimize(&self) {
1326        let state = self.0.state.borrow();
1327        const WINDOW_ICONIC_STATE: u32 = 3;
1328        let message = ClientMessageEvent::new(
1329            32,
1330            self.0.x_window,
1331            state.atoms.WM_CHANGE_STATE,
1332            [WINDOW_ICONIC_STATE, 0, 0, 0, 0],
1333        );
1334        check_reply(
1335            || "X11 SendEvent to minimize window failed.",
1336            self.0.xcb.send_event(
1337                false,
1338                state.x_root_window,
1339                EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
1340                message,
1341            ),
1342        )
1343        .unwrap();
1344    }
1345
1346    fn zoom(&self) {
1347        let state = self.0.state.borrow();
1348        self.set_wm_hints(
1349            || "X11 SendEvent to maximize a window failed.",
1350            WmHintPropertyState::Toggle,
1351            state.atoms._NET_WM_STATE_MAXIMIZED_VERT,
1352            state.atoms._NET_WM_STATE_MAXIMIZED_HORZ,
1353        )
1354        .unwrap();
1355    }
1356
1357    fn toggle_fullscreen(&self) {
1358        let state = self.0.state.borrow();
1359        self.set_wm_hints(
1360            || "X11 SendEvent to fullscreen a window failed.",
1361            WmHintPropertyState::Toggle,
1362            state.atoms._NET_WM_STATE_FULLSCREEN,
1363            xproto::AtomEnum::NONE.into(),
1364        )
1365        .unwrap();
1366    }
1367
1368    fn is_fullscreen(&self) -> bool {
1369        self.0.state.borrow().fullscreen
1370    }
1371
1372    fn on_request_frame(&self, callback: Box<dyn FnMut(RequestFrameOptions)>) {
1373        self.0.callbacks.borrow_mut().request_frame = Some(callback);
1374    }
1375
1376    fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>) {
1377        self.0.callbacks.borrow_mut().input = Some(callback);
1378    }
1379
1380    fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
1381        self.0.callbacks.borrow_mut().active_status_change = Some(callback);
1382    }
1383
1384    fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>) {
1385        self.0.callbacks.borrow_mut().hovered_status_change = Some(callback);
1386    }
1387
1388    fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
1389        self.0.callbacks.borrow_mut().resize = Some(callback);
1390    }
1391
1392    fn on_moved(&self, callback: Box<dyn FnMut()>) {
1393        self.0.callbacks.borrow_mut().moved = Some(callback);
1394    }
1395
1396    fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
1397        self.0.callbacks.borrow_mut().should_close = Some(callback);
1398    }
1399
1400    fn on_close(&self, callback: Box<dyn FnOnce()>) {
1401        self.0.callbacks.borrow_mut().close = Some(callback);
1402    }
1403
1404    fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
1405        self.0.callbacks.borrow_mut().appearance_changed = Some(callback);
1406    }
1407
1408    fn draw(&self, scene: &Scene) {
1409        let mut inner = self.0.state.borrow_mut();
1410        inner.renderer.draw(scene);
1411    }
1412
1413    fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
1414        let inner = self.0.state.borrow();
1415        inner.renderer.sprite_atlas().clone()
1416    }
1417
1418    fn show_window_menu(&self, position: Point<Pixels>) {
1419        let state = self.0.state.borrow();
1420
1421        check_reply(
1422            || "X11 UngrabPointer failed.",
1423            self.0.xcb.ungrab_pointer(x11rb::CURRENT_TIME),
1424        )
1425        .unwrap();
1426
1427        let coords = self.get_root_position(position).unwrap();
1428        let message = ClientMessageEvent::new(
1429            32,
1430            self.0.x_window,
1431            state.atoms._GTK_SHOW_WINDOW_MENU,
1432            [
1433                XINPUT_ALL_DEVICE_GROUPS as u32,
1434                coords.dst_x as u32,
1435                coords.dst_y as u32,
1436                0,
1437                0,
1438            ],
1439        );
1440        check_reply(
1441            || "X11 SendEvent to show window menu failed.",
1442            self.0.xcb.send_event(
1443                false,
1444                state.x_root_window,
1445                EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
1446                message,
1447            ),
1448        )
1449        .unwrap();
1450    }
1451
1452    fn start_window_move(&self) {
1453        const MOVERESIZE_MOVE: u32 = 8;
1454        self.send_moveresize(MOVERESIZE_MOVE).unwrap();
1455    }
1456
1457    fn start_window_resize(&self, edge: ResizeEdge) {
1458        self.send_moveresize(edge.to_moveresize()).unwrap();
1459    }
1460
1461    fn window_decorations(&self) -> crate::Decorations {
1462        let state = self.0.state.borrow();
1463
1464        // Client window decorations require compositor support
1465        if !state.client_side_decorations_supported {
1466            return Decorations::Server;
1467        }
1468
1469        match state.decorations {
1470            WindowDecorations::Server => Decorations::Server,
1471            WindowDecorations::Client => {
1472                let tiling = if state.fullscreen {
1473                    Tiling::tiled()
1474                } else if let Some(edge_constraints) = &state.edge_constraints {
1475                    edge_constraints.to_tiling()
1476                } else {
1477                    // https://source.chromium.org/chromium/chromium/src/+/main:ui/ozone/platform/x11/x11_window.cc;l=2519;drc=1f14cc876cc5bf899d13284a12c451498219bb2d
1478                    Tiling {
1479                        top: state.maximized_vertical,
1480                        bottom: state.maximized_vertical,
1481                        left: state.maximized_horizontal,
1482                        right: state.maximized_horizontal,
1483                    }
1484                };
1485                Decorations::Client { tiling }
1486            }
1487        }
1488    }
1489
1490    fn set_client_inset(&self, inset: Pixels) {
1491        let mut state = self.0.state.borrow_mut();
1492
1493        let dp = (inset.0 * state.scale_factor) as u32;
1494
1495        let insets = if state.fullscreen {
1496            [0, 0, 0, 0]
1497        } else if let Some(edge_constraints) = &state.edge_constraints {
1498            let left = if edge_constraints.left_tiled { 0 } else { dp };
1499            let top = if edge_constraints.top_tiled { 0 } else { dp };
1500            let right = if edge_constraints.right_tiled { 0 } else { dp };
1501            let bottom = if edge_constraints.bottom_tiled { 0 } else { dp };
1502
1503            [left, right, top, bottom]
1504        } else {
1505            let (left, right) = if state.maximized_horizontal {
1506                (0, 0)
1507            } else {
1508                (dp, dp)
1509            };
1510            let (top, bottom) = if state.maximized_vertical {
1511                (0, 0)
1512            } else {
1513                (dp, dp)
1514            };
1515            [left, right, top, bottom]
1516        };
1517
1518        if state.last_insets != insets {
1519            state.last_insets = insets;
1520
1521            check_reply(
1522                || "X11 ChangeProperty for _GTK_FRAME_EXTENTS failed.",
1523                self.0.xcb.change_property(
1524                    xproto::PropMode::REPLACE,
1525                    self.0.x_window,
1526                    state.atoms._GTK_FRAME_EXTENTS,
1527                    xproto::AtomEnum::CARDINAL,
1528                    size_of::<u32>() as u8 * 8,
1529                    4,
1530                    bytemuck::cast_slice::<u32, u8>(&insets),
1531                ),
1532            )
1533            .unwrap();
1534        }
1535    }
1536
1537    fn request_decorations(&self, mut decorations: crate::WindowDecorations) {
1538        let mut state = self.0.state.borrow_mut();
1539
1540        if matches!(decorations, crate::WindowDecorations::Client)
1541            && !state.client_side_decorations_supported
1542        {
1543            log::info!(
1544                "x11: no compositor present, falling back to server-side window decorations"
1545            );
1546            decorations = crate::WindowDecorations::Server;
1547        }
1548
1549        // https://github.com/rust-windowing/winit/blob/master/src/platform_impl/linux/x11/util/hint.rs#L53-L87
1550        let hints_data: [u32; 5] = match decorations {
1551            WindowDecorations::Server => [1 << 1, 0, 1, 0, 0],
1552            WindowDecorations::Client => [1 << 1, 0, 0, 0, 0],
1553        };
1554
1555        check_reply(
1556            || "X11 ChangeProperty for _MOTIF_WM_HINTS failed.",
1557            self.0.xcb.change_property(
1558                xproto::PropMode::REPLACE,
1559                self.0.x_window,
1560                state.atoms._MOTIF_WM_HINTS,
1561                state.atoms._MOTIF_WM_HINTS,
1562                size_of::<u32>() as u8 * 8,
1563                5,
1564                bytemuck::cast_slice::<u32, u8>(&hints_data),
1565            ),
1566        )
1567        .unwrap();
1568
1569        match decorations {
1570            WindowDecorations::Server => {
1571                state.decorations = WindowDecorations::Server;
1572                let is_transparent = state.is_transparent();
1573                state.renderer.update_transparency(is_transparent);
1574            }
1575            WindowDecorations::Client => {
1576                state.decorations = WindowDecorations::Client;
1577                let is_transparent = state.is_transparent();
1578                state.renderer.update_transparency(is_transparent);
1579            }
1580        }
1581
1582        drop(state);
1583        let mut callbacks = self.0.callbacks.borrow_mut();
1584        if let Some(appearance_changed) = callbacks.appearance_changed.as_mut() {
1585            appearance_changed();
1586        }
1587    }
1588
1589    fn update_ime_position(&self, bounds: Bounds<ScaledPixels>) {
1590        let mut state = self.0.state.borrow_mut();
1591        let client = state.client.clone();
1592        drop(state);
1593        client.update_ime_position(bounds);
1594    }
1595
1596    fn gpu_specs(&self) -> Option<GpuSpecs> {
1597        self.0.state.borrow().renderer.gpu_specs().into()
1598    }
1599}