window.rs

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