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