window.rs

   1use anyhow::{Context as _, anyhow};
   2use x11rb::connection::RequestConnection;
   3
   4use crate::linux::X11ClientStatePtr;
   5use gpui::{
   6    AnyWindowHandle, Bounds, Decorations, DevicePixels, ForegroundExecutor, GpuSpecs, Modifiers,
   7    Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow,
   8    Point, PromptButton, PromptLevel, RequestFrameOptions, ResizeEdge, ScaledPixels, Scene, Size,
   9    Tiling, WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowControlArea,
  10    WindowDecorations, WindowKind, WindowParams, px,
  11};
  12use gpui_wgpu::{CompositorGpuHint, WgpuContext, WgpuRenderer, WgpuSurfaceConfig};
  13
  14use collections::FxHashSet;
  15use raw_window_handle as rwh;
  16use util::{ResultExt, maybe};
  17use x11rb::{
  18    connection::Connection,
  19    cookie::{Cookie, VoidCookie},
  20    errors::ConnectionError,
  21    properties::WmSizeHints,
  22    protocol::{
  23        sync,
  24        xinput::{self, ConnectionExt as _},
  25        xproto::{self, ClientMessageEvent, ConnectionExt, TranslateCoordinatesReply},
  26    },
  27    wrapper::ConnectionExt as _,
  28    xcb_ffi::XCBConnection,
  29};
  30
  31use std::{
  32    cell::RefCell, ffi::c_void, fmt::Display, num::NonZeroU32, ptr::NonNull, rc::Rc, sync::Arc,
  33};
  34
  35use super::{X11Display, XINPUT_ALL_DEVICE_GROUPS, XINPUT_ALL_DEVICES};
  36
  37x11rb::atom_manager! {
  38    pub XcbAtoms: AtomsCookie {
  39        XA_ATOM,
  40        XdndAware,
  41        XdndStatus,
  42        XdndEnter,
  43        XdndLeave,
  44        XdndPosition,
  45        XdndSelection,
  46        XdndDrop,
  47        XdndFinished,
  48        XdndTypeList,
  49        XdndActionCopy,
  50        TextUriList: b"text/uri-list",
  51        UTF8_STRING,
  52        TEXT,
  53        STRING,
  54        TEXT_PLAIN_UTF8: b"text/plain;charset=utf-8",
  55        TEXT_PLAIN: b"text/plain",
  56        XDND_DATA,
  57        WM_PROTOCOLS,
  58        WM_DELETE_WINDOW,
  59        WM_CHANGE_STATE,
  60        WM_TRANSIENT_FOR,
  61        _NET_WM_PID,
  62        _NET_WM_NAME,
  63        _NET_WM_STATE,
  64        _NET_WM_STATE_MAXIMIZED_VERT,
  65        _NET_WM_STATE_MAXIMIZED_HORZ,
  66        _NET_WM_STATE_FULLSCREEN,
  67        _NET_WM_STATE_HIDDEN,
  68        _NET_WM_STATE_FOCUSED,
  69        _NET_ACTIVE_WINDOW,
  70        _NET_WM_SYNC_REQUEST,
  71        _NET_WM_SYNC_REQUEST_COUNTER,
  72        _NET_WM_BYPASS_COMPOSITOR,
  73        _NET_WM_MOVERESIZE,
  74        _NET_WM_WINDOW_TYPE,
  75        _NET_WM_WINDOW_TYPE_NOTIFICATION,
  76        _NET_WM_WINDOW_TYPE_DIALOG,
  77        _NET_WM_STATE_MODAL,
  78        _NET_WM_SYNC,
  79        _NET_SUPPORTED,
  80        _MOTIF_WM_HINTS,
  81        _GTK_SHOW_WINDOW_MENU,
  82        _GTK_FRAME_EXTENTS,
  83        _GTK_EDGE_CONSTRAINTS,
  84        _NET_CLIENT_LIST_STACKING,
  85    }
  86}
  87
  88fn query_render_extent(
  89    xcb: &Rc<XCBConnection>,
  90    x_window: xproto::Window,
  91) -> anyhow::Result<Size<DevicePixels>> {
  92    let reply = get_reply(|| "X11 GetGeometry failed.", xcb.get_geometry(x_window))?;
  93    Ok(Size {
  94        width: DevicePixels(reply.width as i32),
  95        height: DevicePixels(reply.height as i32),
  96    })
  97}
  98
  99fn resize_edge_to_moveresize(edge: ResizeEdge) -> u32 {
 100    match edge {
 101        ResizeEdge::TopLeft => 0,
 102        ResizeEdge::Top => 1,
 103        ResizeEdge::TopRight => 2,
 104        ResizeEdge::Right => 3,
 105        ResizeEdge::BottomRight => 4,
 106        ResizeEdge::Bottom => 5,
 107        ResizeEdge::BottomLeft => 6,
 108        ResizeEdge::Left => 7,
 109    }
 110}
 111
 112#[derive(Debug)]
 113struct EdgeConstraints {
 114    top_tiled: bool,
 115    #[allow(dead_code)]
 116    top_resizable: bool,
 117
 118    right_tiled: bool,
 119    #[allow(dead_code)]
 120    right_resizable: bool,
 121
 122    bottom_tiled: bool,
 123    #[allow(dead_code)]
 124    bottom_resizable: bool,
 125
 126    left_tiled: bool,
 127    #[allow(dead_code)]
 128    left_resizable: bool,
 129}
 130
 131impl EdgeConstraints {
 132    fn from_atom(atom: u32) -> Self {
 133        EdgeConstraints {
 134            top_tiled: (atom & (1 << 0)) != 0,
 135            top_resizable: (atom & (1 << 1)) != 0,
 136            right_tiled: (atom & (1 << 2)) != 0,
 137            right_resizable: (atom & (1 << 3)) != 0,
 138            bottom_tiled: (atom & (1 << 4)) != 0,
 139            bottom_resizable: (atom & (1 << 5)) != 0,
 140            left_tiled: (atom & (1 << 6)) != 0,
 141            left_resizable: (atom & (1 << 7)) != 0,
 142        }
 143    }
 144
 145    fn to_tiling(&self) -> Tiling {
 146        Tiling {
 147            top: self.top_tiled,
 148            right: self.right_tiled,
 149            bottom: self.bottom_tiled,
 150            left: self.left_tiled,
 151        }
 152    }
 153}
 154
 155#[derive(Copy, Clone, Debug)]
 156struct Visual {
 157    id: xproto::Visualid,
 158    colormap: u32,
 159    depth: u8,
 160}
 161
 162struct VisualSet {
 163    inherit: Visual,
 164    opaque: Option<Visual>,
 165    transparent: Option<Visual>,
 166    root: u32,
 167    black_pixel: u32,
 168}
 169
 170fn find_visuals(xcb: &XCBConnection, screen_index: usize) -> VisualSet {
 171    let screen = &xcb.setup().roots[screen_index];
 172    let mut set = VisualSet {
 173        inherit: Visual {
 174            id: screen.root_visual,
 175            colormap: screen.default_colormap,
 176            depth: screen.root_depth,
 177        },
 178        opaque: None,
 179        transparent: None,
 180        root: screen.root,
 181        black_pixel: screen.black_pixel,
 182    };
 183
 184    for depth_info in screen.allowed_depths.iter() {
 185        for visual_type in depth_info.visuals.iter() {
 186            let visual = Visual {
 187                id: visual_type.visual_id,
 188                colormap: 0,
 189                depth: depth_info.depth,
 190            };
 191            log::debug!(
 192                "Visual id: {}, class: {:?}, depth: {}, bits_per_value: {}, masks: 0x{:x} 0x{:x} 0x{:x}",
 193                visual_type.visual_id,
 194                visual_type.class,
 195                depth_info.depth,
 196                visual_type.bits_per_rgb_value,
 197                visual_type.red_mask,
 198                visual_type.green_mask,
 199                visual_type.blue_mask,
 200            );
 201
 202            if (
 203                visual_type.red_mask,
 204                visual_type.green_mask,
 205                visual_type.blue_mask,
 206            ) != (0xFF0000, 0xFF00, 0xFF)
 207            {
 208                continue;
 209            }
 210            let color_mask = visual_type.red_mask | visual_type.green_mask | visual_type.blue_mask;
 211            let alpha_mask = color_mask as usize ^ ((1usize << depth_info.depth) - 1);
 212
 213            if alpha_mask == 0 {
 214                if set.opaque.is_none() {
 215                    set.opaque = Some(visual);
 216                }
 217            } else {
 218                if set.transparent.is_none() {
 219                    set.transparent = Some(visual);
 220                }
 221            }
 222        }
 223    }
 224
 225    set
 226}
 227
 228struct RawWindow {
 229    connection: *mut c_void,
 230    screen_id: usize,
 231    window_id: u32,
 232    visual_id: u32,
 233}
 234
 235// Safety: The raw pointers in RawWindow point to X11 connection
 236// which is valid for the window's lifetime. These are used only for
 237// passing to wgpu which needs Send+Sync for surface creation.
 238unsafe impl Send for RawWindow {}
 239unsafe impl Sync for RawWindow {}
 240
 241#[derive(Default)]
 242pub struct Callbacks {
 243    request_frame: Option<Box<dyn FnMut(RequestFrameOptions)>>,
 244    input: Option<Box<dyn FnMut(PlatformInput) -> gpui::DispatchEventResult>>,
 245    active_status_change: Option<Box<dyn FnMut(bool)>>,
 246    hovered_status_change: Option<Box<dyn FnMut(bool)>>,
 247    resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
 248    moved: Option<Box<dyn FnMut()>>,
 249    should_close: Option<Box<dyn FnMut() -> bool>>,
 250    close: Option<Box<dyn FnOnce()>>,
 251    appearance_changed: Option<Box<dyn FnMut()>>,
 252}
 253
 254pub struct X11WindowState {
 255    pub destroyed: bool,
 256    parent: Option<X11WindowStatePtr>,
 257    children: FxHashSet<xproto::Window>,
 258    client: X11ClientStatePtr,
 259    executor: ForegroundExecutor,
 260    atoms: XcbAtoms,
 261    x_root_window: xproto::Window,
 262    pub(crate) counter_id: sync::Counter,
 263    pub(crate) last_sync_counter: Option<sync::Int64>,
 264    bounds: Bounds<Pixels>,
 265    scale_factor: f32,
 266    renderer: WgpuRenderer,
 267    display: Rc<dyn PlatformDisplay>,
 268    input_handler: Option<PlatformInputHandler>,
 269    appearance: WindowAppearance,
 270    background_appearance: WindowBackgroundAppearance,
 271    maximized_vertical: bool,
 272    maximized_horizontal: bool,
 273    hidden: bool,
 274    active: bool,
 275    hovered: bool,
 276    fullscreen: bool,
 277    client_side_decorations_supported: bool,
 278    decorations: WindowDecorations,
 279    edge_constraints: Option<EdgeConstraints>,
 280    pub handle: AnyWindowHandle,
 281    last_insets: [u32; 4],
 282}
 283
 284impl X11WindowState {
 285    fn is_transparent(&self) -> bool {
 286        self.background_appearance != WindowBackgroundAppearance::Opaque
 287    }
 288}
 289
 290#[derive(Clone)]
 291pub(crate) struct X11WindowStatePtr {
 292    pub state: Rc<RefCell<X11WindowState>>,
 293    pub(crate) callbacks: Rc<RefCell<Callbacks>>,
 294    xcb: Rc<XCBConnection>,
 295    pub(crate) x_window: xproto::Window,
 296}
 297
 298impl rwh::HasWindowHandle for RawWindow {
 299    fn window_handle(&self) -> Result<rwh::WindowHandle<'_>, rwh::HandleError> {
 300        let Some(non_zero) = NonZeroU32::new(self.window_id) else {
 301            log::error!("RawWindow.window_id zero when getting window handle.");
 302            return Err(rwh::HandleError::Unavailable);
 303        };
 304        let mut handle = rwh::XcbWindowHandle::new(non_zero);
 305        handle.visual_id = NonZeroU32::new(self.visual_id);
 306        Ok(unsafe { rwh::WindowHandle::borrow_raw(handle.into()) })
 307    }
 308}
 309impl rwh::HasDisplayHandle for RawWindow {
 310    fn display_handle(&self) -> Result<rwh::DisplayHandle<'_>, rwh::HandleError> {
 311        let Some(non_zero) = NonNull::new(self.connection) else {
 312            log::error!("Null RawWindow.connection when getting display handle.");
 313            return Err(rwh::HandleError::Unavailable);
 314        };
 315        let handle = rwh::XcbDisplayHandle::new(Some(non_zero), self.screen_id as i32);
 316        Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) })
 317    }
 318}
 319
 320impl rwh::HasWindowHandle for X11Window {
 321    fn window_handle(&self) -> Result<rwh::WindowHandle<'_>, rwh::HandleError> {
 322        let Some(non_zero) = NonZeroU32::new(self.0.x_window) else {
 323            return Err(rwh::HandleError::Unavailable);
 324        };
 325        let handle = rwh::XcbWindowHandle::new(non_zero);
 326        Ok(unsafe { rwh::WindowHandle::borrow_raw(handle.into()) })
 327    }
 328}
 329
 330impl rwh::HasDisplayHandle for X11Window {
 331    fn display_handle(&self) -> Result<rwh::DisplayHandle<'_>, rwh::HandleError> {
 332        let connection =
 333            as_raw_xcb_connection::AsRawXcbConnection::as_raw_xcb_connection(&*self.0.xcb)
 334                as *mut _;
 335        let Some(non_zero) = NonNull::new(connection) else {
 336            return Err(rwh::HandleError::Unavailable);
 337        };
 338        let screen_id = {
 339            let state = self.0.state.borrow();
 340            u32::from(state.display.id()) as i32
 341        };
 342        let handle = rwh::XcbDisplayHandle::new(Some(non_zero), screen_id);
 343        Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) })
 344    }
 345}
 346
 347pub(crate) fn xcb_flush(xcb: &XCBConnection) {
 348    xcb.flush()
 349        .map_err(handle_connection_error)
 350        .context("X11 flush failed")
 351        .log_err();
 352}
 353
 354pub(crate) fn check_reply<E, F, C>(
 355    failure_context: F,
 356    result: Result<VoidCookie<'_, C>, ConnectionError>,
 357) -> anyhow::Result<()>
 358where
 359    E: Display + Send + Sync + 'static,
 360    F: FnOnce() -> E,
 361    C: RequestConnection,
 362{
 363    result
 364        .map_err(handle_connection_error)
 365        .and_then(|response| response.check().map_err(|reply_error| anyhow!(reply_error)))
 366        .with_context(failure_context)
 367}
 368
 369pub(crate) fn get_reply<E, F, C, O>(
 370    failure_context: F,
 371    result: Result<Cookie<'_, C, O>, ConnectionError>,
 372) -> anyhow::Result<O>
 373where
 374    E: Display + Send + Sync + 'static,
 375    F: FnOnce() -> E,
 376    C: RequestConnection,
 377    O: x11rb::x11_utils::TryParse,
 378{
 379    result
 380        .map_err(handle_connection_error)
 381        .and_then(|response| response.reply().map_err(|reply_error| anyhow!(reply_error)))
 382        .with_context(failure_context)
 383}
 384
 385/// Convert X11 connection errors to `anyhow::Error` and panic for unrecoverable errors.
 386pub(crate) fn handle_connection_error(err: ConnectionError) -> anyhow::Error {
 387    match err {
 388        ConnectionError::UnknownError => anyhow!("X11 connection: Unknown error"),
 389        ConnectionError::UnsupportedExtension => anyhow!("X11 connection: Unsupported extension"),
 390        ConnectionError::MaximumRequestLengthExceeded => {
 391            anyhow!("X11 connection: Maximum request length exceeded")
 392        }
 393        ConnectionError::FdPassingFailed => {
 394            panic!("X11 connection: File descriptor passing failed")
 395        }
 396        ConnectionError::ParseError(parse_error) => {
 397            anyhow!(parse_error).context("Parse error in X11 response")
 398        }
 399        ConnectionError::InsufficientMemory => panic!("X11 connection: Insufficient memory"),
 400        ConnectionError::IoError(err) => anyhow!(err).context("X11 connection: IOError"),
 401        _ => anyhow!(err),
 402    }
 403}
 404
 405impl X11WindowState {
 406    pub fn new(
 407        handle: AnyWindowHandle,
 408        client: X11ClientStatePtr,
 409        executor: ForegroundExecutor,
 410        gpu_context: &mut Option<WgpuContext>,
 411        compositor_gpu: Option<CompositorGpuHint>,
 412        params: WindowParams,
 413        xcb: &Rc<XCBConnection>,
 414        client_side_decorations_supported: bool,
 415        x_main_screen_index: usize,
 416        x_window: xproto::Window,
 417        atoms: &XcbAtoms,
 418        scale_factor: f32,
 419        appearance: WindowAppearance,
 420        parent_window: Option<X11WindowStatePtr>,
 421    ) -> anyhow::Result<Self> {
 422        let x_screen_index = params
 423            .display_id
 424            .map_or(x_main_screen_index, |did| u32::from(did) as usize);
 425
 426        let visual_set = find_visuals(xcb, x_screen_index);
 427
 428        let visual = match visual_set.transparent {
 429            Some(visual) => visual,
 430            None => {
 431                log::warn!("Unable to find a transparent visual",);
 432                visual_set.inherit
 433            }
 434        };
 435        log::info!("Using {:?}", visual);
 436
 437        let colormap = if visual.colormap != 0 {
 438            visual.colormap
 439        } else {
 440            let id = xcb.generate_id()?;
 441            log::info!("Creating colormap {}", id);
 442            check_reply(
 443                || format!("X11 CreateColormap failed. id: {}", id),
 444                xcb.create_colormap(xproto::ColormapAlloc::NONE, id, visual_set.root, visual.id),
 445            )?;
 446            id
 447        };
 448
 449        let win_aux = xproto::CreateWindowAux::new()
 450            // https://stackoverflow.com/questions/43218127/x11-xlib-xcb-creating-a-window-requires-border-pixel-if-specifying-colormap-wh
 451            .border_pixel(visual_set.black_pixel)
 452            .colormap(colormap)
 453            .override_redirect((params.kind == WindowKind::PopUp) as u32)
 454            .event_mask(
 455                xproto::EventMask::EXPOSURE
 456                    | xproto::EventMask::STRUCTURE_NOTIFY
 457                    | xproto::EventMask::FOCUS_CHANGE
 458                    | xproto::EventMask::KEY_PRESS
 459                    | xproto::EventMask::KEY_RELEASE
 460                    | xproto::EventMask::PROPERTY_CHANGE
 461                    | xproto::EventMask::VISIBILITY_CHANGE,
 462            );
 463
 464        let mut bounds = params.bounds.to_device_pixels(scale_factor);
 465        if bounds.size.width.0 == 0 || bounds.size.height.0 == 0 {
 466            log::warn!(
 467                "Window bounds contain a zero value. height={}, width={}. Falling back to defaults.",
 468                bounds.size.height.0,
 469                bounds.size.width.0
 470            );
 471            bounds.size.width = 800.into();
 472            bounds.size.height = 600.into();
 473        }
 474
 475        check_reply(
 476            || {
 477                format!(
 478                    "X11 CreateWindow failed. depth: {}, x_window: {}, visual_set.root: {}, bounds.origin.x.0: {}, bounds.origin.y.0: {}, bounds.size.width.0: {}, bounds.size.height.0: {}",
 479                    visual.depth,
 480                    x_window,
 481                    visual_set.root,
 482                    bounds.origin.x.0 + 2,
 483                    bounds.origin.y.0,
 484                    bounds.size.width.0,
 485                    bounds.size.height.0
 486                )
 487            },
 488            xcb.create_window(
 489                visual.depth,
 490                x_window,
 491                visual_set.root,
 492                (bounds.origin.x.0 + 2) as i16,
 493                bounds.origin.y.0 as i16,
 494                bounds.size.width.0 as u16,
 495                bounds.size.height.0 as u16,
 496                0,
 497                xproto::WindowClass::INPUT_OUTPUT,
 498                visual.id,
 499                &win_aux,
 500            ),
 501        )?;
 502
 503        // Collect errors during setup, so that window can be destroyed on failure.
 504        let setup_result = maybe!({
 505            let pid = std::process::id();
 506            check_reply(
 507                || "X11 ChangeProperty for _NET_WM_PID failed.",
 508                xcb.change_property32(
 509                    xproto::PropMode::REPLACE,
 510                    x_window,
 511                    atoms._NET_WM_PID,
 512                    xproto::AtomEnum::CARDINAL,
 513                    &[pid],
 514                ),
 515            )?;
 516
 517            let reply = get_reply(|| "X11 GetGeometry failed.", xcb.get_geometry(x_window))?;
 518            if reply.x == 0 && reply.y == 0 {
 519                bounds.origin.x.0 += 2;
 520                // Work around a bug where our rendered content appears
 521                // outside the window bounds when opened at the default position
 522                // (14px, 49px on X + Gnome + Ubuntu 22).
 523                let x = bounds.origin.x.0;
 524                let y = bounds.origin.y.0;
 525                check_reply(
 526                    || format!("X11 ConfigureWindow failed. x: {}, y: {}", x, y),
 527                    xcb.configure_window(x_window, &xproto::ConfigureWindowAux::new().x(x).y(y)),
 528                )?;
 529            }
 530            if let Some(titlebar) = params.titlebar
 531                && let Some(title) = titlebar.title
 532            {
 533                check_reply(
 534                    || "X11 ChangeProperty8 on window title failed.",
 535                    xcb.change_property8(
 536                        xproto::PropMode::REPLACE,
 537                        x_window,
 538                        xproto::AtomEnum::WM_NAME,
 539                        xproto::AtomEnum::STRING,
 540                        title.as_bytes(),
 541                    ),
 542                )?;
 543            }
 544
 545            if params.kind == WindowKind::PopUp {
 546                check_reply(
 547                    || "X11 ChangeProperty32 setting window type for pop-up failed.",
 548                    xcb.change_property32(
 549                        xproto::PropMode::REPLACE,
 550                        x_window,
 551                        atoms._NET_WM_WINDOW_TYPE,
 552                        xproto::AtomEnum::ATOM,
 553                        &[atoms._NET_WM_WINDOW_TYPE_NOTIFICATION],
 554                    ),
 555                )?;
 556            }
 557
 558            if params.kind == WindowKind::Floating || params.kind == WindowKind::Dialog {
 559                if let Some(parent_window) = parent_window.as_ref().map(|w| w.x_window) {
 560                    // WM_TRANSIENT_FOR hint indicating the main application window. For floating windows, we set
 561                    // a parent window (WM_TRANSIENT_FOR) such that the window manager knows where to
 562                    // place the floating window in relation to the main window.
 563                    // https://specifications.freedesktop.org/wm-spec/1.4/ar01s05.html
 564                    check_reply(
 565                        || "X11 ChangeProperty32 setting WM_TRANSIENT_FOR for floating window failed.",
 566                        xcb.change_property32(
 567                            xproto::PropMode::REPLACE,
 568                            x_window,
 569                            atoms.WM_TRANSIENT_FOR,
 570                            xproto::AtomEnum::WINDOW,
 571                            &[parent_window],
 572                        ),
 573                    )?;
 574                }
 575            }
 576
 577            let parent = if params.kind == WindowKind::Dialog
 578                && let Some(parent) = parent_window
 579            {
 580                parent.add_child(x_window);
 581
 582                Some(parent)
 583            } else {
 584                None
 585            };
 586
 587            if params.kind == WindowKind::Dialog {
 588                // _NET_WM_WINDOW_TYPE_DIALOG indicates that this is a dialog (floating) window
 589                // https://specifications.freedesktop.org/wm-spec/1.4/ar01s05.html
 590                check_reply(
 591                    || "X11 ChangeProperty32 setting window type for dialog window failed.",
 592                    xcb.change_property32(
 593                        xproto::PropMode::REPLACE,
 594                        x_window,
 595                        atoms._NET_WM_WINDOW_TYPE,
 596                        xproto::AtomEnum::ATOM,
 597                        &[atoms._NET_WM_WINDOW_TYPE_DIALOG],
 598                    ),
 599                )?;
 600
 601                // We set the modal state for dialog windows, so that the window manager
 602                // can handle it appropriately (e.g., prevent interaction with the parent window
 603                // while the dialog is open).
 604                check_reply(
 605                    || "X11 ChangeProperty32 setting modal state for dialog window failed.",
 606                    xcb.change_property32(
 607                        xproto::PropMode::REPLACE,
 608                        x_window,
 609                        atoms._NET_WM_STATE,
 610                        xproto::AtomEnum::ATOM,
 611                        &[atoms._NET_WM_STATE_MODAL],
 612                    ),
 613                )?;
 614            }
 615
 616            check_reply(
 617                || "X11 ChangeProperty32 setting protocols failed.",
 618                xcb.change_property32(
 619                    xproto::PropMode::REPLACE,
 620                    x_window,
 621                    atoms.WM_PROTOCOLS,
 622                    xproto::AtomEnum::ATOM,
 623                    &[atoms.WM_DELETE_WINDOW, atoms._NET_WM_SYNC_REQUEST],
 624                ),
 625            )?;
 626
 627            get_reply(
 628                || "X11 sync protocol initialize failed.",
 629                sync::initialize(xcb, 3, 1),
 630            )?;
 631            let sync_request_counter = xcb.generate_id()?;
 632            check_reply(
 633                || "X11 sync CreateCounter failed.",
 634                sync::create_counter(xcb, sync_request_counter, sync::Int64 { lo: 0, hi: 0 }),
 635            )?;
 636
 637            check_reply(
 638                || "X11 ChangeProperty32 setting sync request counter failed.",
 639                xcb.change_property32(
 640                    xproto::PropMode::REPLACE,
 641                    x_window,
 642                    atoms._NET_WM_SYNC_REQUEST_COUNTER,
 643                    xproto::AtomEnum::CARDINAL,
 644                    &[sync_request_counter],
 645                ),
 646            )?;
 647
 648            check_reply(
 649                || "X11 XiSelectEvents failed.",
 650                xcb.xinput_xi_select_events(
 651                    x_window,
 652                    &[xinput::EventMask {
 653                        deviceid: XINPUT_ALL_DEVICE_GROUPS,
 654                        mask: vec![
 655                            xinput::XIEventMask::MOTION
 656                                | xinput::XIEventMask::BUTTON_PRESS
 657                                | xinput::XIEventMask::BUTTON_RELEASE
 658                                | xinput::XIEventMask::ENTER
 659                                | xinput::XIEventMask::LEAVE,
 660                        ],
 661                    }],
 662                ),
 663            )?;
 664
 665            check_reply(
 666                || "X11 XiSelectEvents for device changes failed.",
 667                xcb.xinput_xi_select_events(
 668                    x_window,
 669                    &[xinput::EventMask {
 670                        deviceid: XINPUT_ALL_DEVICES,
 671                        mask: vec![
 672                            xinput::XIEventMask::HIERARCHY | xinput::XIEventMask::DEVICE_CHANGED,
 673                        ],
 674                    }],
 675                ),
 676            )?;
 677
 678            xcb_flush(xcb);
 679
 680            let renderer = {
 681                let raw_window = RawWindow {
 682                    connection: as_raw_xcb_connection::AsRawXcbConnection::as_raw_xcb_connection(
 683                        xcb,
 684                    ) as *mut _,
 685                    screen_id: x_screen_index,
 686                    window_id: x_window,
 687                    visual_id: visual.id,
 688                };
 689                let config = WgpuSurfaceConfig {
 690                    // Note: this has to be done after the GPU init, or otherwise
 691                    // the sizes are immediately invalidated.
 692                    size: query_render_extent(xcb, x_window)?,
 693                    // We set it to transparent by default, even if we have client-side
 694                    // decorations, since those seem to work on X11 even without `true` here.
 695                    // If the window appearance changes, then the renderer will get updated
 696                    // too
 697                    transparent: false,
 698                };
 699                WgpuRenderer::new(gpu_context, &raw_window, config, compositor_gpu)?
 700            };
 701
 702            // Set max window size hints based on the GPU's maximum texture dimension.
 703            // This prevents the window from being resized larger than what the GPU can render.
 704            let max_texture_size = renderer.max_texture_size();
 705            let mut size_hints = WmSizeHints::new();
 706            if let Some(size) = params.window_min_size {
 707                size_hints.min_size =
 708                    Some((f32::from(size.width) as i32, f32::from(size.height) as i32));
 709            }
 710            size_hints.max_size = Some((max_texture_size as i32, max_texture_size as i32));
 711            check_reply(
 712                || {
 713                    format!(
 714                        "X11 change of WM_SIZE_HINTS failed. max_size: {:?}",
 715                        max_texture_size
 716                    )
 717                },
 718                size_hints.set_normal_hints(xcb, x_window),
 719            )?;
 720
 721            let display = Rc::new(X11Display::new(xcb, scale_factor, x_screen_index)?);
 722
 723            Ok(Self {
 724                parent,
 725                children: FxHashSet::default(),
 726                client,
 727                executor,
 728                display,
 729                x_root_window: visual_set.root,
 730                bounds: bounds.to_pixels(scale_factor),
 731                scale_factor,
 732                renderer,
 733                atoms: *atoms,
 734                input_handler: None,
 735                active: false,
 736                hovered: false,
 737                fullscreen: false,
 738                maximized_vertical: false,
 739                maximized_horizontal: false,
 740                hidden: false,
 741                appearance,
 742                handle,
 743                background_appearance: WindowBackgroundAppearance::Opaque,
 744                destroyed: false,
 745                client_side_decorations_supported,
 746                decorations: WindowDecorations::Server,
 747                last_insets: [0, 0, 0, 0],
 748                edge_constraints: None,
 749                counter_id: sync_request_counter,
 750                last_sync_counter: None,
 751            })
 752        });
 753
 754        if setup_result.is_err() {
 755            check_reply(
 756                || "X11 DestroyWindow failed while cleaning it up after setup failure.",
 757                xcb.destroy_window(x_window),
 758            )?;
 759            xcb_flush(xcb);
 760        }
 761
 762        setup_result
 763    }
 764
 765    fn content_size(&self) -> Size<Pixels> {
 766        self.bounds.size
 767    }
 768}
 769
 770pub(crate) struct X11Window(pub X11WindowStatePtr);
 771
 772impl Drop for X11Window {
 773    fn drop(&mut self) {
 774        let mut state = self.0.state.borrow_mut();
 775
 776        if let Some(parent) = state.parent.as_ref() {
 777            parent.state.borrow_mut().children.remove(&self.0.x_window);
 778        }
 779
 780        state.renderer.destroy();
 781
 782        let destroy_x_window = maybe!({
 783            check_reply(
 784                || "X11 DestroyWindow failure.",
 785                self.0.xcb.destroy_window(self.0.x_window),
 786            )?;
 787            xcb_flush(&self.0.xcb);
 788
 789            anyhow::Ok(())
 790        })
 791        .log_err();
 792
 793        if destroy_x_window.is_some() {
 794            state.destroyed = true;
 795
 796            let this_ptr = self.0.clone();
 797            let client_ptr = state.client.clone();
 798            state
 799                .executor
 800                .spawn(async move {
 801                    this_ptr.close();
 802                    client_ptr.drop_window(this_ptr.x_window);
 803                })
 804                .detach();
 805        }
 806
 807        drop(state);
 808    }
 809}
 810
 811enum WmHintPropertyState {
 812    // Remove = 0,
 813    // Add = 1,
 814    Toggle = 2,
 815}
 816
 817impl X11Window {
 818    pub fn new(
 819        handle: AnyWindowHandle,
 820        client: X11ClientStatePtr,
 821        executor: ForegroundExecutor,
 822        gpu_context: &mut Option<WgpuContext>,
 823        compositor_gpu: Option<CompositorGpuHint>,
 824        params: WindowParams,
 825        xcb: &Rc<XCBConnection>,
 826        client_side_decorations_supported: bool,
 827        x_main_screen_index: usize,
 828        x_window: xproto::Window,
 829        atoms: &XcbAtoms,
 830        scale_factor: f32,
 831        appearance: WindowAppearance,
 832        parent_window: Option<X11WindowStatePtr>,
 833    ) -> anyhow::Result<Self> {
 834        let ptr = X11WindowStatePtr {
 835            state: Rc::new(RefCell::new(X11WindowState::new(
 836                handle,
 837                client,
 838                executor,
 839                gpu_context,
 840                compositor_gpu,
 841                params,
 842                xcb,
 843                client_side_decorations_supported,
 844                x_main_screen_index,
 845                x_window,
 846                atoms,
 847                scale_factor,
 848                appearance,
 849                parent_window,
 850            )?)),
 851            callbacks: Rc::new(RefCell::new(Callbacks::default())),
 852            xcb: xcb.clone(),
 853            x_window,
 854        };
 855
 856        let state = ptr.state.borrow_mut();
 857        ptr.set_wm_properties(state)?;
 858
 859        Ok(Self(ptr))
 860    }
 861
 862    fn set_wm_hints<C: Display + Send + Sync + 'static, F: FnOnce() -> C>(
 863        &self,
 864        failure_context: F,
 865        wm_hint_property_state: WmHintPropertyState,
 866        prop1: u32,
 867        prop2: u32,
 868    ) -> anyhow::Result<()> {
 869        let state = self.0.state.borrow();
 870        let message = ClientMessageEvent::new(
 871            32,
 872            self.0.x_window,
 873            state.atoms._NET_WM_STATE,
 874            [wm_hint_property_state as u32, prop1, prop2, 1, 0],
 875        );
 876        check_reply(
 877            failure_context,
 878            self.0.xcb.send_event(
 879                false,
 880                state.x_root_window,
 881                xproto::EventMask::SUBSTRUCTURE_REDIRECT | xproto::EventMask::SUBSTRUCTURE_NOTIFY,
 882                message,
 883            ),
 884        )?;
 885        xcb_flush(&self.0.xcb);
 886        Ok(())
 887    }
 888
 889    fn get_root_position(
 890        &self,
 891        position: Point<Pixels>,
 892    ) -> anyhow::Result<TranslateCoordinatesReply> {
 893        let state = self.0.state.borrow();
 894        get_reply(
 895            || "X11 TranslateCoordinates failed.",
 896            self.0.xcb.translate_coordinates(
 897                self.0.x_window,
 898                state.x_root_window,
 899                (f32::from(position.x) * state.scale_factor) as i16,
 900                (f32::from(position.y) * state.scale_factor) as i16,
 901            ),
 902        )
 903    }
 904
 905    fn send_moveresize(&self, flag: u32) -> anyhow::Result<()> {
 906        let state = self.0.state.borrow();
 907
 908        check_reply(
 909            || "X11 UngrabPointer before move/resize of window failed.",
 910            self.0.xcb.ungrab_pointer(x11rb::CURRENT_TIME),
 911        )?;
 912
 913        let pointer = get_reply(
 914            || "X11 QueryPointer before move/resize of window failed.",
 915            self.0.xcb.query_pointer(self.0.x_window),
 916        )?;
 917        let message = ClientMessageEvent::new(
 918            32,
 919            self.0.x_window,
 920            state.atoms._NET_WM_MOVERESIZE,
 921            [
 922                pointer.root_x as u32,
 923                pointer.root_y as u32,
 924                flag,
 925                0, // Left mouse button
 926                0,
 927            ],
 928        );
 929        check_reply(
 930            || "X11 SendEvent to move/resize window failed.",
 931            self.0.xcb.send_event(
 932                false,
 933                state.x_root_window,
 934                xproto::EventMask::SUBSTRUCTURE_REDIRECT | xproto::EventMask::SUBSTRUCTURE_NOTIFY,
 935                message,
 936            ),
 937        )?;
 938
 939        xcb_flush(&self.0.xcb);
 940        Ok(())
 941    }
 942}
 943
 944impl X11WindowStatePtr {
 945    pub fn should_close(&self) -> bool {
 946        let mut cb = self.callbacks.borrow_mut();
 947        if let Some(mut should_close) = cb.should_close.take() {
 948            let result = (should_close)();
 949            cb.should_close = Some(should_close);
 950            result
 951        } else {
 952            true
 953        }
 954    }
 955
 956    pub fn property_notify(&self, event: xproto::PropertyNotifyEvent) -> anyhow::Result<()> {
 957        let state = self.state.borrow_mut();
 958        if event.atom == state.atoms._NET_WM_STATE {
 959            self.set_wm_properties(state)?;
 960        } else if event.atom == state.atoms._GTK_EDGE_CONSTRAINTS {
 961            self.set_edge_constraints(state)?;
 962        }
 963        Ok(())
 964    }
 965
 966    fn set_edge_constraints(
 967        &self,
 968        mut state: std::cell::RefMut<X11WindowState>,
 969    ) -> anyhow::Result<()> {
 970        let reply = get_reply(
 971            || "X11 GetProperty for _GTK_EDGE_CONSTRAINTS failed.",
 972            self.xcb.get_property(
 973                false,
 974                self.x_window,
 975                state.atoms._GTK_EDGE_CONSTRAINTS,
 976                xproto::AtomEnum::CARDINAL,
 977                0,
 978                4,
 979            ),
 980        )?;
 981
 982        if reply.value_len != 0 {
 983            if let Ok(bytes) = reply.value[0..4].try_into() {
 984                let atom = u32::from_ne_bytes(bytes);
 985                let edge_constraints = EdgeConstraints::from_atom(atom);
 986                state.edge_constraints.replace(edge_constraints);
 987            } else {
 988                log::error!("Failed to parse GTK_EDGE_CONSTRAINTS");
 989            }
 990        }
 991
 992        Ok(())
 993    }
 994
 995    fn set_wm_properties(
 996        &self,
 997        mut state: std::cell::RefMut<X11WindowState>,
 998    ) -> anyhow::Result<()> {
 999        let reply = get_reply(
1000            || "X11 GetProperty for _NET_WM_STATE failed.",
1001            self.xcb.get_property(
1002                false,
1003                self.x_window,
1004                state.atoms._NET_WM_STATE,
1005                xproto::AtomEnum::ATOM,
1006                0,
1007                u32::MAX,
1008            ),
1009        )?;
1010
1011        let atoms = reply
1012            .value
1013            .chunks_exact(4)
1014            .map(|chunk| u32::from_ne_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]));
1015
1016        state.active = false;
1017        state.fullscreen = false;
1018        state.maximized_vertical = false;
1019        state.maximized_horizontal = false;
1020        state.hidden = false;
1021
1022        for atom in atoms {
1023            if atom == state.atoms._NET_WM_STATE_FOCUSED {
1024                state.active = true;
1025            } else if atom == state.atoms._NET_WM_STATE_FULLSCREEN {
1026                state.fullscreen = true;
1027            } else if atom == state.atoms._NET_WM_STATE_MAXIMIZED_VERT {
1028                state.maximized_vertical = true;
1029            } else if atom == state.atoms._NET_WM_STATE_MAXIMIZED_HORZ {
1030                state.maximized_horizontal = true;
1031            } else if atom == state.atoms._NET_WM_STATE_HIDDEN {
1032                state.hidden = true;
1033            }
1034        }
1035
1036        Ok(())
1037    }
1038
1039    pub fn add_child(&self, child: xproto::Window) {
1040        let mut state = self.state.borrow_mut();
1041        state.children.insert(child);
1042    }
1043
1044    pub fn is_blocked(&self) -> bool {
1045        let state = self.state.borrow();
1046        !state.children.is_empty()
1047    }
1048
1049    pub fn close(&self) {
1050        let state = self.state.borrow();
1051        let client = state.client.clone();
1052        #[allow(clippy::mutable_key_type)]
1053        let children = state.children.clone();
1054        drop(state);
1055
1056        if let Some(client) = client.get_client() {
1057            for child in children {
1058                if let Some(child_window) = client.get_window(child) {
1059                    child_window.close();
1060                }
1061            }
1062        }
1063
1064        let mut callbacks = self.callbacks.borrow_mut();
1065        if let Some(fun) = callbacks.close.take() {
1066            fun()
1067        }
1068    }
1069
1070    pub fn refresh(&self, request_frame_options: RequestFrameOptions) {
1071        let callback = self.callbacks.borrow_mut().request_frame.take();
1072        if let Some(mut fun) = callback {
1073            fun(request_frame_options);
1074            self.callbacks.borrow_mut().request_frame = Some(fun);
1075        }
1076    }
1077
1078    pub fn handle_input(&self, input: PlatformInput) {
1079        if self.is_blocked() {
1080            return;
1081        }
1082        let callback = self.callbacks.borrow_mut().input.take();
1083        if let Some(mut fun) = callback {
1084            let result = fun(input.clone());
1085            self.callbacks.borrow_mut().input = Some(fun);
1086            if !result.propagate {
1087                return;
1088            }
1089        }
1090        if let PlatformInput::KeyDown(event) = input {
1091            // only allow shift modifier when inserting text
1092            if event.keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
1093                let mut state = self.state.borrow_mut();
1094                if let Some(mut input_handler) = state.input_handler.take() {
1095                    if let Some(key_char) = &event.keystroke.key_char {
1096                        drop(state);
1097                        input_handler.replace_text_in_range(None, key_char);
1098                        state = self.state.borrow_mut();
1099                    }
1100                    state.input_handler = Some(input_handler);
1101                }
1102            }
1103        }
1104    }
1105
1106    pub fn handle_ime_commit(&self, text: String) {
1107        if self.is_blocked() {
1108            return;
1109        }
1110        let mut state = self.state.borrow_mut();
1111        if let Some(mut input_handler) = state.input_handler.take() {
1112            drop(state);
1113            input_handler.replace_text_in_range(None, &text);
1114            let mut state = self.state.borrow_mut();
1115            state.input_handler = Some(input_handler);
1116        }
1117    }
1118
1119    pub fn handle_ime_preedit(&self, text: String) {
1120        if self.is_blocked() {
1121            return;
1122        }
1123        let mut state = self.state.borrow_mut();
1124        if let Some(mut input_handler) = state.input_handler.take() {
1125            drop(state);
1126            input_handler.replace_and_mark_text_in_range(None, &text, None);
1127            let mut state = self.state.borrow_mut();
1128            state.input_handler = Some(input_handler);
1129        }
1130    }
1131
1132    pub fn handle_ime_unmark(&self) {
1133        if self.is_blocked() {
1134            return;
1135        }
1136        let mut state = self.state.borrow_mut();
1137        if let Some(mut input_handler) = state.input_handler.take() {
1138            drop(state);
1139            input_handler.unmark_text();
1140            let mut state = self.state.borrow_mut();
1141            state.input_handler = Some(input_handler);
1142        }
1143    }
1144
1145    pub fn handle_ime_delete(&self) {
1146        if self.is_blocked() {
1147            return;
1148        }
1149        let mut state = self.state.borrow_mut();
1150        if let Some(mut input_handler) = state.input_handler.take() {
1151            drop(state);
1152            if let Some(marked) = input_handler.marked_text_range() {
1153                input_handler.replace_text_in_range(Some(marked), "");
1154            }
1155            let mut state = self.state.borrow_mut();
1156            state.input_handler = Some(input_handler);
1157        }
1158    }
1159
1160    pub fn get_ime_area(&self) -> Option<Bounds<ScaledPixels>> {
1161        let mut state = self.state.borrow_mut();
1162        let scale_factor = state.scale_factor;
1163        let mut bounds: Option<Bounds<Pixels>> = None;
1164        if let Some(mut input_handler) = state.input_handler.take() {
1165            drop(state);
1166            if let Some(selection) = input_handler.selected_text_range(true) {
1167                bounds = input_handler.bounds_for_range(selection.range);
1168            }
1169            let mut state = self.state.borrow_mut();
1170            state.input_handler = Some(input_handler);
1171        };
1172        bounds.map(|b| b.scale(scale_factor))
1173    }
1174
1175    pub fn set_bounds(&self, bounds: Bounds<i32>) -> anyhow::Result<()> {
1176        let mut resize_args = None;
1177        let is_resize;
1178        {
1179            let mut state = self.state.borrow_mut();
1180            let bounds = bounds.map(|f| px(f as f32 / state.scale_factor));
1181
1182            is_resize = bounds.size.width != state.bounds.size.width
1183                || bounds.size.height != state.bounds.size.height;
1184
1185            // If it's a resize event (only width/height changed), we ignore `bounds.origin`
1186            // because it contains wrong values.
1187            if is_resize {
1188                state.bounds.size = bounds.size;
1189            } else {
1190                state.bounds = bounds;
1191            }
1192
1193            let gpu_size = query_render_extent(&self.xcb, self.x_window)?;
1194            if true {
1195                state.renderer.update_drawable_size(gpu_size);
1196                resize_args = Some((state.content_size(), state.scale_factor));
1197            }
1198            if let Some(value) = state.last_sync_counter.take() {
1199                check_reply(
1200                    || "X11 sync SetCounter failed.",
1201                    sync::set_counter(&self.xcb, state.counter_id, value),
1202                )?;
1203            }
1204        }
1205
1206        let mut callbacks = self.callbacks.borrow_mut();
1207        if let Some((content_size, scale_factor)) = resize_args
1208            && let Some(ref mut fun) = callbacks.resize
1209        {
1210            fun(content_size, scale_factor)
1211        }
1212
1213        if !is_resize && let Some(ref mut fun) = callbacks.moved {
1214            fun();
1215        }
1216
1217        Ok(())
1218    }
1219
1220    pub fn set_active(&self, focus: bool) {
1221        let callback = self.callbacks.borrow_mut().active_status_change.take();
1222        if let Some(mut fun) = callback {
1223            fun(focus);
1224            self.callbacks.borrow_mut().active_status_change = Some(fun);
1225        }
1226    }
1227
1228    pub fn set_hovered(&self, focus: bool) {
1229        let callback = self.callbacks.borrow_mut().hovered_status_change.take();
1230        if let Some(mut fun) = callback {
1231            fun(focus);
1232            self.callbacks.borrow_mut().hovered_status_change = Some(fun);
1233        }
1234    }
1235
1236    pub fn set_appearance(&mut self, appearance: WindowAppearance) {
1237        let mut state = self.state.borrow_mut();
1238        state.appearance = appearance;
1239        let is_transparent = state.is_transparent();
1240        state.renderer.update_transparency(is_transparent);
1241        state.appearance = appearance;
1242        drop(state);
1243        let callback = self.callbacks.borrow_mut().appearance_changed.take();
1244        if let Some(mut fun) = callback {
1245            fun();
1246            self.callbacks.borrow_mut().appearance_changed = Some(fun);
1247        }
1248    }
1249}
1250
1251impl PlatformWindow for X11Window {
1252    fn bounds(&self) -> Bounds<Pixels> {
1253        self.0.state.borrow().bounds
1254    }
1255
1256    fn is_maximized(&self) -> bool {
1257        let state = self.0.state.borrow();
1258
1259        // A maximized window that gets minimized will still retain its maximized state.
1260        !state.hidden && state.maximized_vertical && state.maximized_horizontal
1261    }
1262
1263    fn window_bounds(&self) -> WindowBounds {
1264        let state = self.0.state.borrow();
1265        if self.is_maximized() {
1266            WindowBounds::Maximized(state.bounds)
1267        } else {
1268            WindowBounds::Windowed(state.bounds)
1269        }
1270    }
1271
1272    fn inner_window_bounds(&self) -> WindowBounds {
1273        let state = self.0.state.borrow();
1274        if self.is_maximized() {
1275            WindowBounds::Maximized(state.bounds)
1276        } else {
1277            let mut bounds = state.bounds;
1278            let [left, right, top, bottom] = state.last_insets;
1279
1280            let [left, right, top, bottom] = [
1281                px((left as f32) / state.scale_factor),
1282                px((right as f32) / state.scale_factor),
1283                px((top as f32) / state.scale_factor),
1284                px((bottom as f32) / state.scale_factor),
1285            ];
1286
1287            bounds.origin.x += left;
1288            bounds.origin.y += top;
1289            bounds.size.width -= left + right;
1290            bounds.size.height -= top + bottom;
1291
1292            WindowBounds::Windowed(bounds)
1293        }
1294    }
1295
1296    fn content_size(&self) -> Size<Pixels> {
1297        // After the wgpu migration, X11WindowState::content_size() returns logical pixels
1298        // (bounds.size is already divided by scale_factor in set_bounds), so no further
1299        // division is needed here. This matches the Wayland implementation.
1300        self.0.state.borrow().content_size()
1301    }
1302
1303    fn resize(&mut self, size: Size<Pixels>) {
1304        let state = self.0.state.borrow();
1305        let size = size.to_device_pixels(state.scale_factor);
1306        let width = size.width.0 as u32;
1307        let height = size.height.0 as u32;
1308
1309        check_reply(
1310            || {
1311                format!(
1312                    "X11 ConfigureWindow failed. width: {}, height: {}",
1313                    width, height
1314                )
1315            },
1316            self.0.xcb.configure_window(
1317                self.0.x_window,
1318                &xproto::ConfigureWindowAux::new()
1319                    .width(width)
1320                    .height(height),
1321            ),
1322        )
1323        .log_err();
1324        xcb_flush(&self.0.xcb);
1325    }
1326
1327    fn scale_factor(&self) -> f32 {
1328        self.0.state.borrow().scale_factor
1329    }
1330
1331    fn appearance(&self) -> WindowAppearance {
1332        self.0.state.borrow().appearance
1333    }
1334
1335    fn display(&self) -> Option<Rc<dyn PlatformDisplay>> {
1336        Some(self.0.state.borrow().display.clone())
1337    }
1338
1339    fn mouse_position(&self) -> Point<Pixels> {
1340        get_reply(
1341            || "X11 QueryPointer failed.",
1342            self.0.xcb.query_pointer(self.0.x_window),
1343        )
1344        .log_err()
1345        .map_or(Point::new(Pixels::ZERO, Pixels::ZERO), |reply| {
1346            Point::new((reply.root_x as u32).into(), (reply.root_y as u32).into())
1347        })
1348    }
1349
1350    fn modifiers(&self) -> Modifiers {
1351        self.0
1352            .state
1353            .borrow()
1354            .client
1355            .0
1356            .upgrade()
1357            .map(|ref_cell| ref_cell.borrow().modifiers)
1358            .unwrap_or_default()
1359    }
1360
1361    fn capslock(&self) -> gpui::Capslock {
1362        self.0
1363            .state
1364            .borrow()
1365            .client
1366            .0
1367            .upgrade()
1368            .map(|ref_cell| ref_cell.borrow().capslock)
1369            .unwrap_or_default()
1370    }
1371
1372    fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
1373        self.0.state.borrow_mut().input_handler = Some(input_handler);
1374    }
1375
1376    fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
1377        self.0.state.borrow_mut().input_handler.take()
1378    }
1379
1380    fn prompt(
1381        &self,
1382        _level: PromptLevel,
1383        _msg: &str,
1384        _detail: Option<&str>,
1385        _answers: &[PromptButton],
1386    ) -> Option<futures::channel::oneshot::Receiver<usize>> {
1387        None
1388    }
1389
1390    fn activate(&self) {
1391        let data = [1, xproto::Time::CURRENT_TIME.into(), 0, 0, 0];
1392        let message = xproto::ClientMessageEvent::new(
1393            32,
1394            self.0.x_window,
1395            self.0.state.borrow().atoms._NET_ACTIVE_WINDOW,
1396            data,
1397        );
1398        self.0
1399            .xcb
1400            .send_event(
1401                false,
1402                self.0.state.borrow().x_root_window,
1403                xproto::EventMask::SUBSTRUCTURE_REDIRECT | xproto::EventMask::SUBSTRUCTURE_NOTIFY,
1404                message,
1405            )
1406            .log_err();
1407        self.0
1408            .xcb
1409            .set_input_focus(
1410                xproto::InputFocus::POINTER_ROOT,
1411                self.0.x_window,
1412                xproto::Time::CURRENT_TIME,
1413            )
1414            .log_err();
1415        xcb_flush(&self.0.xcb);
1416    }
1417
1418    fn is_active(&self) -> bool {
1419        self.0.state.borrow().active
1420    }
1421
1422    fn is_hovered(&self) -> bool {
1423        self.0.state.borrow().hovered
1424    }
1425
1426    fn set_title(&mut self, title: &str) {
1427        check_reply(
1428            || "X11 ChangeProperty8 on WM_NAME failed.",
1429            self.0.xcb.change_property8(
1430                xproto::PropMode::REPLACE,
1431                self.0.x_window,
1432                xproto::AtomEnum::WM_NAME,
1433                xproto::AtomEnum::STRING,
1434                title.as_bytes(),
1435            ),
1436        )
1437        .log_err();
1438
1439        check_reply(
1440            || "X11 ChangeProperty8 on _NET_WM_NAME failed.",
1441            self.0.xcb.change_property8(
1442                xproto::PropMode::REPLACE,
1443                self.0.x_window,
1444                self.0.state.borrow().atoms._NET_WM_NAME,
1445                self.0.state.borrow().atoms.UTF8_STRING,
1446                title.as_bytes(),
1447            ),
1448        )
1449        .log_err();
1450        xcb_flush(&self.0.xcb);
1451    }
1452
1453    fn set_app_id(&mut self, app_id: &str) {
1454        let mut data = Vec::with_capacity(app_id.len() * 2 + 1);
1455        data.extend(app_id.bytes()); // instance https://unix.stackexchange.com/a/494170
1456        data.push(b'\0');
1457        data.extend(app_id.bytes()); // class
1458
1459        check_reply(
1460            || "X11 ChangeProperty8 for WM_CLASS failed.",
1461            self.0.xcb.change_property8(
1462                xproto::PropMode::REPLACE,
1463                self.0.x_window,
1464                xproto::AtomEnum::WM_CLASS,
1465                xproto::AtomEnum::STRING,
1466                &data,
1467            ),
1468        )
1469        .log_err();
1470    }
1471
1472    fn map_window(&mut self) -> anyhow::Result<()> {
1473        check_reply(
1474            || "X11 MapWindow failed.",
1475            self.0.xcb.map_window(self.0.x_window),
1476        )?;
1477        Ok(())
1478    }
1479
1480    fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance) {
1481        let mut state = self.0.state.borrow_mut();
1482        state.background_appearance = background_appearance;
1483        let transparent = state.is_transparent();
1484        state.renderer.update_transparency(transparent);
1485    }
1486
1487    fn background_appearance(&self) -> WindowBackgroundAppearance {
1488        self.0.state.borrow().background_appearance
1489    }
1490
1491    fn is_subpixel_rendering_supported(&self) -> bool {
1492        self.0
1493            .state
1494            .borrow()
1495            .client
1496            .0
1497            .upgrade()
1498            .map(|ref_cell| {
1499                let state = ref_cell.borrow();
1500                state
1501                    .gpu_context
1502                    .as_ref()
1503                    .is_some_and(|ctx| ctx.supports_dual_source_blending())
1504            })
1505            .unwrap_or_default()
1506    }
1507
1508    fn minimize(&self) {
1509        let state = self.0.state.borrow();
1510        const WINDOW_ICONIC_STATE: u32 = 3;
1511        let message = ClientMessageEvent::new(
1512            32,
1513            self.0.x_window,
1514            state.atoms.WM_CHANGE_STATE,
1515            [WINDOW_ICONIC_STATE, 0, 0, 0, 0],
1516        );
1517        check_reply(
1518            || "X11 SendEvent to minimize window failed.",
1519            self.0.xcb.send_event(
1520                false,
1521                state.x_root_window,
1522                xproto::EventMask::SUBSTRUCTURE_REDIRECT | xproto::EventMask::SUBSTRUCTURE_NOTIFY,
1523                message,
1524            ),
1525        )
1526        .log_err();
1527    }
1528
1529    fn zoom(&self) {
1530        let state = self.0.state.borrow();
1531        self.set_wm_hints(
1532            || "X11 SendEvent to maximize a window failed.",
1533            WmHintPropertyState::Toggle,
1534            state.atoms._NET_WM_STATE_MAXIMIZED_VERT,
1535            state.atoms._NET_WM_STATE_MAXIMIZED_HORZ,
1536        )
1537        .log_err();
1538    }
1539
1540    fn toggle_fullscreen(&self) {
1541        let state = self.0.state.borrow();
1542        self.set_wm_hints(
1543            || "X11 SendEvent to fullscreen a window failed.",
1544            WmHintPropertyState::Toggle,
1545            state.atoms._NET_WM_STATE_FULLSCREEN,
1546            xproto::AtomEnum::NONE.into(),
1547        )
1548        .log_err();
1549    }
1550
1551    fn is_fullscreen(&self) -> bool {
1552        self.0.state.borrow().fullscreen
1553    }
1554
1555    fn on_request_frame(&self, callback: Box<dyn FnMut(RequestFrameOptions)>) {
1556        self.0.callbacks.borrow_mut().request_frame = Some(callback);
1557    }
1558
1559    fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> gpui::DispatchEventResult>) {
1560        self.0.callbacks.borrow_mut().input = Some(callback);
1561    }
1562
1563    fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
1564        self.0.callbacks.borrow_mut().active_status_change = Some(callback);
1565    }
1566
1567    fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>) {
1568        self.0.callbacks.borrow_mut().hovered_status_change = Some(callback);
1569    }
1570
1571    fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
1572        self.0.callbacks.borrow_mut().resize = Some(callback);
1573    }
1574
1575    fn on_moved(&self, callback: Box<dyn FnMut()>) {
1576        self.0.callbacks.borrow_mut().moved = Some(callback);
1577    }
1578
1579    fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
1580        self.0.callbacks.borrow_mut().should_close = Some(callback);
1581    }
1582
1583    fn on_close(&self, callback: Box<dyn FnOnce()>) {
1584        self.0.callbacks.borrow_mut().close = Some(callback);
1585    }
1586
1587    fn on_hit_test_window_control(&self, _callback: Box<dyn FnMut() -> Option<WindowControlArea>>) {
1588    }
1589
1590    fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
1591        self.0.callbacks.borrow_mut().appearance_changed = Some(callback);
1592    }
1593
1594    fn draw(&self, scene: &Scene) {
1595        let mut inner = self.0.state.borrow_mut();
1596        inner.renderer.draw(scene);
1597    }
1598
1599    fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
1600        let inner = self.0.state.borrow();
1601        inner.renderer.sprite_atlas().clone()
1602    }
1603
1604    fn show_window_menu(&self, position: Point<Pixels>) {
1605        let state = self.0.state.borrow();
1606
1607        check_reply(
1608            || "X11 UngrabPointer failed.",
1609            self.0.xcb.ungrab_pointer(x11rb::CURRENT_TIME),
1610        )
1611        .log_err();
1612
1613        let Some(coords) = self.get_root_position(position).log_err() else {
1614            return;
1615        };
1616        let message = ClientMessageEvent::new(
1617            32,
1618            self.0.x_window,
1619            state.atoms._GTK_SHOW_WINDOW_MENU,
1620            [
1621                XINPUT_ALL_DEVICE_GROUPS as u32,
1622                coords.dst_x as u32,
1623                coords.dst_y as u32,
1624                0,
1625                0,
1626            ],
1627        );
1628        check_reply(
1629            || "X11 SendEvent to show window menu failed.",
1630            self.0.xcb.send_event(
1631                false,
1632                state.x_root_window,
1633                xproto::EventMask::SUBSTRUCTURE_REDIRECT | xproto::EventMask::SUBSTRUCTURE_NOTIFY,
1634                message,
1635            ),
1636        )
1637        .log_err();
1638    }
1639
1640    fn start_window_move(&self) {
1641        const MOVERESIZE_MOVE: u32 = 8;
1642        self.send_moveresize(MOVERESIZE_MOVE).log_err();
1643    }
1644
1645    fn start_window_resize(&self, edge: ResizeEdge) {
1646        self.send_moveresize(resize_edge_to_moveresize(edge))
1647            .log_err();
1648    }
1649
1650    fn window_decorations(&self) -> gpui::Decorations {
1651        let state = self.0.state.borrow();
1652
1653        // Client window decorations require compositor support
1654        if !state.client_side_decorations_supported {
1655            return Decorations::Server;
1656        }
1657
1658        match state.decorations {
1659            WindowDecorations::Server => Decorations::Server,
1660            WindowDecorations::Client => {
1661                let tiling = if state.fullscreen {
1662                    Tiling::tiled()
1663                } else if let Some(edge_constraints) = &state.edge_constraints {
1664                    edge_constraints.to_tiling()
1665                } else {
1666                    // https://source.chromium.org/chromium/chromium/src/+/main:ui/ozone/platform/x11/x11_window.cc;l=2519;drc=1f14cc876cc5bf899d13284a12c451498219bb2d
1667                    Tiling {
1668                        top: state.maximized_vertical,
1669                        bottom: state.maximized_vertical,
1670                        left: state.maximized_horizontal,
1671                        right: state.maximized_horizontal,
1672                    }
1673                };
1674                Decorations::Client { tiling }
1675            }
1676        }
1677    }
1678
1679    fn set_client_inset(&self, inset: Pixels) {
1680        let mut state = self.0.state.borrow_mut();
1681
1682        let dp = (f32::from(inset) * state.scale_factor) as u32;
1683
1684        let insets = if state.fullscreen {
1685            [0, 0, 0, 0]
1686        } else if let Some(edge_constraints) = &state.edge_constraints {
1687            let left = if edge_constraints.left_tiled { 0 } else { dp };
1688            let top = if edge_constraints.top_tiled { 0 } else { dp };
1689            let right = if edge_constraints.right_tiled { 0 } else { dp };
1690            let bottom = if edge_constraints.bottom_tiled { 0 } else { dp };
1691
1692            [left, right, top, bottom]
1693        } else {
1694            let (left, right) = if state.maximized_horizontal {
1695                (0, 0)
1696            } else {
1697                (dp, dp)
1698            };
1699            let (top, bottom) = if state.maximized_vertical {
1700                (0, 0)
1701            } else {
1702                (dp, dp)
1703            };
1704            [left, right, top, bottom]
1705        };
1706
1707        if state.last_insets != insets {
1708            state.last_insets = insets;
1709
1710            check_reply(
1711                || "X11 ChangeProperty for _GTK_FRAME_EXTENTS failed.",
1712                self.0.xcb.change_property(
1713                    xproto::PropMode::REPLACE,
1714                    self.0.x_window,
1715                    state.atoms._GTK_FRAME_EXTENTS,
1716                    xproto::AtomEnum::CARDINAL,
1717                    size_of::<u32>() as u8 * 8,
1718                    4,
1719                    bytemuck::cast_slice::<u32, u8>(&insets),
1720                ),
1721            )
1722            .log_err();
1723        }
1724    }
1725
1726    fn request_decorations(&self, mut decorations: gpui::WindowDecorations) {
1727        let mut state = self.0.state.borrow_mut();
1728
1729        if matches!(decorations, gpui::WindowDecorations::Client)
1730            && !state.client_side_decorations_supported
1731        {
1732            log::info!(
1733                "x11: no compositor present, falling back to server-side window decorations"
1734            );
1735            decorations = gpui::WindowDecorations::Server;
1736        }
1737
1738        // https://github.com/rust-windowing/winit/blob/master/src/platform_impl/linux/x11/util/hint.rs#L53-L87
1739        let hints_data: [u32; 5] = match decorations {
1740            WindowDecorations::Server => [1 << 1, 0, 1, 0, 0],
1741            WindowDecorations::Client => [1 << 1, 0, 0, 0, 0],
1742        };
1743
1744        let success = check_reply(
1745            || "X11 ChangeProperty for _MOTIF_WM_HINTS failed.",
1746            self.0.xcb.change_property(
1747                xproto::PropMode::REPLACE,
1748                self.0.x_window,
1749                state.atoms._MOTIF_WM_HINTS,
1750                state.atoms._MOTIF_WM_HINTS,
1751                size_of::<u32>() as u8 * 8,
1752                5,
1753                bytemuck::cast_slice::<u32, u8>(&hints_data),
1754            ),
1755        )
1756        .log_err();
1757
1758        let Some(()) = success else {
1759            return;
1760        };
1761
1762        match decorations {
1763            WindowDecorations::Server => {
1764                state.decorations = WindowDecorations::Server;
1765                let is_transparent = state.is_transparent();
1766                state.renderer.update_transparency(is_transparent);
1767            }
1768            WindowDecorations::Client => {
1769                state.decorations = WindowDecorations::Client;
1770                let is_transparent = state.is_transparent();
1771                state.renderer.update_transparency(is_transparent);
1772            }
1773        }
1774
1775        drop(state);
1776        let mut callbacks = self.0.callbacks.borrow_mut();
1777        if let Some(appearance_changed) = callbacks.appearance_changed.as_mut() {
1778            appearance_changed();
1779        }
1780    }
1781
1782    fn update_ime_position(&self, bounds: Bounds<Pixels>) {
1783        let state = self.0.state.borrow();
1784        let client = state.client.clone();
1785        drop(state);
1786        client.update_ime_position(bounds);
1787    }
1788
1789    fn gpu_specs(&self) -> Option<GpuSpecs> {
1790        self.0.state.borrow().renderer.gpu_specs().into()
1791    }
1792}