window.rs

   1use anyhow::{anyhow, Context};
   2
   3use crate::{
   4    platform::blade::{BladeRenderer, BladeSurfaceConfig},
   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_NAME,
  59        _NET_WM_STATE,
  60        _NET_WM_STATE_MAXIMIZED_VERT,
  61        _NET_WM_STATE_MAXIMIZED_HORZ,
  62        _NET_WM_STATE_FULLSCREEN,
  63        _NET_WM_STATE_HIDDEN,
  64        _NET_WM_STATE_FOCUSED,
  65        _NET_ACTIVE_WINDOW,
  66        _NET_WM_SYNC_REQUEST,
  67        _NET_WM_SYNC_REQUEST_COUNTER,
  68        _NET_WM_BYPASS_COMPOSITOR,
  69        _NET_WM_MOVERESIZE,
  70        _NET_WM_WINDOW_TYPE,
  71        _NET_WM_WINDOW_TYPE_NOTIFICATION,
  72        _NET_WM_SYNC,
  73        _NET_SUPPORTED,
  74        _MOTIF_WM_HINTS,
  75        _GTK_SHOW_WINDOW_MENU,
  76        _GTK_FRAME_EXTENTS,
  77        _GTK_EDGE_CONSTRAINTS,
  78        _NET_CLIENT_LIST_STACKING,
  79    }
  80}
  81
  82fn query_render_extent(
  83    xcb: &Rc<XCBConnection>,
  84    x_window: xproto::Window,
  85) -> anyhow::Result<gpu::Extent> {
  86    let reply = get_reply(|| "X11 GetGeometry failed.", xcb.get_geometry(x_window))?;
  87    Ok(gpu::Extent {
  88        width: reply.width as u32,
  89        height: reply.height as u32,
  90        depth: 1,
  91    })
  92}
  93
  94impl ResizeEdge {
  95    fn to_moveresize(&self) -> u32 {
  96        match self {
  97            ResizeEdge::TopLeft => 0,
  98            ResizeEdge::Top => 1,
  99            ResizeEdge::TopRight => 2,
 100            ResizeEdge::Right => 3,
 101            ResizeEdge::BottomRight => 4,
 102            ResizeEdge::Bottom => 5,
 103            ResizeEdge::BottomLeft => 6,
 104            ResizeEdge::Left => 7,
 105        }
 106    }
 107}
 108
 109#[derive(Debug)]
 110struct EdgeConstraints {
 111    top_tiled: bool,
 112    #[allow(dead_code)]
 113    top_resizable: bool,
 114
 115    right_tiled: bool,
 116    #[allow(dead_code)]
 117    right_resizable: bool,
 118
 119    bottom_tiled: bool,
 120    #[allow(dead_code)]
 121    bottom_resizable: bool,
 122
 123    left_tiled: bool,
 124    #[allow(dead_code)]
 125    left_resizable: bool,
 126}
 127
 128impl EdgeConstraints {
 129    fn from_atom(atom: u32) -> Self {
 130        EdgeConstraints {
 131            top_tiled: (atom & (1 << 0)) != 0,
 132            top_resizable: (atom & (1 << 1)) != 0,
 133            right_tiled: (atom & (1 << 2)) != 0,
 134            right_resizable: (atom & (1 << 3)) != 0,
 135            bottom_tiled: (atom & (1 << 4)) != 0,
 136            bottom_resizable: (atom & (1 << 5)) != 0,
 137            left_tiled: (atom & (1 << 6)) != 0,
 138            left_resizable: (atom & (1 << 7)) != 0,
 139        }
 140    }
 141
 142    fn to_tiling(&self) -> Tiling {
 143        Tiling {
 144            top: self.top_tiled,
 145            right: self.right_tiled,
 146            bottom: self.bottom_tiled,
 147            left: self.left_tiled,
 148        }
 149    }
 150}
 151
 152#[derive(Copy, Clone, Debug)]
 153struct Visual {
 154    id: xproto::Visualid,
 155    colormap: u32,
 156    depth: u8,
 157}
 158
 159struct VisualSet {
 160    inherit: Visual,
 161    opaque: Option<Visual>,
 162    transparent: Option<Visual>,
 163    root: u32,
 164    black_pixel: u32,
 165}
 166
 167fn find_visuals(xcb: &XCBConnection, screen_index: usize) -> VisualSet {
 168    let screen = &xcb.setup().roots[screen_index];
 169    let mut set = VisualSet {
 170        inherit: Visual {
 171            id: screen.root_visual,
 172            colormap: screen.default_colormap,
 173            depth: screen.root_depth,
 174        },
 175        opaque: None,
 176        transparent: None,
 177        root: screen.root,
 178        black_pixel: screen.black_pixel,
 179    };
 180
 181    for depth_info in screen.allowed_depths.iter() {
 182        for visual_type in depth_info.visuals.iter() {
 183            let visual = Visual {
 184                id: visual_type.visual_id,
 185                colormap: 0,
 186                depth: depth_info.depth,
 187            };
 188            log::debug!("Visual id: {}, class: {:?}, depth: {}, bits_per_value: {}, masks: 0x{:x} 0x{:x} 0x{:x}",
 189                visual_type.visual_id,
 190                visual_type.class,
 191                depth_info.depth,
 192                visual_type.bits_per_rgb_value,
 193                visual_type.red_mask, visual_type.green_mask, visual_type.blue_mask,
 194            );
 195
 196            if (
 197                visual_type.red_mask,
 198                visual_type.green_mask,
 199                visual_type.blue_mask,
 200            ) != (0xFF0000, 0xFF00, 0xFF)
 201            {
 202                continue;
 203            }
 204            let color_mask = visual_type.red_mask | visual_type.green_mask | visual_type.blue_mask;
 205            let alpha_mask = color_mask as usize ^ ((1usize << depth_info.depth) - 1);
 206
 207            if alpha_mask == 0 {
 208                if set.opaque.is_none() {
 209                    set.opaque = Some(visual);
 210                }
 211            } else {
 212                if set.transparent.is_none() {
 213                    set.transparent = Some(visual);
 214                }
 215            }
 216        }
 217    }
 218
 219    set
 220}
 221
 222struct RawWindow {
 223    connection: *mut c_void,
 224    screen_id: usize,
 225    window_id: u32,
 226    visual_id: u32,
 227}
 228
 229#[derive(Default)]
 230pub struct Callbacks {
 231    request_frame: Option<Box<dyn FnMut(RequestFrameOptions)>>,
 232    input: Option<Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>>,
 233    active_status_change: Option<Box<dyn FnMut(bool)>>,
 234    hovered_status_change: Option<Box<dyn FnMut(bool)>>,
 235    resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
 236    moved: Option<Box<dyn FnMut()>>,
 237    should_close: Option<Box<dyn FnMut() -> bool>>,
 238    close: Option<Box<dyn FnOnce()>>,
 239    appearance_changed: Option<Box<dyn FnMut()>>,
 240}
 241
 242pub struct X11WindowState {
 243    pub destroyed: bool,
 244    client: X11ClientStatePtr,
 245    executor: ForegroundExecutor,
 246    atoms: XcbAtoms,
 247    x_root_window: xproto::Window,
 248    pub(crate) counter_id: sync::Counter,
 249    pub(crate) last_sync_counter: Option<sync::Int64>,
 250    _raw: RawWindow,
 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    #[allow(clippy::too_many_arguments)]
 357    pub fn new(
 358        handle: AnyWindowHandle,
 359        client: X11ClientStatePtr,
 360        executor: ForegroundExecutor,
 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            if let Some(size) = params.window_min_size {
 440                let mut size_hints = WmSizeHints::new();
 441                let min_size = (size.width.0 as i32, size.height.0 as i32);
 442                size_hints.min_size = Some(min_size);
 443                check_reply(
 444                    || {
 445                        format!(
 446                            "X11 change of WM_SIZE_HINTS failed. min_size: {:?}",
 447                            min_size
 448                        )
 449                    },
 450                    size_hints.set_normal_hints(xcb, x_window),
 451                )?;
 452            }
 453
 454            let reply = get_reply(|| "X11 GetGeometry failed.", xcb.get_geometry(x_window))?;
 455            if reply.x == 0 && reply.y == 0 {
 456                bounds.origin.x.0 += 2;
 457                // Work around a bug where our rendered content appears
 458                // outside the window bounds when opened at the default position
 459                // (14px, 49px on X + Gnome + Ubuntu 22).
 460                let x = bounds.origin.x.0;
 461                let y = bounds.origin.y.0;
 462                check_reply(
 463                    || format!("X11 ConfigureWindow failed. x: {}, y: {}", x, y),
 464                    xcb.configure_window(x_window, &xproto::ConfigureWindowAux::new().x(x).y(y)),
 465                )?;
 466            }
 467            if let Some(titlebar) = params.titlebar {
 468                if let Some(title) = titlebar.title {
 469                    check_reply(
 470                        || "X11 ChangeProperty8 on window title failed.",
 471                        xcb.change_property8(
 472                            xproto::PropMode::REPLACE,
 473                            x_window,
 474                            xproto::AtomEnum::WM_NAME,
 475                            xproto::AtomEnum::STRING,
 476                            title.as_bytes(),
 477                        ),
 478                    )?;
 479                }
 480            }
 481            if params.kind == WindowKind::PopUp {
 482                check_reply(
 483                    || "X11 ChangeProperty32 setting window type for pop-up failed.",
 484                    xcb.change_property32(
 485                        xproto::PropMode::REPLACE,
 486                        x_window,
 487                        atoms._NET_WM_WINDOW_TYPE,
 488                        xproto::AtomEnum::ATOM,
 489                        &[atoms._NET_WM_WINDOW_TYPE_NOTIFICATION],
 490                    ),
 491                )?;
 492            }
 493
 494            check_reply(
 495                || "X11 ChangeProperty32 setting protocols failed.",
 496                xcb.change_property32(
 497                    xproto::PropMode::REPLACE,
 498                    x_window,
 499                    atoms.WM_PROTOCOLS,
 500                    xproto::AtomEnum::ATOM,
 501                    &[atoms.WM_DELETE_WINDOW, atoms._NET_WM_SYNC_REQUEST],
 502                ),
 503            )?;
 504
 505            get_reply(
 506                || "X11 sync protocol initialize failed.",
 507                sync::initialize(xcb, 3, 1),
 508            )?;
 509            let sync_request_counter = xcb.generate_id()?;
 510            check_reply(
 511                || "X11 sync CreateCounter failed.",
 512                sync::create_counter(xcb, sync_request_counter, sync::Int64 { lo: 0, hi: 0 }),
 513            )?;
 514
 515            check_reply(
 516                || "X11 ChangeProperty32 setting sync request counter failed.",
 517                xcb.change_property32(
 518                    xproto::PropMode::REPLACE,
 519                    x_window,
 520                    atoms._NET_WM_SYNC_REQUEST_COUNTER,
 521                    xproto::AtomEnum::CARDINAL,
 522                    &[sync_request_counter],
 523                ),
 524            )?;
 525
 526            check_reply(
 527                || "X11 XiSelectEvents failed.",
 528                xcb.xinput_xi_select_events(
 529                    x_window,
 530                    &[xinput::EventMask {
 531                        deviceid: XINPUT_ALL_DEVICE_GROUPS,
 532                        mask: vec![
 533                            xinput::XIEventMask::MOTION
 534                                | xinput::XIEventMask::BUTTON_PRESS
 535                                | xinput::XIEventMask::BUTTON_RELEASE
 536                                | xinput::XIEventMask::ENTER
 537                                | xinput::XIEventMask::LEAVE,
 538                        ],
 539                    }],
 540                ),
 541            )?;
 542
 543            check_reply(
 544                || "X11 XiSelectEvents for device changes failed.",
 545                xcb.xinput_xi_select_events(
 546                    x_window,
 547                    &[xinput::EventMask {
 548                        deviceid: XINPUT_ALL_DEVICES,
 549                        mask: vec![
 550                            xinput::XIEventMask::HIERARCHY | xinput::XIEventMask::DEVICE_CHANGED,
 551                        ],
 552                    }],
 553                ),
 554            )?;
 555
 556            xcb.flush().with_context(|| "X11 Flush failed.")?;
 557
 558            let raw = RawWindow {
 559                connection: as_raw_xcb_connection::AsRawXcbConnection::as_raw_xcb_connection(xcb)
 560                    as *mut _,
 561                screen_id: x_screen_index,
 562                window_id: x_window,
 563                visual_id: visual.id,
 564            };
 565            let gpu = Arc::new(
 566                unsafe {
 567                    gpu::Context::init_windowed(
 568                        &raw,
 569                        gpu::ContextDesc {
 570                            validation: false,
 571                            capture: false,
 572                            overlay: false,
 573                        },
 574                    )
 575                }
 576                .map_err(|e| anyhow!("{:?}", e))?,
 577            );
 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            check_reply(|| "X11 MapWindow failed.", xcb.map_window(x_window))?;
 590
 591            let display = Rc::new(X11Display::new(xcb, scale_factor, x_screen_index)?);
 592
 593            Ok(Self {
 594                client,
 595                executor,
 596                display,
 597                _raw: raw,
 598                x_root_window: visual_set.root,
 599                bounds: bounds.to_pixels(scale_factor),
 600                scale_factor,
 601                renderer: BladeRenderer::new(gpu, config),
 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    #[allow(clippy::too_many_arguments)]
 715    pub fn new(
 716        handle: AnyWindowHandle,
 717        client: X11ClientStatePtr,
 718        executor: ForegroundExecutor,
 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                params,
 734                xcb,
 735                client_side_decorations_supported,
 736                x_main_screen_index,
 737                x_window,
 738                atoms,
 739                scale_factor,
 740                appearance,
 741            )?)),
 742            callbacks: Rc::new(RefCell::new(Callbacks::default())),
 743            xcb: xcb.clone(),
 744            x_window,
 745        };
 746
 747        let state = ptr.state.borrow_mut();
 748        ptr.set_wm_properties(state)?;
 749
 750        Ok(Self(ptr))
 751    }
 752
 753    fn set_wm_hints<C: Display + Send + Sync + 'static, F: FnOnce() -> C>(
 754        &self,
 755        failure_context: F,
 756        wm_hint_property_state: WmHintPropertyState,
 757        prop1: u32,
 758        prop2: u32,
 759    ) -> anyhow::Result<()> {
 760        let state = self.0.state.borrow();
 761        let message = ClientMessageEvent::new(
 762            32,
 763            self.0.x_window,
 764            state.atoms._NET_WM_STATE,
 765            [wm_hint_property_state as u32, prop1, prop2, 1, 0],
 766        );
 767        check_reply(
 768            failure_context,
 769            self.0.xcb.send_event(
 770                false,
 771                state.x_root_window,
 772                EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
 773                message,
 774            ),
 775        )
 776    }
 777
 778    fn get_root_position(
 779        &self,
 780        position: Point<Pixels>,
 781    ) -> anyhow::Result<TranslateCoordinatesReply> {
 782        let state = self.0.state.borrow();
 783        get_reply(
 784            || "X11 TranslateCoordinates failed.",
 785            self.0.xcb.translate_coordinates(
 786                self.0.x_window,
 787                state.x_root_window,
 788                (position.x.0 * state.scale_factor) as i16,
 789                (position.y.0 * state.scale_factor) as i16,
 790            ),
 791        )
 792    }
 793
 794    fn send_moveresize(&self, flag: u32) -> anyhow::Result<()> {
 795        let state = self.0.state.borrow();
 796
 797        check_reply(
 798            || "X11 UngrabPointer before move/resize of window ailed.",
 799            self.0.xcb.ungrab_pointer(x11rb::CURRENT_TIME),
 800        )?;
 801
 802        let pointer = get_reply(
 803            || "X11 QueryPointer before move/resize of window failed.",
 804            self.0.xcb.query_pointer(self.0.x_window),
 805        )?;
 806        let message = ClientMessageEvent::new(
 807            32,
 808            self.0.x_window,
 809            state.atoms._NET_WM_MOVERESIZE,
 810            [
 811                pointer.root_x as u32,
 812                pointer.root_y as u32,
 813                flag,
 814                0, // Left mouse button
 815                0,
 816            ],
 817        );
 818        check_reply(
 819            || "X11 SendEvent to move/resize window failed.",
 820            self.0.xcb.send_event(
 821                false,
 822                state.x_root_window,
 823                EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
 824                message,
 825            ),
 826        )?;
 827
 828        self.flush()
 829    }
 830
 831    fn flush(&self) -> anyhow::Result<()> {
 832        self.0.xcb.flush().with_context(|| "X11 Flush failed.")
 833    }
 834}
 835
 836impl X11WindowStatePtr {
 837    pub fn should_close(&self) -> bool {
 838        let mut cb = self.callbacks.borrow_mut();
 839        if let Some(mut should_close) = cb.should_close.take() {
 840            let result = (should_close)();
 841            cb.should_close = Some(should_close);
 842            result
 843        } else {
 844            true
 845        }
 846    }
 847
 848    pub fn property_notify(&self, event: xproto::PropertyNotifyEvent) -> anyhow::Result<()> {
 849        let mut state = self.state.borrow_mut();
 850        if event.atom == state.atoms._NET_WM_STATE {
 851            self.set_wm_properties(state)?;
 852        } else if event.atom == state.atoms._GTK_EDGE_CONSTRAINTS {
 853            self.set_edge_constraints(state)?;
 854        }
 855        Ok(())
 856    }
 857
 858    fn set_edge_constraints(
 859        &self,
 860        mut state: std::cell::RefMut<X11WindowState>,
 861    ) -> anyhow::Result<()> {
 862        let reply = get_reply(
 863            || "X11 GetProperty for _GTK_EDGE_CONSTRAINTS failed.",
 864            self.xcb.get_property(
 865                false,
 866                self.x_window,
 867                state.atoms._GTK_EDGE_CONSTRAINTS,
 868                xproto::AtomEnum::CARDINAL,
 869                0,
 870                4,
 871            ),
 872        )?;
 873
 874        if reply.value_len != 0 {
 875            let atom = u32::from_ne_bytes(reply.value[0..4].try_into().unwrap());
 876            let edge_constraints = EdgeConstraints::from_atom(atom);
 877            state.edge_constraints.replace(edge_constraints);
 878        }
 879
 880        Ok(())
 881    }
 882
 883    fn set_wm_properties(
 884        &self,
 885        mut state: std::cell::RefMut<X11WindowState>,
 886    ) -> anyhow::Result<()> {
 887        let reply = get_reply(
 888            || "X11 GetProperty for _NET_WM_STATE failed.",
 889            self.xcb.get_property(
 890                false,
 891                self.x_window,
 892                state.atoms._NET_WM_STATE,
 893                xproto::AtomEnum::ATOM,
 894                0,
 895                u32::MAX,
 896            ),
 897        )?;
 898
 899        let atoms = reply
 900            .value
 901            .chunks_exact(4)
 902            .map(|chunk| u32::from_ne_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]));
 903
 904        state.active = false;
 905        state.fullscreen = false;
 906        state.maximized_vertical = false;
 907        state.maximized_horizontal = false;
 908        state.hidden = true;
 909
 910        for atom in atoms {
 911            if atom == state.atoms._NET_WM_STATE_FOCUSED {
 912                state.active = true;
 913            } else if atom == state.atoms._NET_WM_STATE_FULLSCREEN {
 914                state.fullscreen = true;
 915            } else if atom == state.atoms._NET_WM_STATE_MAXIMIZED_VERT {
 916                state.maximized_vertical = true;
 917            } else if atom == state.atoms._NET_WM_STATE_MAXIMIZED_HORZ {
 918                state.maximized_horizontal = true;
 919            } else if atom == state.atoms._NET_WM_STATE_HIDDEN {
 920                state.hidden = true;
 921            }
 922        }
 923
 924        Ok(())
 925    }
 926
 927    pub fn close(&self) {
 928        let mut callbacks = self.callbacks.borrow_mut();
 929        if let Some(fun) = callbacks.close.take() {
 930            fun()
 931        }
 932    }
 933
 934    pub fn refresh(&self, request_frame_options: RequestFrameOptions) {
 935        let mut cb = self.callbacks.borrow_mut();
 936        if let Some(ref mut fun) = cb.request_frame {
 937            fun(request_frame_options);
 938        }
 939    }
 940
 941    pub fn handle_input(&self, input: PlatformInput) {
 942        if let Some(ref mut fun) = self.callbacks.borrow_mut().input {
 943            if !fun(input.clone()).propagate {
 944                return;
 945            }
 946        }
 947        if let PlatformInput::KeyDown(event) = input {
 948            let mut state = self.state.borrow_mut();
 949            if let Some(mut input_handler) = state.input_handler.take() {
 950                if let Some(key_char) = &event.keystroke.key_char {
 951                    drop(state);
 952                    input_handler.replace_text_in_range(None, key_char);
 953                    state = self.state.borrow_mut();
 954                }
 955                state.input_handler = Some(input_handler);
 956            }
 957        }
 958    }
 959
 960    pub fn handle_ime_commit(&self, text: String) {
 961        let mut state = self.state.borrow_mut();
 962        if let Some(mut input_handler) = state.input_handler.take() {
 963            drop(state);
 964            input_handler.replace_text_in_range(None, &text);
 965            let mut state = self.state.borrow_mut();
 966            state.input_handler = Some(input_handler);
 967        }
 968    }
 969
 970    pub fn handle_ime_preedit(&self, text: String) {
 971        let mut state = self.state.borrow_mut();
 972        if let Some(mut input_handler) = state.input_handler.take() {
 973            drop(state);
 974            input_handler.replace_and_mark_text_in_range(None, &text, None);
 975            let mut state = self.state.borrow_mut();
 976            state.input_handler = Some(input_handler);
 977        }
 978    }
 979
 980    pub fn handle_ime_unmark(&self) {
 981        let mut state = self.state.borrow_mut();
 982        if let Some(mut input_handler) = state.input_handler.take() {
 983            drop(state);
 984            input_handler.unmark_text();
 985            let mut state = self.state.borrow_mut();
 986            state.input_handler = Some(input_handler);
 987        }
 988    }
 989
 990    pub fn handle_ime_delete(&self) {
 991        let mut state = self.state.borrow_mut();
 992        if let Some(mut input_handler) = state.input_handler.take() {
 993            drop(state);
 994            if let Some(marked) = input_handler.marked_text_range() {
 995                input_handler.replace_text_in_range(Some(marked), "");
 996            }
 997            let mut state = self.state.borrow_mut();
 998            state.input_handler = Some(input_handler);
 999        }
1000    }
1001
1002    pub fn get_ime_area(&self) -> Option<Bounds<Pixels>> {
1003        let mut state = self.state.borrow_mut();
1004        let mut bounds: Option<Bounds<Pixels>> = None;
1005        if let Some(mut input_handler) = state.input_handler.take() {
1006            drop(state);
1007            if let Some(selection) = input_handler.selected_text_range(true) {
1008                bounds = input_handler.bounds_for_range(selection.range);
1009            }
1010            let mut state = self.state.borrow_mut();
1011            state.input_handler = Some(input_handler);
1012        };
1013        bounds
1014    }
1015
1016    pub fn configure(&self, bounds: Bounds<i32>) -> anyhow::Result<()> {
1017        let mut resize_args = None;
1018        let is_resize;
1019        {
1020            let mut state = self.state.borrow_mut();
1021            let bounds = bounds.map(|f| px(f as f32 / state.scale_factor));
1022
1023            is_resize = bounds.size.width != state.bounds.size.width
1024                || bounds.size.height != state.bounds.size.height;
1025
1026            // If it's a resize event (only width/height changed), we ignore `bounds.origin`
1027            // because it contains wrong values.
1028            if is_resize {
1029                state.bounds.size = bounds.size;
1030            } else {
1031                state.bounds = bounds;
1032            }
1033
1034            let gpu_size = query_render_extent(&self.xcb, self.x_window)?;
1035            if true {
1036                state.renderer.update_drawable_size(size(
1037                    DevicePixels(gpu_size.width as i32),
1038                    DevicePixels(gpu_size.height as i32),
1039                ));
1040                resize_args = Some((state.content_size(), state.scale_factor));
1041            }
1042            if let Some(value) = state.last_sync_counter.take() {
1043                check_reply(
1044                    || "X11 sync SetCounter failed.",
1045                    sync::set_counter(&self.xcb, state.counter_id, value),
1046                )?;
1047            }
1048        }
1049
1050        let mut callbacks = self.callbacks.borrow_mut();
1051        if let Some((content_size, scale_factor)) = resize_args {
1052            if let Some(ref mut fun) = callbacks.resize {
1053                fun(content_size, scale_factor)
1054            }
1055        }
1056        if !is_resize {
1057            if let Some(ref mut fun) = callbacks.moved {
1058                fun();
1059            }
1060        }
1061
1062        Ok(())
1063    }
1064
1065    pub fn set_active(&self, focus: bool) {
1066        if let Some(ref mut fun) = self.callbacks.borrow_mut().active_status_change {
1067            fun(focus);
1068        }
1069    }
1070
1071    pub fn set_hovered(&self, focus: bool) {
1072        if let Some(ref mut fun) = self.callbacks.borrow_mut().hovered_status_change {
1073            fun(focus);
1074        }
1075    }
1076
1077    pub fn set_appearance(&mut self, appearance: WindowAppearance) {
1078        let mut state = self.state.borrow_mut();
1079        state.appearance = appearance;
1080        let is_transparent = state.is_transparent();
1081        state.renderer.update_transparency(is_transparent);
1082        state.appearance = appearance;
1083        drop(state);
1084        let mut callbacks = self.callbacks.borrow_mut();
1085        if let Some(ref mut fun) = callbacks.appearance_changed {
1086            (fun)()
1087        }
1088    }
1089}
1090
1091impl PlatformWindow for X11Window {
1092    fn bounds(&self) -> Bounds<Pixels> {
1093        self.0.state.borrow().bounds
1094    }
1095
1096    fn is_maximized(&self) -> bool {
1097        let state = self.0.state.borrow();
1098
1099        // A maximized window that gets minimized will still retain its maximized state.
1100        !state.hidden && state.maximized_vertical && state.maximized_horizontal
1101    }
1102
1103    fn window_bounds(&self) -> WindowBounds {
1104        let state = self.0.state.borrow();
1105        if self.is_maximized() {
1106            WindowBounds::Maximized(state.bounds)
1107        } else {
1108            WindowBounds::Windowed(state.bounds)
1109        }
1110    }
1111
1112    fn content_size(&self) -> Size<Pixels> {
1113        // We divide by the scale factor here because this value is queried to determine how much to draw,
1114        // but it will be multiplied later by the scale to adjust for scaling.
1115        let state = self.0.state.borrow();
1116        state
1117            .content_size()
1118            .map(|size| size.div(state.scale_factor))
1119    }
1120
1121    fn scale_factor(&self) -> f32 {
1122        self.0.state.borrow().scale_factor
1123    }
1124
1125    fn appearance(&self) -> WindowAppearance {
1126        self.0.state.borrow().appearance
1127    }
1128
1129    fn display(&self) -> Option<Rc<dyn PlatformDisplay>> {
1130        Some(self.0.state.borrow().display.clone())
1131    }
1132
1133    fn mouse_position(&self) -> Point<Pixels> {
1134        let reply = get_reply(
1135            || "X11 QueryPointer failed.",
1136            self.0.xcb.query_pointer(self.0.x_window),
1137        )
1138        .unwrap();
1139        Point::new((reply.root_x as u32).into(), (reply.root_y as u32).into())
1140    }
1141
1142    fn modifiers(&self) -> Modifiers {
1143        self.0
1144            .state
1145            .borrow()
1146            .client
1147            .0
1148            .upgrade()
1149            .map(|ref_cell| ref_cell.borrow().modifiers)
1150            .unwrap_or_default()
1151    }
1152
1153    fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
1154        self.0.state.borrow_mut().input_handler = Some(input_handler);
1155    }
1156
1157    fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
1158        self.0.state.borrow_mut().input_handler.take()
1159    }
1160
1161    fn prompt(
1162        &self,
1163        _level: PromptLevel,
1164        _msg: &str,
1165        _detail: Option<&str>,
1166        _answers: &[&str],
1167    ) -> Option<futures::channel::oneshot::Receiver<usize>> {
1168        None
1169    }
1170
1171    fn activate(&self) {
1172        let data = [1, xproto::Time::CURRENT_TIME.into(), 0, 0, 0];
1173        let message = xproto::ClientMessageEvent::new(
1174            32,
1175            self.0.x_window,
1176            self.0.state.borrow().atoms._NET_ACTIVE_WINDOW,
1177            data,
1178        );
1179        self.0
1180            .xcb
1181            .send_event(
1182                false,
1183                self.0.state.borrow().x_root_window,
1184                xproto::EventMask::SUBSTRUCTURE_REDIRECT | xproto::EventMask::SUBSTRUCTURE_NOTIFY,
1185                message,
1186            )
1187            .log_err();
1188        self.0
1189            .xcb
1190            .set_input_focus(
1191                xproto::InputFocus::POINTER_ROOT,
1192                self.0.x_window,
1193                xproto::Time::CURRENT_TIME,
1194            )
1195            .log_err();
1196        self.flush().unwrap();
1197    }
1198
1199    fn is_active(&self) -> bool {
1200        self.0.state.borrow().active
1201    }
1202
1203    fn is_hovered(&self) -> bool {
1204        self.0.state.borrow().hovered
1205    }
1206
1207    fn set_title(&mut self, title: &str) {
1208        check_reply(
1209            || "X11 ChangeProperty8 on WM_NAME failed.",
1210            self.0.xcb.change_property8(
1211                xproto::PropMode::REPLACE,
1212                self.0.x_window,
1213                xproto::AtomEnum::WM_NAME,
1214                xproto::AtomEnum::STRING,
1215                title.as_bytes(),
1216            ),
1217        )
1218        .log_err();
1219
1220        check_reply(
1221            || "X11 ChangeProperty8 on _NET_WM_NAME failed.",
1222            self.0.xcb.change_property8(
1223                xproto::PropMode::REPLACE,
1224                self.0.x_window,
1225                self.0.state.borrow().atoms._NET_WM_NAME,
1226                self.0.state.borrow().atoms.UTF8_STRING,
1227                title.as_bytes(),
1228            ),
1229        )
1230        .log_err();
1231        self.flush().log_err();
1232    }
1233
1234    fn set_app_id(&mut self, app_id: &str) {
1235        let mut data = Vec::with_capacity(app_id.len() * 2 + 1);
1236        data.extend(app_id.bytes()); // instance https://unix.stackexchange.com/a/494170
1237        data.push(b'\0');
1238        data.extend(app_id.bytes()); // class
1239
1240        check_reply(
1241            || "X11 ChangeProperty8 for WM_CLASS failed.",
1242            self.0.xcb.change_property8(
1243                xproto::PropMode::REPLACE,
1244                self.0.x_window,
1245                xproto::AtomEnum::WM_CLASS,
1246                xproto::AtomEnum::STRING,
1247                &data,
1248            ),
1249        )
1250        .unwrap();
1251    }
1252
1253    fn set_edited(&mut self, _edited: bool) {
1254        log::info!("ignoring macOS specific set_edited");
1255    }
1256
1257    fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance) {
1258        let mut state = self.0.state.borrow_mut();
1259        state.background_appearance = background_appearance;
1260        let transparent = state.is_transparent();
1261        state.renderer.update_transparency(transparent);
1262    }
1263
1264    fn show_character_palette(&self) {
1265        log::info!("ignoring macOS specific show_character_palette");
1266    }
1267
1268    fn minimize(&self) {
1269        let state = self.0.state.borrow();
1270        const WINDOW_ICONIC_STATE: u32 = 3;
1271        let message = ClientMessageEvent::new(
1272            32,
1273            self.0.x_window,
1274            state.atoms.WM_CHANGE_STATE,
1275            [WINDOW_ICONIC_STATE, 0, 0, 0, 0],
1276        );
1277        check_reply(
1278            || "X11 SendEvent to minimize window failed.",
1279            self.0.xcb.send_event(
1280                false,
1281                state.x_root_window,
1282                EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
1283                message,
1284            ),
1285        )
1286        .unwrap();
1287    }
1288
1289    fn zoom(&self) {
1290        let state = self.0.state.borrow();
1291        self.set_wm_hints(
1292            || "X11 SendEvent to maximize a window failed.",
1293            WmHintPropertyState::Toggle,
1294            state.atoms._NET_WM_STATE_MAXIMIZED_VERT,
1295            state.atoms._NET_WM_STATE_MAXIMIZED_HORZ,
1296        )
1297        .unwrap();
1298    }
1299
1300    fn toggle_fullscreen(&self) {
1301        let state = self.0.state.borrow();
1302        self.set_wm_hints(
1303            || "X11 SendEvent to fullscreen a window failed.",
1304            WmHintPropertyState::Toggle,
1305            state.atoms._NET_WM_STATE_FULLSCREEN,
1306            xproto::AtomEnum::NONE.into(),
1307        )
1308        .unwrap();
1309    }
1310
1311    fn is_fullscreen(&self) -> bool {
1312        self.0.state.borrow().fullscreen
1313    }
1314
1315    fn on_request_frame(&self, callback: Box<dyn FnMut(RequestFrameOptions)>) {
1316        self.0.callbacks.borrow_mut().request_frame = Some(callback);
1317    }
1318
1319    fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>) {
1320        self.0.callbacks.borrow_mut().input = Some(callback);
1321    }
1322
1323    fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
1324        self.0.callbacks.borrow_mut().active_status_change = Some(callback);
1325    }
1326
1327    fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>) {
1328        self.0.callbacks.borrow_mut().hovered_status_change = Some(callback);
1329    }
1330
1331    fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
1332        self.0.callbacks.borrow_mut().resize = Some(callback);
1333    }
1334
1335    fn on_moved(&self, callback: Box<dyn FnMut()>) {
1336        self.0.callbacks.borrow_mut().moved = Some(callback);
1337    }
1338
1339    fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
1340        self.0.callbacks.borrow_mut().should_close = Some(callback);
1341    }
1342
1343    fn on_close(&self, callback: Box<dyn FnOnce()>) {
1344        self.0.callbacks.borrow_mut().close = Some(callback);
1345    }
1346
1347    fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
1348        self.0.callbacks.borrow_mut().appearance_changed = Some(callback);
1349    }
1350
1351    fn draw(&self, scene: &Scene) {
1352        let mut inner = self.0.state.borrow_mut();
1353        inner.renderer.draw(scene);
1354    }
1355
1356    fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
1357        let inner = self.0.state.borrow();
1358        inner.renderer.sprite_atlas().clone()
1359    }
1360
1361    fn show_window_menu(&self, position: Point<Pixels>) {
1362        let state = self.0.state.borrow();
1363
1364        check_reply(
1365            || "X11 UngrabPointer failed.",
1366            self.0.xcb.ungrab_pointer(x11rb::CURRENT_TIME),
1367        )
1368        .unwrap();
1369
1370        let coords = self.get_root_position(position).unwrap();
1371        let message = ClientMessageEvent::new(
1372            32,
1373            self.0.x_window,
1374            state.atoms._GTK_SHOW_WINDOW_MENU,
1375            [
1376                XINPUT_ALL_DEVICE_GROUPS as u32,
1377                coords.dst_x as u32,
1378                coords.dst_y as u32,
1379                0,
1380                0,
1381            ],
1382        );
1383        check_reply(
1384            || "X11 SendEvent to show window menu failed.",
1385            self.0.xcb.send_event(
1386                false,
1387                state.x_root_window,
1388                EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
1389                message,
1390            ),
1391        )
1392        .unwrap();
1393    }
1394
1395    fn start_window_move(&self) {
1396        const MOVERESIZE_MOVE: u32 = 8;
1397        self.send_moveresize(MOVERESIZE_MOVE).unwrap();
1398    }
1399
1400    fn start_window_resize(&self, edge: ResizeEdge) {
1401        self.send_moveresize(edge.to_moveresize()).unwrap();
1402    }
1403
1404    fn window_decorations(&self) -> crate::Decorations {
1405        let state = self.0.state.borrow();
1406
1407        // Client window decorations require compositor support
1408        if !state.client_side_decorations_supported {
1409            return Decorations::Server;
1410        }
1411
1412        match state.decorations {
1413            WindowDecorations::Server => Decorations::Server,
1414            WindowDecorations::Client => {
1415                let tiling = if state.fullscreen {
1416                    Tiling::tiled()
1417                } else if let Some(edge_constraints) = &state.edge_constraints {
1418                    edge_constraints.to_tiling()
1419                } else {
1420                    // https://source.chromium.org/chromium/chromium/src/+/main:ui/ozone/platform/x11/x11_window.cc;l=2519;drc=1f14cc876cc5bf899d13284a12c451498219bb2d
1421                    Tiling {
1422                        top: state.maximized_vertical,
1423                        bottom: state.maximized_vertical,
1424                        left: state.maximized_horizontal,
1425                        right: state.maximized_horizontal,
1426                    }
1427                };
1428                Decorations::Client { tiling }
1429            }
1430        }
1431    }
1432
1433    fn set_client_inset(&self, inset: Pixels) {
1434        let mut state = self.0.state.borrow_mut();
1435
1436        let dp = (inset.0 * state.scale_factor) as u32;
1437
1438        let insets = if state.fullscreen {
1439            [0, 0, 0, 0]
1440        } else if let Some(edge_constraints) = &state.edge_constraints {
1441            let left = if edge_constraints.left_tiled { 0 } else { dp };
1442            let top = if edge_constraints.top_tiled { 0 } else { dp };
1443            let right = if edge_constraints.right_tiled { 0 } else { dp };
1444            let bottom = if edge_constraints.bottom_tiled { 0 } else { dp };
1445
1446            [left, right, top, bottom]
1447        } else {
1448            let (left, right) = if state.maximized_horizontal {
1449                (0, 0)
1450            } else {
1451                (dp, dp)
1452            };
1453            let (top, bottom) = if state.maximized_vertical {
1454                (0, 0)
1455            } else {
1456                (dp, dp)
1457            };
1458            [left, right, top, bottom]
1459        };
1460
1461        if state.last_insets != insets {
1462            state.last_insets = insets;
1463
1464            check_reply(
1465                || "X11 ChangeProperty for _GTK_FRAME_EXTENTS failed.",
1466                self.0.xcb.change_property(
1467                    xproto::PropMode::REPLACE,
1468                    self.0.x_window,
1469                    state.atoms._GTK_FRAME_EXTENTS,
1470                    xproto::AtomEnum::CARDINAL,
1471                    size_of::<u32>() as u8 * 8,
1472                    4,
1473                    bytemuck::cast_slice::<u32, u8>(&insets),
1474                ),
1475            )
1476            .unwrap();
1477        }
1478    }
1479
1480    fn request_decorations(&self, mut decorations: crate::WindowDecorations) {
1481        let mut state = self.0.state.borrow_mut();
1482
1483        if matches!(decorations, crate::WindowDecorations::Client)
1484            && !state.client_side_decorations_supported
1485        {
1486            log::info!(
1487                "x11: no compositor present, falling back to server-side window decorations"
1488            );
1489            decorations = crate::WindowDecorations::Server;
1490        }
1491
1492        // https://github.com/rust-windowing/winit/blob/master/src/platform_impl/linux/x11/util/hint.rs#L53-L87
1493        let hints_data: [u32; 5] = match decorations {
1494            WindowDecorations::Server => [1 << 1, 0, 1, 0, 0],
1495            WindowDecorations::Client => [1 << 1, 0, 0, 0, 0],
1496        };
1497
1498        check_reply(
1499            || "X11 ChangeProperty for _MOTIF_WM_HINTS failed.",
1500            self.0.xcb.change_property(
1501                xproto::PropMode::REPLACE,
1502                self.0.x_window,
1503                state.atoms._MOTIF_WM_HINTS,
1504                state.atoms._MOTIF_WM_HINTS,
1505                size_of::<u32>() as u8 * 8,
1506                5,
1507                bytemuck::cast_slice::<u32, u8>(&hints_data),
1508            ),
1509        )
1510        .unwrap();
1511
1512        match decorations {
1513            WindowDecorations::Server => {
1514                state.decorations = WindowDecorations::Server;
1515                let is_transparent = state.is_transparent();
1516                state.renderer.update_transparency(is_transparent);
1517            }
1518            WindowDecorations::Client => {
1519                state.decorations = WindowDecorations::Client;
1520                let is_transparent = state.is_transparent();
1521                state.renderer.update_transparency(is_transparent);
1522            }
1523        }
1524
1525        drop(state);
1526        let mut callbacks = self.0.callbacks.borrow_mut();
1527        if let Some(appearance_changed) = callbacks.appearance_changed.as_mut() {
1528            appearance_changed();
1529        }
1530    }
1531
1532    fn update_ime_position(&self, bounds: Bounds<ScaledPixels>) {
1533        let mut state = self.0.state.borrow_mut();
1534        let client = state.client.clone();
1535        drop(state);
1536        client.update_ime_position(bounds);
1537    }
1538
1539    fn gpu_specs(&self) -> Option<GpuSpecs> {
1540        self.0.state.borrow().renderer.gpu_specs().into()
1541    }
1542}