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