window.rs

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