window.rs

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