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