window.rs

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