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