window.rs

   1use anyhow::Context;
   2
   3use crate::{
   4    platform::blade::{BladeRenderer, BladeSurfaceConfig},
   5    px, size, AnyWindowHandle, Bounds, DevicePixels, ForegroundExecutor, Modifiers, Pixels,
   6    PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point,
   7    PromptLevel, Scene, Size, WindowAppearance, WindowBackgroundAppearance, WindowBounds,
   8    WindowKind, WindowParams, X11ClientStatePtr,
   9};
  10
  11use blade_graphics as gpu;
  12use raw_window_handle as rwh;
  13use util::{maybe, ResultExt};
  14use x11rb::{
  15    connection::Connection,
  16    protocol::{
  17        randr::{self, ConnectionExt as _},
  18        xinput::{self, ConnectionExt as _},
  19        xproto::{
  20            self, ClientMessageEvent, ConnectionExt as _, EventMask, TranslateCoordinatesReply,
  21        },
  22    },
  23    wrapper::ConnectionExt as _,
  24    xcb_ffi::XCBConnection,
  25};
  26
  27use std::{
  28    cell::RefCell,
  29    ffi::c_void,
  30    num::NonZeroU32,
  31    ops::Div,
  32    ptr::NonNull,
  33    rc::Rc,
  34    sync::{self, Arc},
  35    time::Duration,
  36};
  37
  38use super::{X11Display, XINPUT_MASTER_DEVICE};
  39x11rb::atom_manager! {
  40    pub XcbAtoms: AtomsCookie {
  41        UTF8_STRING,
  42        WM_PROTOCOLS,
  43        WM_DELETE_WINDOW,
  44        WM_CHANGE_STATE,
  45        _NET_WM_NAME,
  46        _NET_WM_STATE,
  47        _NET_WM_STATE_MAXIMIZED_VERT,
  48        _NET_WM_STATE_MAXIMIZED_HORZ,
  49        _NET_WM_STATE_FULLSCREEN,
  50        _NET_WM_STATE_HIDDEN,
  51        _NET_WM_STATE_FOCUSED,
  52        _NET_ACTIVE_WINDOW,
  53        _NET_WM_MOVERESIZE,
  54        _NET_WM_WINDOW_TYPE,
  55        _NET_WM_WINDOW_TYPE_NOTIFICATION,
  56        _GTK_SHOW_WINDOW_MENU,
  57    }
  58}
  59
  60fn query_render_extent(xcb_connection: &XCBConnection, x_window: xproto::Window) -> gpu::Extent {
  61    let reply = xcb_connection
  62        .get_geometry(x_window)
  63        .unwrap()
  64        .reply()
  65        .unwrap();
  66    gpu::Extent {
  67        width: reply.width as u32,
  68        height: reply.height as u32,
  69        depth: 1,
  70    }
  71}
  72
  73#[derive(Debug)]
  74struct Visual {
  75    id: xproto::Visualid,
  76    colormap: u32,
  77    depth: u8,
  78}
  79
  80struct VisualSet {
  81    inherit: Visual,
  82    opaque: Option<Visual>,
  83    transparent: Option<Visual>,
  84    root: u32,
  85    black_pixel: u32,
  86}
  87
  88fn find_visuals(xcb_connection: &XCBConnection, screen_index: usize) -> VisualSet {
  89    let screen = &xcb_connection.setup().roots[screen_index];
  90    let mut set = VisualSet {
  91        inherit: Visual {
  92            id: screen.root_visual,
  93            colormap: screen.default_colormap,
  94            depth: screen.root_depth,
  95        },
  96        opaque: None,
  97        transparent: None,
  98        root: screen.root,
  99        black_pixel: screen.black_pixel,
 100    };
 101
 102    for depth_info in screen.allowed_depths.iter() {
 103        for visual_type in depth_info.visuals.iter() {
 104            let visual = Visual {
 105                id: visual_type.visual_id,
 106                colormap: 0,
 107                depth: depth_info.depth,
 108            };
 109            log::debug!("Visual id: {}, class: {:?}, depth: {}, bits_per_value: {}, masks: 0x{:x} 0x{:x} 0x{:x}",
 110                visual_type.visual_id,
 111                visual_type.class,
 112                depth_info.depth,
 113                visual_type.bits_per_rgb_value,
 114                visual_type.red_mask, visual_type.green_mask, visual_type.blue_mask,
 115            );
 116
 117            if (
 118                visual_type.red_mask,
 119                visual_type.green_mask,
 120                visual_type.blue_mask,
 121            ) != (0xFF0000, 0xFF00, 0xFF)
 122            {
 123                continue;
 124            }
 125            let color_mask = visual_type.red_mask | visual_type.green_mask | visual_type.blue_mask;
 126            let alpha_mask = color_mask as usize ^ ((1usize << depth_info.depth) - 1);
 127
 128            if alpha_mask == 0 {
 129                if set.opaque.is_none() {
 130                    set.opaque = Some(visual);
 131                }
 132            } else {
 133                if set.transparent.is_none() {
 134                    set.transparent = Some(visual);
 135                }
 136            }
 137        }
 138    }
 139
 140    set
 141}
 142
 143struct RawWindow {
 144    connection: *mut c_void,
 145    screen_id: usize,
 146    window_id: u32,
 147    visual_id: u32,
 148}
 149
 150#[derive(Default)]
 151pub struct Callbacks {
 152    request_frame: Option<Box<dyn FnMut()>>,
 153    input: Option<Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>>,
 154    active_status_change: Option<Box<dyn FnMut(bool)>>,
 155    resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
 156    moved: Option<Box<dyn FnMut()>>,
 157    should_close: Option<Box<dyn FnMut() -> bool>>,
 158    close: Option<Box<dyn FnOnce()>>,
 159    appearance_changed: Option<Box<dyn FnMut()>>,
 160}
 161
 162pub struct X11WindowState {
 163    pub destroyed: bool,
 164    refresh_rate: Duration,
 165    client: X11ClientStatePtr,
 166    executor: ForegroundExecutor,
 167    atoms: XcbAtoms,
 168    x_root_window: xproto::Window,
 169    _raw: RawWindow,
 170    bounds: Bounds<Pixels>,
 171    scale_factor: f32,
 172    renderer: BladeRenderer,
 173    display: Rc<dyn PlatformDisplay>,
 174    input_handler: Option<PlatformInputHandler>,
 175    appearance: WindowAppearance,
 176    pub handle: AnyWindowHandle,
 177}
 178
 179#[derive(Clone)]
 180pub(crate) struct X11WindowStatePtr {
 181    pub state: Rc<RefCell<X11WindowState>>,
 182    pub(crate) callbacks: Rc<RefCell<Callbacks>>,
 183    xcb_connection: Rc<XCBConnection>,
 184    pub x_window: xproto::Window,
 185}
 186
 187impl rwh::HasWindowHandle for RawWindow {
 188    fn window_handle(&self) -> Result<rwh::WindowHandle, rwh::HandleError> {
 189        let non_zero = NonZeroU32::new(self.window_id).unwrap();
 190        let mut handle = rwh::XcbWindowHandle::new(non_zero);
 191        handle.visual_id = NonZeroU32::new(self.visual_id);
 192        Ok(unsafe { rwh::WindowHandle::borrow_raw(handle.into()) })
 193    }
 194}
 195impl rwh::HasDisplayHandle for RawWindow {
 196    fn display_handle(&self) -> Result<rwh::DisplayHandle, rwh::HandleError> {
 197        let non_zero = NonNull::new(self.connection).unwrap();
 198        let handle = rwh::XcbDisplayHandle::new(Some(non_zero), self.screen_id as i32);
 199        Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) })
 200    }
 201}
 202
 203impl rwh::HasWindowHandle for X11Window {
 204    fn window_handle(&self) -> Result<rwh::WindowHandle, rwh::HandleError> {
 205        unimplemented!()
 206    }
 207}
 208impl rwh::HasDisplayHandle for X11Window {
 209    fn display_handle(&self) -> Result<rwh::DisplayHandle, rwh::HandleError> {
 210        unimplemented!()
 211    }
 212}
 213
 214impl X11WindowState {
 215    #[allow(clippy::too_many_arguments)]
 216    pub fn new(
 217        handle: AnyWindowHandle,
 218        client: X11ClientStatePtr,
 219        executor: ForegroundExecutor,
 220        params: WindowParams,
 221        xcb_connection: &Rc<XCBConnection>,
 222        x_main_screen_index: usize,
 223        x_window: xproto::Window,
 224        atoms: &XcbAtoms,
 225        scale_factor: f32,
 226        appearance: WindowAppearance,
 227    ) -> anyhow::Result<Self> {
 228        let x_screen_index = params
 229            .display_id
 230            .map_or(x_main_screen_index, |did| did.0 as usize);
 231
 232        let visual_set = find_visuals(&xcb_connection, x_screen_index);
 233        let visual_maybe = match params.window_background {
 234            WindowBackgroundAppearance::Opaque => visual_set.opaque,
 235            WindowBackgroundAppearance::Transparent | WindowBackgroundAppearance::Blurred => {
 236                visual_set.transparent
 237            }
 238        };
 239        let visual = match visual_maybe {
 240            Some(visual) => visual,
 241            None => {
 242                log::warn!(
 243                    "Unable to find a matching visual for {:?}",
 244                    params.window_background
 245                );
 246                visual_set.inherit
 247            }
 248        };
 249        log::info!("Using {:?}", visual);
 250
 251        let colormap = if visual.colormap != 0 {
 252            visual.colormap
 253        } else {
 254            let id = xcb_connection.generate_id().unwrap();
 255            log::info!("Creating colormap {}", id);
 256            xcb_connection
 257                .create_colormap(xproto::ColormapAlloc::NONE, id, visual_set.root, visual.id)
 258                .unwrap()
 259                .check()?;
 260            id
 261        };
 262
 263        let win_aux = xproto::CreateWindowAux::new()
 264            // https://stackoverflow.com/questions/43218127/x11-xlib-xcb-creating-a-window-requires-border-pixel-if-specifying-colormap-wh
 265            .border_pixel(visual_set.black_pixel)
 266            .colormap(colormap)
 267            .event_mask(
 268                xproto::EventMask::EXPOSURE
 269                    | xproto::EventMask::STRUCTURE_NOTIFY
 270                    | xproto::EventMask::FOCUS_CHANGE
 271                    | xproto::EventMask::KEY_PRESS
 272                    | xproto::EventMask::KEY_RELEASE,
 273            );
 274
 275        let mut bounds = params.bounds.to_device_pixels(scale_factor);
 276        if bounds.size.width.0 == 0 || bounds.size.height.0 == 0 {
 277            log::warn!("Window bounds contain a zero value. height={}, width={}. Falling back to defaults.", bounds.size.height.0, bounds.size.width.0);
 278            bounds.size.width = 800.into();
 279            bounds.size.height = 600.into();
 280        }
 281
 282        xcb_connection
 283            .create_window(
 284                visual.depth,
 285                x_window,
 286                visual_set.root,
 287                (bounds.origin.x.0 + 2) as i16,
 288                bounds.origin.y.0 as i16,
 289                bounds.size.width.0 as u16,
 290                bounds.size.height.0 as u16,
 291                0,
 292                xproto::WindowClass::INPUT_OUTPUT,
 293                visual.id,
 294                &win_aux,
 295            )
 296            .unwrap()
 297            .check().with_context(|| {
 298                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: {}",
 299                    visual.depth, x_window, visual_set.root, bounds.origin.x.0 + 2, bounds.origin.y.0, bounds.size.width.0, bounds.size.height.0)
 300            })?;
 301
 302        let reply = xcb_connection
 303            .get_geometry(x_window)
 304            .unwrap()
 305            .reply()
 306            .unwrap();
 307        if reply.x == 0 && reply.y == 0 {
 308            bounds.origin.x.0 += 2;
 309            // Work around a bug where our rendered content appears
 310            // outside the window bounds when opened at the default position
 311            // (14px, 49px on X + Gnome + Ubuntu 22).
 312            xcb_connection
 313                .configure_window(
 314                    x_window,
 315                    &xproto::ConfigureWindowAux::new()
 316                        .x(bounds.origin.x.0)
 317                        .y(bounds.origin.y.0),
 318                )
 319                .unwrap();
 320        }
 321        if let Some(titlebar) = params.titlebar {
 322            if let Some(title) = titlebar.title {
 323                xcb_connection
 324                    .change_property8(
 325                        xproto::PropMode::REPLACE,
 326                        x_window,
 327                        xproto::AtomEnum::WM_NAME,
 328                        xproto::AtomEnum::STRING,
 329                        title.as_bytes(),
 330                    )
 331                    .unwrap();
 332            }
 333        }
 334        if params.kind == WindowKind::PopUp {
 335            xcb_connection
 336                .change_property32(
 337                    xproto::PropMode::REPLACE,
 338                    x_window,
 339                    atoms._NET_WM_WINDOW_TYPE,
 340                    xproto::AtomEnum::ATOM,
 341                    &[atoms._NET_WM_WINDOW_TYPE_NOTIFICATION],
 342                )
 343                .unwrap();
 344        }
 345
 346        xcb_connection
 347            .change_property32(
 348                xproto::PropMode::REPLACE,
 349                x_window,
 350                atoms.WM_PROTOCOLS,
 351                xproto::AtomEnum::ATOM,
 352                &[atoms.WM_DELETE_WINDOW],
 353            )
 354            .unwrap();
 355
 356        xcb_connection
 357            .xinput_xi_select_events(
 358                x_window,
 359                &[xinput::EventMask {
 360                    deviceid: XINPUT_MASTER_DEVICE,
 361                    mask: vec![
 362                        xinput::XIEventMask::MOTION
 363                            | xinput::XIEventMask::BUTTON_PRESS
 364                            | xinput::XIEventMask::BUTTON_RELEASE
 365                            | xinput::XIEventMask::LEAVE,
 366                    ],
 367                }],
 368            )
 369            .unwrap();
 370
 371        xcb_connection.flush().unwrap();
 372
 373        let raw = RawWindow {
 374            connection: as_raw_xcb_connection::AsRawXcbConnection::as_raw_xcb_connection(
 375                xcb_connection,
 376            ) as *mut _,
 377            screen_id: x_screen_index,
 378            window_id: x_window,
 379            visual_id: visual.id,
 380        };
 381        let gpu = Arc::new(
 382            unsafe {
 383                gpu::Context::init_windowed(
 384                    &raw,
 385                    gpu::ContextDesc {
 386                        validation: false,
 387                        capture: false,
 388                        overlay: false,
 389                    },
 390                )
 391            }
 392            .map_err(|e| anyhow::anyhow!("{:?}", e))?,
 393        );
 394
 395        let config = BladeSurfaceConfig {
 396            // Note: this has to be done after the GPU init, or otherwise
 397            // the sizes are immediately invalidated.
 398            size: query_render_extent(xcb_connection, x_window),
 399            transparent: params.window_background != WindowBackgroundAppearance::Opaque,
 400        };
 401        xcb_connection.map_window(x_window).unwrap();
 402
 403        let screen_resources = xcb_connection
 404            .randr_get_screen_resources(x_window)
 405            .unwrap()
 406            .reply()
 407            .expect("Could not find available screens");
 408
 409        let mode = screen_resources
 410            .crtcs
 411            .iter()
 412            .find_map(|crtc| {
 413                let crtc_info = xcb_connection
 414                    .randr_get_crtc_info(*crtc, x11rb::CURRENT_TIME)
 415                    .ok()?
 416                    .reply()
 417                    .ok()?;
 418
 419                screen_resources
 420                    .modes
 421                    .iter()
 422                    .find(|m| m.id == crtc_info.mode)
 423            })
 424            .expect("Unable to find screen refresh rate");
 425
 426        let refresh_rate = mode_refresh_rate(&mode);
 427
 428        Ok(Self {
 429            client,
 430            executor,
 431            display: Rc::new(
 432                X11Display::new(xcb_connection, scale_factor, x_screen_index).unwrap(),
 433            ),
 434            _raw: raw,
 435            x_root_window: visual_set.root,
 436            bounds: bounds.to_pixels(scale_factor),
 437            scale_factor,
 438            renderer: BladeRenderer::new(gpu, config),
 439            atoms: *atoms,
 440            input_handler: None,
 441            appearance,
 442            handle,
 443            destroyed: false,
 444            refresh_rate,
 445        })
 446    }
 447
 448    fn content_size(&self) -> Size<Pixels> {
 449        let size = self.renderer.viewport_size();
 450        Size {
 451            width: size.width.into(),
 452            height: size.height.into(),
 453        }
 454    }
 455}
 456
 457pub(crate) struct X11Window(pub X11WindowStatePtr);
 458
 459impl Drop for X11Window {
 460    fn drop(&mut self) {
 461        let mut state = self.0.state.borrow_mut();
 462        state.renderer.destroy();
 463
 464        let destroy_x_window = maybe!({
 465            self.0.xcb_connection.unmap_window(self.0.x_window)?;
 466            self.0.xcb_connection.destroy_window(self.0.x_window)?;
 467            self.0.xcb_connection.flush()?;
 468
 469            anyhow::Ok(())
 470        })
 471        .context("unmapping and destroying X11 window")
 472        .log_err();
 473
 474        if destroy_x_window.is_some() {
 475            // Mark window as destroyed so that we can filter out when X11 events
 476            // for it still come in.
 477            state.destroyed = true;
 478
 479            let this_ptr = self.0.clone();
 480            let client_ptr = state.client.clone();
 481            state
 482                .executor
 483                .spawn(async move {
 484                    this_ptr.close();
 485                    client_ptr.drop_window(this_ptr.x_window);
 486                })
 487                .detach();
 488        }
 489
 490        drop(state);
 491    }
 492}
 493
 494enum WmHintPropertyState {
 495    // Remove = 0,
 496    // Add = 1,
 497    Toggle = 2,
 498}
 499
 500impl X11Window {
 501    #[allow(clippy::too_many_arguments)]
 502    pub fn new(
 503        handle: AnyWindowHandle,
 504        client: X11ClientStatePtr,
 505        executor: ForegroundExecutor,
 506        params: WindowParams,
 507        xcb_connection: &Rc<XCBConnection>,
 508        x_main_screen_index: usize,
 509        x_window: xproto::Window,
 510        atoms: &XcbAtoms,
 511        scale_factor: f32,
 512        appearance: WindowAppearance,
 513    ) -> anyhow::Result<Self> {
 514        Ok(Self(X11WindowStatePtr {
 515            state: Rc::new(RefCell::new(X11WindowState::new(
 516                handle,
 517                client,
 518                executor,
 519                params,
 520                xcb_connection,
 521                x_main_screen_index,
 522                x_window,
 523                atoms,
 524                scale_factor,
 525                appearance,
 526            )?)),
 527            callbacks: Rc::new(RefCell::new(Callbacks::default())),
 528            xcb_connection: xcb_connection.clone(),
 529            x_window,
 530        }))
 531    }
 532
 533    fn set_wm_hints(&self, wm_hint_property_state: WmHintPropertyState, prop1: u32, prop2: u32) {
 534        let state = self.0.state.borrow();
 535        let message = ClientMessageEvent::new(
 536            32,
 537            self.0.x_window,
 538            state.atoms._NET_WM_STATE,
 539            [wm_hint_property_state as u32, prop1, prop2, 1, 0],
 540        );
 541        self.0
 542            .xcb_connection
 543            .send_event(
 544                false,
 545                state.x_root_window,
 546                EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
 547                message,
 548            )
 549            .unwrap();
 550    }
 551
 552    fn get_wm_hints(&self) -> Vec<u32> {
 553        let reply = self
 554            .0
 555            .xcb_connection
 556            .get_property(
 557                false,
 558                self.0.x_window,
 559                self.0.state.borrow().atoms._NET_WM_STATE,
 560                xproto::AtomEnum::ATOM,
 561                0,
 562                u32::MAX,
 563            )
 564            .unwrap()
 565            .reply()
 566            .unwrap();
 567        // Reply is in u8 but atoms are represented as u32
 568        reply
 569            .value
 570            .chunks_exact(4)
 571            .map(|chunk| u32::from_ne_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]))
 572            .collect()
 573    }
 574
 575    fn get_root_position(&self, position: Point<Pixels>) -> TranslateCoordinatesReply {
 576        let state = self.0.state.borrow();
 577        self.0
 578            .xcb_connection
 579            .translate_coordinates(
 580                self.0.x_window,
 581                state.x_root_window,
 582                (position.x.0 * state.scale_factor) as i16,
 583                (position.y.0 * state.scale_factor) as i16,
 584            )
 585            .unwrap()
 586            .reply()
 587            .unwrap()
 588    }
 589}
 590
 591impl X11WindowStatePtr {
 592    pub fn should_close(&self) -> bool {
 593        let mut cb = self.callbacks.borrow_mut();
 594        if let Some(mut should_close) = cb.should_close.take() {
 595            let result = (should_close)();
 596            cb.should_close = Some(should_close);
 597            result
 598        } else {
 599            true
 600        }
 601    }
 602
 603    pub fn close(&self) {
 604        let mut callbacks = self.callbacks.borrow_mut();
 605        if let Some(fun) = callbacks.close.take() {
 606            fun()
 607        }
 608    }
 609
 610    pub fn refresh(&self) {
 611        let mut cb = self.callbacks.borrow_mut();
 612        if let Some(ref mut fun) = cb.request_frame {
 613            fun();
 614        }
 615    }
 616
 617    pub fn handle_input(&self, input: PlatformInput) {
 618        if let Some(ref mut fun) = self.callbacks.borrow_mut().input {
 619            if !fun(input.clone()).propagate {
 620                return;
 621            }
 622        }
 623        if let PlatformInput::KeyDown(event) = input {
 624            let mut state = self.state.borrow_mut();
 625            if let Some(mut input_handler) = state.input_handler.take() {
 626                if let Some(ime_key) = &event.keystroke.ime_key {
 627                    drop(state);
 628                    input_handler.replace_text_in_range(None, ime_key);
 629                    state = self.state.borrow_mut();
 630                }
 631                state.input_handler = Some(input_handler);
 632            }
 633        }
 634    }
 635
 636    pub fn handle_ime_commit(&self, text: String) {
 637        let mut state = self.state.borrow_mut();
 638        if let Some(mut input_handler) = state.input_handler.take() {
 639            drop(state);
 640            input_handler.replace_text_in_range(None, &text);
 641            let mut state = self.state.borrow_mut();
 642            state.input_handler = Some(input_handler);
 643        }
 644    }
 645
 646    pub fn handle_ime_preedit(&self, text: String) {
 647        let mut state = self.state.borrow_mut();
 648        if let Some(mut input_handler) = state.input_handler.take() {
 649            drop(state);
 650            input_handler.replace_and_mark_text_in_range(None, &text, None);
 651            let mut state = self.state.borrow_mut();
 652            state.input_handler = Some(input_handler);
 653        }
 654    }
 655
 656    pub fn handle_ime_unmark(&self) {
 657        let mut state = self.state.borrow_mut();
 658        if let Some(mut input_handler) = state.input_handler.take() {
 659            drop(state);
 660            input_handler.unmark_text();
 661            let mut state = self.state.borrow_mut();
 662            state.input_handler = Some(input_handler);
 663        }
 664    }
 665
 666    pub fn handle_ime_delete(&self) {
 667        let mut state = self.state.borrow_mut();
 668        if let Some(mut input_handler) = state.input_handler.take() {
 669            drop(state);
 670            if let Some(marked) = input_handler.marked_text_range() {
 671                input_handler.replace_text_in_range(Some(marked), "");
 672            }
 673            let mut state = self.state.borrow_mut();
 674            state.input_handler = Some(input_handler);
 675        }
 676    }
 677
 678    pub fn get_ime_area(&self) -> Option<Bounds<Pixels>> {
 679        let mut state = self.state.borrow_mut();
 680        let mut bounds: Option<Bounds<Pixels>> = None;
 681        if let Some(mut input_handler) = state.input_handler.take() {
 682            drop(state);
 683            if let Some(range) = input_handler.selected_text_range() {
 684                bounds = input_handler.bounds_for_range(range);
 685            }
 686            let mut state = self.state.borrow_mut();
 687            state.input_handler = Some(input_handler);
 688        };
 689        bounds
 690    }
 691
 692    pub fn configure(&self, bounds: Bounds<i32>) {
 693        let mut resize_args = None;
 694        let is_resize;
 695        {
 696            let mut state = self.state.borrow_mut();
 697            let bounds = bounds.map(|f| px(f as f32 / state.scale_factor));
 698
 699            is_resize = bounds.size.width != state.bounds.size.width
 700                || bounds.size.height != state.bounds.size.height;
 701
 702            // If it's a resize event (only width/height changed), we ignore `bounds.origin`
 703            // because it contains wrong values.
 704            if is_resize {
 705                state.bounds.size = bounds.size;
 706            } else {
 707                state.bounds = bounds;
 708            }
 709
 710            let gpu_size = query_render_extent(&self.xcb_connection, self.x_window);
 711            if true {
 712                state.renderer.update_drawable_size(size(
 713                    DevicePixels(gpu_size.width as i32),
 714                    DevicePixels(gpu_size.height as i32),
 715                ));
 716                resize_args = Some((state.content_size(), state.scale_factor));
 717            }
 718        }
 719
 720        let mut callbacks = self.callbacks.borrow_mut();
 721        if let Some((content_size, scale_factor)) = resize_args {
 722            if let Some(ref mut fun) = callbacks.resize {
 723                fun(content_size, scale_factor)
 724            }
 725        }
 726        if !is_resize {
 727            if let Some(ref mut fun) = callbacks.moved {
 728                fun()
 729            }
 730        }
 731    }
 732
 733    pub fn set_focused(&self, focus: bool) {
 734        if let Some(ref mut fun) = self.callbacks.borrow_mut().active_status_change {
 735            fun(focus);
 736        }
 737    }
 738
 739    pub fn set_appearance(&mut self, appearance: WindowAppearance) {
 740        self.state.borrow_mut().appearance = appearance;
 741
 742        let mut callbacks = self.callbacks.borrow_mut();
 743        if let Some(ref mut fun) = callbacks.appearance_changed {
 744            (fun)()
 745        }
 746    }
 747
 748    pub fn refresh_rate(&self) -> Duration {
 749        self.state.borrow().refresh_rate
 750    }
 751}
 752
 753impl PlatformWindow for X11Window {
 754    fn bounds(&self) -> Bounds<Pixels> {
 755        self.0.state.borrow().bounds
 756    }
 757
 758    fn is_maximized(&self) -> bool {
 759        let state = self.0.state.borrow();
 760        let wm_hints = self.get_wm_hints();
 761        // A maximized window that gets minimized will still retain its maximized state.
 762        !wm_hints.contains(&state.atoms._NET_WM_STATE_HIDDEN)
 763            && wm_hints.contains(&state.atoms._NET_WM_STATE_MAXIMIZED_VERT)
 764            && wm_hints.contains(&state.atoms._NET_WM_STATE_MAXIMIZED_HORZ)
 765    }
 766
 767    fn window_bounds(&self) -> WindowBounds {
 768        let state = self.0.state.borrow();
 769        if self.is_maximized() {
 770            WindowBounds::Maximized(state.bounds)
 771        } else {
 772            WindowBounds::Windowed(state.bounds)
 773        }
 774    }
 775
 776    fn content_size(&self) -> Size<Pixels> {
 777        // We divide by the scale factor here because this value is queried to determine how much to draw,
 778        // but it will be multiplied later by the scale to adjust for scaling.
 779        let state = self.0.state.borrow();
 780        state
 781            .content_size()
 782            .map(|size| size.div(state.scale_factor))
 783    }
 784
 785    fn scale_factor(&self) -> f32 {
 786        self.0.state.borrow().scale_factor
 787    }
 788
 789    fn appearance(&self) -> WindowAppearance {
 790        self.0.state.borrow().appearance
 791    }
 792
 793    fn display(&self) -> Option<Rc<dyn PlatformDisplay>> {
 794        Some(self.0.state.borrow().display.clone())
 795    }
 796
 797    fn mouse_position(&self) -> Point<Pixels> {
 798        let reply = self
 799            .0
 800            .xcb_connection
 801            .query_pointer(self.0.x_window)
 802            .unwrap()
 803            .reply()
 804            .unwrap();
 805        Point::new((reply.root_x as u32).into(), (reply.root_y as u32).into())
 806    }
 807
 808    fn modifiers(&self) -> Modifiers {
 809        self.0
 810            .state
 811            .borrow()
 812            .client
 813            .0
 814            .upgrade()
 815            .map(|ref_cell| ref_cell.borrow().modifiers)
 816            .unwrap_or_default()
 817    }
 818
 819    fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
 820        self.0.state.borrow_mut().input_handler = Some(input_handler);
 821    }
 822
 823    fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
 824        self.0.state.borrow_mut().input_handler.take()
 825    }
 826
 827    fn prompt(
 828        &self,
 829        _level: PromptLevel,
 830        _msg: &str,
 831        _detail: Option<&str>,
 832        _answers: &[&str],
 833    ) -> Option<futures::channel::oneshot::Receiver<usize>> {
 834        None
 835    }
 836
 837    fn activate(&self) {
 838        let data = [1, xproto::Time::CURRENT_TIME.into(), 0, 0, 0];
 839        let message = xproto::ClientMessageEvent::new(
 840            32,
 841            self.0.x_window,
 842            self.0.state.borrow().atoms._NET_ACTIVE_WINDOW,
 843            data,
 844        );
 845        self.0
 846            .xcb_connection
 847            .send_event(
 848                false,
 849                self.0.state.borrow().x_root_window,
 850                xproto::EventMask::SUBSTRUCTURE_REDIRECT | xproto::EventMask::SUBSTRUCTURE_NOTIFY,
 851                message,
 852            )
 853            .log_err();
 854        self.0
 855            .xcb_connection
 856            .set_input_focus(
 857                xproto::InputFocus::POINTER_ROOT,
 858                self.0.x_window,
 859                xproto::Time::CURRENT_TIME,
 860            )
 861            .log_err();
 862    }
 863
 864    fn is_active(&self) -> bool {
 865        let state = self.0.state.borrow();
 866        self.get_wm_hints()
 867            .contains(&state.atoms._NET_WM_STATE_FOCUSED)
 868    }
 869
 870    fn set_title(&mut self, title: &str) {
 871        self.0
 872            .xcb_connection
 873            .change_property8(
 874                xproto::PropMode::REPLACE,
 875                self.0.x_window,
 876                xproto::AtomEnum::WM_NAME,
 877                xproto::AtomEnum::STRING,
 878                title.as_bytes(),
 879            )
 880            .unwrap();
 881
 882        self.0
 883            .xcb_connection
 884            .change_property8(
 885                xproto::PropMode::REPLACE,
 886                self.0.x_window,
 887                self.0.state.borrow().atoms._NET_WM_NAME,
 888                self.0.state.borrow().atoms.UTF8_STRING,
 889                title.as_bytes(),
 890            )
 891            .unwrap();
 892    }
 893
 894    fn set_app_id(&mut self, app_id: &str) {
 895        let mut data = Vec::with_capacity(app_id.len() * 2 + 1);
 896        data.extend(app_id.bytes()); // instance https://unix.stackexchange.com/a/494170
 897        data.push(b'\0');
 898        data.extend(app_id.bytes()); // class
 899
 900        self.0
 901            .xcb_connection
 902            .change_property8(
 903                xproto::PropMode::REPLACE,
 904                self.0.x_window,
 905                xproto::AtomEnum::WM_CLASS,
 906                xproto::AtomEnum::STRING,
 907                &data,
 908            )
 909            .unwrap();
 910    }
 911
 912    fn set_edited(&mut self, _edited: bool) {
 913        log::info!("ignoring macOS specific set_edited");
 914    }
 915
 916    fn set_background_appearance(&mut self, background_appearance: WindowBackgroundAppearance) {
 917        let mut inner = self.0.state.borrow_mut();
 918        let transparent = background_appearance != WindowBackgroundAppearance::Opaque;
 919        inner.renderer.update_transparency(transparent);
 920    }
 921
 922    fn show_character_palette(&self) {
 923        log::info!("ignoring macOS specific show_character_palette");
 924    }
 925
 926    fn minimize(&self) {
 927        let state = self.0.state.borrow();
 928        const WINDOW_ICONIC_STATE: u32 = 3;
 929        let message = ClientMessageEvent::new(
 930            32,
 931            self.0.x_window,
 932            state.atoms.WM_CHANGE_STATE,
 933            [WINDOW_ICONIC_STATE, 0, 0, 0, 0],
 934        );
 935        self.0
 936            .xcb_connection
 937            .send_event(
 938                false,
 939                state.x_root_window,
 940                EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
 941                message,
 942            )
 943            .unwrap();
 944    }
 945
 946    fn zoom(&self) {
 947        let state = self.0.state.borrow();
 948        self.set_wm_hints(
 949            WmHintPropertyState::Toggle,
 950            state.atoms._NET_WM_STATE_MAXIMIZED_VERT,
 951            state.atoms._NET_WM_STATE_MAXIMIZED_HORZ,
 952        );
 953    }
 954
 955    fn toggle_fullscreen(&self) {
 956        let state = self.0.state.borrow();
 957        self.set_wm_hints(
 958            WmHintPropertyState::Toggle,
 959            state.atoms._NET_WM_STATE_FULLSCREEN,
 960            xproto::AtomEnum::NONE.into(),
 961        );
 962    }
 963
 964    fn is_fullscreen(&self) -> bool {
 965        let state = self.0.state.borrow();
 966        self.get_wm_hints()
 967            .contains(&state.atoms._NET_WM_STATE_FULLSCREEN)
 968    }
 969
 970    fn on_request_frame(&self, callback: Box<dyn FnMut()>) {
 971        self.0.callbacks.borrow_mut().request_frame = Some(callback);
 972    }
 973
 974    fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>) {
 975        self.0.callbacks.borrow_mut().input = Some(callback);
 976    }
 977
 978    fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
 979        self.0.callbacks.borrow_mut().active_status_change = Some(callback);
 980    }
 981
 982    fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
 983        self.0.callbacks.borrow_mut().resize = Some(callback);
 984    }
 985
 986    fn on_moved(&self, callback: Box<dyn FnMut()>) {
 987        self.0.callbacks.borrow_mut().moved = Some(callback);
 988    }
 989
 990    fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
 991        self.0.callbacks.borrow_mut().should_close = Some(callback);
 992    }
 993
 994    fn on_close(&self, callback: Box<dyn FnOnce()>) {
 995        self.0.callbacks.borrow_mut().close = Some(callback);
 996    }
 997
 998    fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
 999        self.0.callbacks.borrow_mut().appearance_changed = Some(callback);
