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};
  37
  38x11rb::atom_manager! {
  39    pub XcbAtoms: AtomsCookie {
  40        UTF8_STRING,
  41        WM_PROTOCOLS,
  42        WM_DELETE_WINDOW,
  43        WM_CHANGE_STATE,
  44        _NET_WM_NAME,
  45        _NET_WM_STATE,
  46        _NET_WM_STATE_MAXIMIZED_VERT,
  47        _NET_WM_STATE_MAXIMIZED_HORZ,
  48        _NET_WM_STATE_FULLSCREEN,
  49        _NET_WM_STATE_HIDDEN,
  50        _NET_WM_STATE_FOCUSED,
  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
 274        xcb_connection
 275            .create_window(
 276                visual.depth,
 277                x_window,
 278                visual_set.root,
 279                (bounds.origin.x.0 + 2) as i16,
 280                bounds.origin.y.0 as i16,
 281                bounds.size.width.0 as u16,
 282                bounds.size.height.0 as u16,
 283                0,
 284                xproto::WindowClass::INPUT_OUTPUT,
 285                visual.id,
 286                &win_aux,
 287            )
 288            .unwrap()
 289            .check()?;
 290
 291        let reply = xcb_connection
 292            .get_geometry(x_window)
 293            .unwrap()
 294            .reply()
 295            .unwrap();
 296        if reply.x == 0 && reply.y == 0 {
 297            bounds.origin.x.0 += 2;
 298            // Work around a bug where our rendered content appears
 299            // outside the window bounds when opened at the default position
 300            // (14px, 49px on X + Gnome + Ubuntu 22).
 301            xcb_connection
 302                .configure_window(
 303                    x_window,
 304                    &xproto::ConfigureWindowAux::new()
 305                        .x(bounds.origin.x.0)
 306                        .y(bounds.origin.y.0),
 307                )
 308                .unwrap();
 309        }
 310        if let Some(titlebar) = params.titlebar {
 311            if let Some(title) = titlebar.title {
 312                xcb_connection
 313                    .change_property8(
 314                        xproto::PropMode::REPLACE,
 315                        x_window,
 316                        xproto::AtomEnum::WM_NAME,
 317                        xproto::AtomEnum::STRING,
 318                        title.as_bytes(),
 319                    )
 320                    .unwrap();
 321            }
 322        }
 323        if params.kind == WindowKind::PopUp {
 324            xcb_connection
 325                .change_property32(
 326                    xproto::PropMode::REPLACE,
 327                    x_window,
 328                    atoms._NET_WM_WINDOW_TYPE,
 329                    xproto::AtomEnum::ATOM,
 330                    &[atoms._NET_WM_WINDOW_TYPE_NOTIFICATION],
 331                )
 332                .unwrap();
 333        }
 334
 335        xcb_connection
 336            .change_property32(
 337                xproto::PropMode::REPLACE,
 338                x_window,
 339                atoms.WM_PROTOCOLS,
 340                xproto::AtomEnum::ATOM,
 341                &[atoms.WM_DELETE_WINDOW],
 342            )
 343            .unwrap();
 344
 345        xcb_connection
 346            .xinput_xi_select_events(
 347                x_window,
 348                &[xinput::EventMask {
 349                    deviceid: XINPUT_MASTER_DEVICE,
 350                    mask: vec![
 351                        xinput::XIEventMask::MOTION
 352                            | xinput::XIEventMask::BUTTON_PRESS
 353                            | xinput::XIEventMask::BUTTON_RELEASE
 354                            | xinput::XIEventMask::LEAVE,
 355                    ],
 356                }],
 357            )
 358            .unwrap();
 359
 360        xcb_connection.flush().unwrap();
 361
 362        let raw = RawWindow {
 363            connection: as_raw_xcb_connection::AsRawXcbConnection::as_raw_xcb_connection(
 364                xcb_connection,
 365            ) as *mut _,
 366            screen_id: x_screen_index,
 367            window_id: x_window,
 368            visual_id: visual.id,
 369        };
 370        let gpu = Arc::new(
 371            unsafe {
 372                gpu::Context::init_windowed(
 373                    &raw,
 374                    gpu::ContextDesc {
 375                        validation: false,
 376                        capture: false,
 377                        overlay: false,
 378                    },
 379                )
 380            }
 381            .map_err(|e| anyhow::anyhow!("{:?}", e))?,
 382        );
 383
 384        let config = BladeSurfaceConfig {
 385            // Note: this has to be done after the GPU init, or otherwise
 386            // the sizes are immediately invalidated.
 387            size: query_render_extent(xcb_connection, x_window),
 388            transparent: params.window_background != WindowBackgroundAppearance::Opaque,
 389        };
 390        xcb_connection.map_window(x_window).unwrap();
 391
 392        Ok(Self {
 393            client,
 394            executor,
 395            display: Rc::new(
 396                X11Display::new(xcb_connection, scale_factor, x_screen_index).unwrap(),
 397            ),
 398            _raw: raw,
 399            x_root_window: visual_set.root,
 400            bounds: bounds.to_pixels(scale_factor),
 401            scale_factor,
 402            renderer: BladeRenderer::new(gpu, config),
 403            atoms: *atoms,
 404            input_handler: None,
 405            appearance,
 406            handle,
 407            destroyed: false,
 408        })
 409    }
 410
 411    fn content_size(&self) -> Size<Pixels> {
 412        let size = self.renderer.viewport_size();
 413        Size {
 414            width: size.width.into(),
 415            height: size.height.into(),
 416        }
 417    }
 418}
 419
 420pub(crate) struct X11Window(pub X11WindowStatePtr);
 421
 422impl Drop for X11Window {
 423    fn drop(&mut self) {
 424        let mut state = self.0.state.borrow_mut();
 425        state.renderer.destroy();
 426
 427        let destroy_x_window = maybe!({
 428            self.0.xcb_connection.unmap_window(self.0.x_window)?;
 429            self.0.xcb_connection.destroy_window(self.0.x_window)?;
 430            self.0.xcb_connection.flush()?;
 431
 432            anyhow::Ok(())
 433        })
 434        .context("unmapping and destroying X11 window")
 435        .log_err();
 436
 437        if destroy_x_window.is_some() {
 438            // Mark window as destroyed so that we can filter out when X11 events
 439            // for it still come in.
 440            state.destroyed = true;
 441
 442            let this_ptr = self.0.clone();
 443            let client_ptr = state.client.clone();
 444            state
 445                .executor
 446                .spawn(async move {
 447                    this_ptr.close();
 448                    client_ptr.drop_window(this_ptr.x_window);
 449                })
 450                .detach();
 451        }
 452
 453        drop(state);
 454    }
 455}
 456
 457enum WmHintPropertyState {
 458    // Remove = 0,
 459    // Add = 1,
 460    Toggle = 2,
 461}
 462
 463impl X11Window {
 464    #[allow(clippy::too_many_arguments)]
 465    pub fn new(
 466        handle: AnyWindowHandle,
 467        client: X11ClientStatePtr,
 468        executor: ForegroundExecutor,
 469        params: WindowParams,
 470        xcb_connection: &Rc<XCBConnection>,
 471        x_main_screen_index: usize,
 472        x_window: xproto::Window,
 473        atoms: &XcbAtoms,
 474        scale_factor: f32,
 475        appearance: WindowAppearance,
 476    ) -> anyhow::Result<Self> {
 477        Ok(Self(X11WindowStatePtr {
 478            state: Rc::new(RefCell::new(X11WindowState::new(
 479                handle,
 480                client,
 481                executor,
 482                params,
 483                xcb_connection,
 484                x_main_screen_index,
 485                x_window,
 486                atoms,
 487                scale_factor,
 488                appearance,
 489            )?)),
 490            callbacks: Rc::new(RefCell::new(Callbacks::default())),
 491            xcb_connection: xcb_connection.clone(),
 492            x_window,
 493        }))
 494    }
 495
 496    fn set_wm_hints(&self, wm_hint_property_state: WmHintPropertyState, prop1: u32, prop2: u32) {
 497        let state = self.0.state.borrow();
 498        let message = ClientMessageEvent::new(
 499            32,
 500            self.0.x_window,
 501            state.atoms._NET_WM_STATE,
 502            [wm_hint_property_state as u32, prop1, prop2, 1, 0],
 503        );
 504        self.0
 505            .xcb_connection
 506            .send_event(
 507                false,
 508                state.x_root_window,
 509                EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
 510                message,
 511            )
 512            .unwrap();
 513    }
 514
 515    fn get_wm_hints(&self) -> Vec<u32> {
 516        let reply = self
 517            .0
 518            .xcb_connection
 519            .get_property(
 520                false,
 521                self.0.x_window,
 522                self.0.state.borrow().atoms._NET_WM_STATE,
 523                xproto::AtomEnum::ATOM,
 524                0,
 525                u32::MAX,
 526            )
 527            .unwrap()
 528            .reply()
 529            .unwrap();
 530        // Reply is in u8 but atoms are represented as u32
 531        reply
 532            .value
 533            .chunks_exact(4)
 534            .map(|chunk| u32::from_ne_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]))
 535            .collect()
 536    }
 537
 538    fn get_root_position(&self, position: Point<Pixels>) -> TranslateCoordinatesReply {
 539        let state = self.0.state.borrow();
 540        self.0
 541            .xcb_connection
 542            .translate_coordinates(
 543                self.0.x_window,
 544                state.x_root_window,
 545                (position.x.0 * state.scale_factor) as i16,
 546                (position.y.0 * state.scale_factor) as i16,
 547            )
 548            .unwrap()
 549            .reply()
 550            .unwrap()
 551    }
 552}
 553
 554impl X11WindowStatePtr {
 555    pub fn should_close(&self) -> bool {
 556        let mut cb = self.callbacks.borrow_mut();
 557        if let Some(mut should_close) = cb.should_close.take() {
 558            let result = (should_close)();
 559            cb.should_close = Some(should_close);
 560            result
 561        } else {
 562            true
 563        }
 564    }
 565
 566    pub fn close(&self) {
 567        let mut callbacks = self.callbacks.borrow_mut();
 568        if let Some(fun) = callbacks.close.take() {
 569            fun()
 570        }
 571    }
 572
 573    pub fn refresh(&self) {
 574        let mut cb = self.callbacks.borrow_mut();
 575        if let Some(ref mut fun) = cb.request_frame {
 576            fun();
 577        }
 578    }
 579
 580    pub fn handle_input(&self, input: PlatformInput) {
 581        if let Some(ref mut fun) = self.callbacks.borrow_mut().input {
 582            if !fun(input.clone()).propagate {
 583                return;
 584            }
 585        }
 586        if let PlatformInput::KeyDown(event) = input {
 587            let mut state = self.state.borrow_mut();
 588            if let Some(mut input_handler) = state.input_handler.take() {
 589                if let Some(ime_key) = &event.keystroke.ime_key {
 590                    drop(state);
 591                    input_handler.replace_text_in_range(None, ime_key);
 592                    state = self.state.borrow_mut();
 593                }
 594                state.input_handler = Some(input_handler);
 595            }
 596        }
 597    }
 598
 599    pub fn handle_ime_commit(&self, text: String) {
 600        let mut state = self.state.borrow_mut();
 601        if let Some(mut input_handler) = state.input_handler.take() {
 602            drop(state);
 603            input_handler.replace_text_in_range(None, &text);
 604            let mut state = self.state.borrow_mut();
 605            state.input_handler = Some(input_handler);
 606        }
 607    }
 608
 609    pub fn handle_ime_preedit(&self, text: String) {
 610        let mut state = self.state.borrow_mut();
 611        if let Some(mut input_handler) = state.input_handler.take() {
 612            drop(state);
 613            input_handler.replace_and_mark_text_in_range(None, &text, None);
 614            let mut state = self.state.borrow_mut();
 615            state.input_handler = Some(input_handler);
 616        }
 617    }
 618
 619    pub fn handle_ime_unmark(&self) {
 620        let mut state = self.state.borrow_mut();
 621        if let Some(mut input_handler) = state.input_handler.take() {
 622            drop(state);
 623            input_handler.unmark_text();
 624            let mut state = self.state.borrow_mut();
 625            state.input_handler = Some(input_handler);
 626        }
 627    }
 628
 629    pub fn handle_ime_delete(&self) {
 630        let mut state = self.state.borrow_mut();
 631        if let Some(mut input_handler) = state.input_handler.take() {
 632            drop(state);
 633            if let Some(marked) = input_handler.marked_text_range() {
 634                input_handler.replace_text_in_range(Some(marked), "");
 635            }
 636            let mut state = self.state.borrow_mut();
 637            state.input_handler = Some(input_handler);
 638        }
 639    }
 640
 641    pub fn get_ime_area(&self) -> Option<Bounds<Pixels>> {
 642        let mut state = self.state.borrow_mut();
 643        let mut bounds: Option<Bounds<Pixels>> = None;
 644        if let Some(mut input_handler) = state.input_handler.take() {
 645            drop(state);
 646            if let Some(range) = input_handler.selected_text_range() {
 647                bounds = input_handler.bounds_for_range(range);
 648            }
 649            let mut state = self.state.borrow_mut();
 650            state.input_handler = Some(input_handler);
 651        };
 652        bounds
 653    }
 654
 655    pub fn configure(&self, bounds: Bounds<i32>) {
 656        let mut resize_args = None;
 657        let is_resize;
 658        {
 659            let mut state = self.state.borrow_mut();
 660            let bounds = bounds.map(|f| px(f as f32 / state.scale_factor));
 661
 662            is_resize = bounds.size.width != state.bounds.size.width
 663                || bounds.size.height != state.bounds.size.height;
 664
 665            // If it's a resize event (only width/height changed), we ignore `bounds.origin`
 666            // because it contains wrong values.
 667            if is_resize {
 668                state.bounds.size = bounds.size;
 669            } else {
 670                state.bounds = bounds;
 671            }
 672
 673            let gpu_size = query_render_extent(&self.xcb_connection, self.x_window);
 674            if true {
 675                state.renderer.update_drawable_size(size(
 676                    DevicePixels(gpu_size.width as i32),
 677                    DevicePixels(gpu_size.height as i32),
 678                ));
 679                resize_args = Some((state.content_size(), state.scale_factor));
 680            }
 681        }
 682
 683        let mut callbacks = self.callbacks.borrow_mut();
 684        if let Some((content_size, scale_factor)) = resize_args {
 685            if let Some(ref mut fun) = callbacks.resize {
 686                fun(content_size, scale_factor)
 687            }
 688        }
 689        if !is_resize {
 690            if let Some(ref mut fun) = callbacks.moved {
 691                fun()
 692            }
 693        }
 694    }
 695
 696    pub fn set_focused(&self, focus: bool) {
 697        if let Some(ref mut fun) = self.callbacks.borrow_mut().active_status_change {
 698            fun(focus);
 699        }
 700    }
 701
 702    pub fn set_appearance(&mut self, appearance: WindowAppearance) {
 703        self.state.borrow_mut().appearance = appearance;
 704
 705        let mut callbacks = self.callbacks.borrow_mut();
 706        if let Some(ref mut fun) = callbacks.appearance_changed {
 707            (fun)()
 708        }
 709    }
 710}
 711
 712impl PlatformWindow for X11Window {
 713    fn bounds(&self) -> Bounds<Pixels> {
 714        self.0.state.borrow().bounds
 715    }
 716
 717    fn is_maximized(&self) -> bool {
 718        let state = self.0.state.borrow();
 719        let wm_hints = self.get_wm_hints();
 720        // A maximized window that gets minimized will still retain its maximized state.
 721        !wm_hints.contains(&state.atoms._NET_WM_STATE_HIDDEN)
 722            && wm_hints.contains(&state.atoms._NET_WM_STATE_MAXIMIZED_VERT)
 723            && wm_hints.contains(&state.atoms._NET_WM_STATE_MAXIMIZED_HORZ)
 724    }
 725
 726    fn window_bounds(&self) -> WindowBounds {
 727        let state = self.0.state.borrow();
 728        if self.is_maximized() {
 729            WindowBounds::Maximized(state.bounds)
 730        } else {
 731            WindowBounds::Windowed(state.bounds)
 732        }
 733    }
 734
 735    fn content_size(&self) -> Size<Pixels> {
 736        // We divide by the scale factor here because this value is queried to determine how much to draw,
 737        // but it will be multiplied later by the scale to adjust for scaling.
 738        let state = self.0.state.borrow();
 739        state
 740            .content_size()
 741            .map(|size| size.div(state.scale_factor))
 742    }
 743
 744    fn scale_factor(&self) -> f32 {
 745        self.0.state.borrow().scale_factor
 746    }
 747
 748    fn appearance(&self) -> WindowAppearance {
 749        self.0.state.borrow().appearance
 750    }
 751
 752    fn display(&self) -> Option<Rc<dyn PlatformDisplay>> {
 753        Some(self.0.state.borrow().display.clone())
 754    }
 755
 756    fn mouse_position(&self) -> Point<Pixels> {
 757        let reply = self
 758            .0
 759            .xcb_connection
 760            .query_pointer(self.0.x_window)
 761            .unwrap()
 762            .reply()
 763            .unwrap();
 764        Point::new((reply.root_x as u32).into(), (reply.root_y as u32).into())
 765    }
 766
 767    fn modifiers(&self) -> Modifiers {
 768        self.0
 769            .state
 770            .borrow()
 771            .client
 772            .0
 773            .upgrade()
 774            .map(|ref_cell| ref_cell.borrow().modifiers)
 775            .unwrap_or_default()
 776    }
 777
 778    fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
 779        self.0.state.borrow_mut().input_handler = Some(input_handler);
 780    }
 781
 782    fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
 783        self.0.state.borrow_mut().input_handler.take()
 784    }
 785
 786    fn prompt(
 787        &self,
 788        _level: PromptLevel,
 789        _msg: &str,
 790        _detail: Option<&str>,
 791        _answers: &[&str],
 792    ) -> Option<futures::channel::oneshot::Receiver<usize>> {
 793        None
 794    }
 795
 796    fn activate(&self) {
 797        let win_aux = xproto::ConfigureWindowAux::new().stack_mode(xproto::StackMode::ABOVE);
 798        self.0
 799            .xcb_connection
 800            .configure_window(self.0.x_window, &win_aux)
 801            .log_err();
 802        self.0
 803            .xcb_connection
 804            .set_input_focus(
 805                xproto::InputFocus::POINTER_ROOT,
 806                self.0.x_window,
 807                xproto::Time::CURRENT_TIME,
 808            )
 809            .log_err();
 810    }
 811
 812    fn is_active(&self) -> bool {
 813        let state = self.0.state.borrow();
 814        self.get_wm_hints()
 815            .contains(&state.atoms._NET_WM_STATE_FOCUSED)
 816    }
 817
 818    fn set_title(&mut self, title: &str) {
 819        self.0
 820            .xcb_connection
 821            .change_property8(
 822                xproto::PropMode::REPLACE,
 823                self.0.x_window,
 824                xproto::AtomEnum::WM_NAME,
 825                xproto::AtomEnum::STRING,
 826                title.as_bytes(),
 827            )
 828            .unwrap();
 829
 830        self.0
 831            .xcb_connection
 832            .change_property8(
 833                xproto::PropMode::REPLACE,
 834                self.0.x_window,
 835                self.0.state.borrow().atoms._NET_WM_NAME,
 836                self.0.state.borrow().atoms.UTF8_STRING,
 837                title.as_bytes(),
 838            )
 839            .unwrap();
 840    }
 841
 842    fn set_app_id(&mut self, app_id: &str) {
 843        let mut data = Vec::with_capacity(app_id.len() * 2 + 1);
 844        data.extend(app_id.bytes()); // instance https://unix.stackexchange.com/a/494170
 845        data.push(b'\0');
 846        data.extend(app_id.bytes()); // class
 847
 848        self.0
 849            .xcb_connection
 850            .change_property8(
 851                xproto::PropMode::REPLACE,
 852                self.0.x_window,
 853                xproto::AtomEnum::WM_CLASS,
 854                xproto::AtomEnum::STRING,
 855                &data,
 856            )
 857            .unwrap();
 858    }
 859
 860    fn set_edited(&mut self, _edited: bool) {
 861        log::info!("ignoring macOS specific set_edited");
 862    }
 863
 864    fn set_background_appearance(&mut self, background_appearance: WindowBackgroundAppearance) {
 865        let mut inner = self.0.state.borrow_mut();
 866        let transparent = background_appearance != WindowBackgroundAppearance::Opaque;
 867        inner.renderer.update_transparency(transparent);
 868    }
 869
 870    fn show_character_palette(&self) {
 871        log::info!("ignoring macOS specific show_character_palette");
 872    }
 873
 874    fn minimize(&self) {
 875        let state = self.0.state.borrow();
 876        const WINDOW_ICONIC_STATE: u32 = 3;
 877        let message = ClientMessageEvent::new(
 878            32,
 879            self.0.x_window,
 880            state.atoms.WM_CHANGE_STATE,
 881            [WINDOW_ICONIC_STATE, 0, 0, 0, 0],
 882        );
 883        self.0
 884            .xcb_connection
 885            .send_event(
 886                false,
 887                state.x_root_window,
 888                EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
 889                message,
 890            )
 891            .unwrap();
 892    }
 893
 894    fn zoom(&self) {
 895        let state = self.0.state.borrow();
 896        self.set_wm_hints(
 897            WmHintPropertyState::Toggle,
 898            state.atoms._NET_WM_STATE_MAXIMIZED_VERT,
 899            state.atoms._NET_WM_STATE_MAXIMIZED_HORZ,
 900        );
 901    }
 902
 903    fn toggle_fullscreen(&self) {
 904        let state = self.0.state.borrow();
 905        self.set_wm_hints(
 906            WmHintPropertyState::Toggle,
 907            state.atoms._NET_WM_STATE_FULLSCREEN,
 908            xproto::AtomEnum::NONE.into(),
 909        );
 910    }
 911
 912    fn is_fullscreen(&self) -> bool {
 913        let state = self.0.state.borrow();
 914        self.get_wm_hints()
 915            .contains(&state.atoms._NET_WM_STATE_FULLSCREEN)
 916    }
 917
 918    fn on_request_frame(&self, callback: Box<dyn FnMut()>) {
 919        self.0.callbacks.borrow_mut().request_frame = Some(callback);
 920    }
 921
 922    fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>) {
 923        self.0.callbacks.borrow_mut().input = Some(callback);
 924    }
 925
 926    fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
 927        self.0.callbacks.borrow_mut().active_status_change = Some(callback);
 928    }
 929
 930    fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
 931        self.0.callbacks.borrow_mut().resize = Some(callback);
 932    }
 933
 934    fn on_moved(&self, callback: Box<dyn FnMut()>) {
 935        self.0.callbacks.borrow_mut().moved = Some(callback);
 936    }
 937
 938    fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
 939        self.0.callbacks.borrow_mut().should_close = Some(callback);
 940    }
 941
 942    fn on_close(&self, callback: Box<dyn FnOnce()>) {
 943        self.0.callbacks.borrow_mut().close = Some(callback);
 944    }
 945
 946    fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
 947        self.0.callbacks.borrow_mut().appearance_changed = Some(callback);
 948    }
 949
 950    fn draw(&self, scene: &Scene) {
 951        let mut inner = self.0.state.borrow_mut();
 952        inner.renderer.draw(scene);
 953    }
 954
 955    fn sprite_atlas(&self) -> sync::Arc<dyn PlatformAtlas> {
 956        let inner = self.0.state.borrow();
 957        inner.renderer.sprite_atlas().clone()
 958    }
 959
 960    fn show_window_menu(&self, position: Point<Pixels>) {
 961        let state = self.0.state.borrow();
 962        let coords = self.get_root_position(position);
 963        let message = ClientMessageEvent::new(
 964            32,
 965            self.0.x_window,
 966            state.atoms._GTK_SHOW_WINDOW_MENU,
 967            [
 968                XINPUT_MASTER_DEVICE as u32,
 969                coords.dst_x as u32,
 970                coords.dst_y as u32,
 971                0,
 972                0,
 973            ],
 974        );
 975        self.0
 976            .xcb_connection
 977            .send_event(
 978                false,
 979                state.x_root_window,
 980                EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
 981                message,
 982            )
 983            .unwrap();
 984    }
 985
 986    fn start_system_move(&self) {
 987        let state = self.0.state.borrow();
 988        let pointer = self
 989            .0
 990            .xcb_connection
 991            .query_pointer(self.0.x_window)
 992            .unwrap()
 993            .reply()
 994            .unwrap();
 995        const MOVERESIZE_MOVE: u32 = 8;
 996        let message = ClientMessageEvent::new(
 997            32,
 998            self.0.x_window,
 999            state.atoms._NET_WM_MOVERESIZE,
1000            [
1001                pointer.root_x as u32,
1002                pointer.root_y as u32,
1003                MOVERESIZE_MOVE,
1004                1, // Left mouse button
1005                1,
1006            ],
1007        );
1008        self.0
1009            .xcb_connection
1010            .send_event(
1011                false,
1012                state.x_root_window,
1013                EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
1014                message,
1015            )
1016            .unwrap();
1017    }
1018
1019    fn should_render_window_controls(&self) -> bool {
1020        false
1021    }
1022}