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