1000    }
1001
1002    fn draw(&self, scene: &Scene) {
1003        let mut inner = self.0.state.borrow_mut();
1004        inner.renderer.draw(scene);
1005    }
1006
1007    fn sprite_atlas(&self) -> sync::Arc<dyn PlatformAtlas> {
1008        let inner = self.0.state.borrow();
1009        inner.renderer.sprite_atlas().clone()
1010    }
1011
1012    fn show_window_menu(&self, position: Point<Pixels>) {
1013        let state = self.0.state.borrow();
1014        let coords = self.get_root_position(position);
1015        let message = ClientMessageEvent::new(
1016            32,
1017            self.0.x_window,
1018            state.atoms._GTK_SHOW_WINDOW_MENU,
1019            [
1020                XINPUT_MASTER_DEVICE as u32,
1021                coords.dst_x as u32,
1022                coords.dst_y as u32,
1023                0,
1024                0,
1025            ],
1026        );
1027        self.0
1028            .xcb_connection
1029            .send_event(
1030                false,
1031                state.x_root_window,
1032                EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
1033                message,
1034            )
1035            .unwrap();
1036    }
1037
1038    fn start_system_move(&self) {
1039        let state = self.0.state.borrow();
1040        let pointer = self
1041            .0
1042            .xcb_connection
1043            .query_pointer(self.0.x_window)
1044            .unwrap()
1045            .reply()
1046            .unwrap();
1047        const MOVERESIZE_MOVE: u32 = 8;
1048        let message = ClientMessageEvent::new(
1049            32,
1050            self.0.x_window,
1051            state.atoms._NET_WM_MOVERESIZE,
1052            [
1053                pointer.root_x as u32,
1054                pointer.root_y as u32,
1055                MOVERESIZE_MOVE,
1056                1, // Left mouse button
1057                1,
1058            ],
1059        );
1060        self.0
1061            .xcb_connection
1062            .send_event(
1063                false,
1064                state.x_root_window,
1065                EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
1066                message,
1067            )
1068            .unwrap();
1069    }
1070
1071    fn should_render_window_controls(&self) -> bool {
1072        false
1073    }
1074}
1075
1076// Adapted from:
1077// https://docs.rs/winit/0.29.11/src/winit/platform_impl/linux/x11/monitor.rs.html#103-111
1078pub fn mode_refresh_rate(mode: &randr::ModeInfo) -> Duration {
1079    if mode.dot_clock == 0 || mode.htotal == 0 || mode.vtotal == 0 {
1080        return Duration::from_millis(16);
1081    }
1082
1083    let millihertz = mode.dot_clock as u64 * 1_000 / (mode.htotal as u64 * mode.vtotal as u64);
1084    let micros = 1_000_000_000 / millihertz;
1085    log::info!("Refreshing at {} micros", micros);
1086    Duration::from_micros(micros)
1087}