window.rs

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