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