window.rs

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