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 callback = self.callbacks.borrow_mut().request_frame.take();
1060        if let Some(mut fun) = callback {
1061            fun(request_frame_options);
1062            self.callbacks.borrow_mut().request_frame = Some(fun);
1063        }
1064    }
1065
1066    pub fn handle_input(&self, input: PlatformInput) {
1067        if self.is_blocked() {
1068            return;
1069        }
1070        let callback = self.callbacks.borrow_mut().input.take();
1071        if let Some(mut fun) = callback {
1072            let result = fun(input.clone());
1073            self.callbacks.borrow_mut().input = Some(fun);
1074            if !result.propagate {
1075                return;
1076            }
1077        }
1078        if let PlatformInput::KeyDown(event) = input {
1079            // only allow shift modifier when inserting text
1080            if event.keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
1081                let mut state = self.state.borrow_mut();
1082                if let Some(mut input_handler) = state.input_handler.take() {
1083                    if let Some(key_char) = &event.keystroke.key_char {
1084                        drop(state);
1085                        input_handler.replace_text_in_range(None, key_char);
1086                        state = self.state.borrow_mut();
1087                    }
1088                    state.input_handler = Some(input_handler);
1089                }
1090            }
1091        }
1092    }
1093
1094    pub fn handle_ime_commit(&self, text: String) {
1095        if self.is_blocked() {
1096            return;
1097        }
1098        let mut state = self.state.borrow_mut();
1099        if let Some(mut input_handler) = state.input_handler.take() {
1100            drop(state);
1101            input_handler.replace_text_in_range(None, &text);
1102            let mut state = self.state.borrow_mut();
1103            state.input_handler = Some(input_handler);
1104        }
1105    }
1106
1107    pub fn handle_ime_preedit(&self, text: String) {
1108        if self.is_blocked() {
1109            return;
1110        }
1111        let mut state = self.state.borrow_mut();
1112        if let Some(mut input_handler) = state.input_handler.take() {
1113            drop(state);
1114            input_handler.replace_and_mark_text_in_range(None, &text, None);
1115            let mut state = self.state.borrow_mut();
1116            state.input_handler = Some(input_handler);
1117        }
1118    }
1119
1120    pub fn handle_ime_unmark(&self) {
1121        if self.is_blocked() {
1122            return;
1123        }
1124        let mut state = self.state.borrow_mut();
1125        if let Some(mut input_handler) = state.input_handler.take() {
1126            drop(state);
1127            input_handler.unmark_text();
1128            let mut state = self.state.borrow_mut();
1129            state.input_handler = Some(input_handler);
1130        }
1131    }
1132
1133    pub fn handle_ime_delete(&self) {
1134        if self.is_blocked() {
1135            return;
1136        }
1137        let mut state = self.state.borrow_mut();
1138        if let Some(mut input_handler) = state.input_handler.take() {
1139            drop(state);
1140            if let Some(marked) = input_handler.marked_text_range() {
1141                input_handler.replace_text_in_range(Some(marked), "");
1142            }
1143            let mut state = self.state.borrow_mut();
1144            state.input_handler = Some(input_handler);
1145        }
1146    }
1147
1148    pub fn get_ime_area(&self) -> Option<Bounds<ScaledPixels>> {
1149        let mut state = self.state.borrow_mut();
1150        let scale_factor = state.scale_factor;
1151        let mut bounds: Option<Bounds<Pixels>> = None;
1152        if let Some(mut input_handler) = state.input_handler.take() {
1153            drop(state);
1154            if let Some(selection) = input_handler.selected_text_range(true) {
1155                bounds = input_handler.bounds_for_range(selection.range);
1156            }
1157            let mut state = self.state.borrow_mut();
1158            state.input_handler = Some(input_handler);
1159        };
1160        bounds.map(|b| b.scale(scale_factor))
1161    }
1162
1163    pub fn set_bounds(&self, bounds: Bounds<i32>) -> anyhow::Result<()> {
1164        let (is_resize, content_size, scale_factor) = {
1165            let mut state = self.state.borrow_mut();
1166            let bounds = bounds.map(|f| px(f as f32 / state.scale_factor));
1167
1168            let is_resize = bounds.size.width != state.bounds.size.width
1169                || bounds.size.height != state.bounds.size.height;
1170
1171            // If it's a resize event (only width/height changed), we ignore `bounds.origin`
1172            // because it contains wrong values.
1173            if is_resize {
1174                state.bounds.size = bounds.size;
1175            } else {
1176                state.bounds = bounds;
1177            }
1178
1179            let gpu_size = query_render_extent(&self.xcb, self.x_window)?;
1180            state.renderer.update_drawable_size(gpu_size);
1181            let result = (is_resize, state.content_size(), state.scale_factor);
1182            if let Some(value) = state.last_sync_counter.take() {
1183                check_reply(
1184                    || "X11 sync SetCounter failed.",
1185                    sync::set_counter(&self.xcb, state.counter_id, value),
1186                )?;
1187            }
1188            result
1189        };
1190
1191        let mut callbacks = self.callbacks.borrow_mut();
1192        if let Some(ref mut fun) = callbacks.resize {
1193            fun(content_size, scale_factor)
1194        }
1195
1196        if !is_resize && let Some(ref mut fun) = callbacks.moved {
1197            fun();
1198        }
1199
1200        Ok(())
1201    }
1202
1203    pub fn set_active(&self, focus: bool) {
1204        let callback = self.callbacks.borrow_mut().active_status_change.take();
1205        if let Some(mut fun) = callback {
1206            fun(focus);
1207            self.callbacks.borrow_mut().active_status_change = Some(fun);
1208        }
1209    }
1210
1211    pub fn set_hovered(&self, focus: bool) {
1212        let callback = self.callbacks.borrow_mut().hovered_status_change.take();
1213        if let Some(mut fun) = callback {
1214            fun(focus);
1215            self.callbacks.borrow_mut().hovered_status_change = Some(fun);
1216        }
1217    }
1218
1219    pub fn set_appearance(&mut self, appearance: WindowAppearance) {
1220        let mut state = self.state.borrow_mut();
1221        state.appearance = appearance;
1222        let is_transparent = state.is_transparent();
1223        state.renderer.update_transparency(is_transparent);
1224        state.appearance = appearance;
1225        drop(state);
1226        let callback = self.callbacks.borrow_mut().appearance_changed.take();
1227        if let Some(mut fun) = callback {
1228            fun();
1229            self.callbacks.borrow_mut().appearance_changed = Some(fun);
1230        }
1231    }
1232}
1233
1234impl PlatformWindow for X11Window {
1235    fn bounds(&self) -> Bounds<Pixels> {
1236        self.0.state.borrow().bounds
1237    }
1238
1239    fn is_maximized(&self) -> bool {
1240        let state = self.0.state.borrow();
1241
1242        // A maximized window that gets minimized will still retain its maximized state.
1243        !state.hidden && state.maximized_vertical && state.maximized_horizontal
1244    }
1245
1246    fn window_bounds(&self) -> WindowBounds {
1247        let state = self.0.state.borrow();
1248        if self.is_maximized() {
1249            WindowBounds::Maximized(state.bounds)
1250        } else {
1251            WindowBounds::Windowed(state.bounds)
1252        }
1253    }
1254
1255    fn inner_window_bounds(&self) -> WindowBounds {
1256        let state = self.0.state.borrow();
1257        if self.is_maximized() {
1258            WindowBounds::Maximized(state.bounds)
1259        } else {
1260            let mut bounds = state.bounds;
1261            let [left, right, top, bottom] = state.last_insets;
1262
1263            let [left, right, top, bottom] = [
1264                px((left as f32) / state.scale_factor),
1265                px((right as f32) / state.scale_factor),
1266                px((top as f32) / state.scale_factor),
1267                px((bottom as f32) / state.scale_factor),
1268            ];
1269
1270            bounds.origin.x += left;
1271            bounds.origin.y += top;
1272            bounds.size.width -= left + right;
1273            bounds.size.height -= top + bottom;
1274
1275            WindowBounds::Windowed(bounds)
1276        }
1277    }
1278
1279    fn content_size(&self) -> Size<Pixels> {
1280        // After the wgpu migration, X11WindowState::content_size() returns logical pixels
1281        // (bounds.size is already divided by scale_factor in set_bounds), so no further
1282        // division is needed here. This matches the Wayland implementation.
1283        self.0.state.borrow().content_size()
1284    }
1285
1286    fn resize(&mut self, size: Size<Pixels>) {
1287        let state = self.0.state.borrow();
1288        let size = size.to_device_pixels(state.scale_factor);
1289        let width = size.width.0 as u32;
1290        let height = size.height.0 as u32;
1291
1292        check_reply(
1293            || {
1294                format!(
1295                    "X11 ConfigureWindow failed. width: {}, height: {}",
1296                    width, height
1297                )
1298            },
1299            self.0.xcb.configure_window(
1300                self.0.x_window,
1301                &xproto::ConfigureWindowAux::new()
1302                    .width(width)
1303                    .height(height),
1304            ),
1305        )
1306        .log_err();
1307        xcb_flush(&self.0.xcb);
1308    }
1309
1310    fn scale_factor(&self) -> f32 {
1311        self.0.state.borrow().scale_factor
1312    }
1313
1314    fn appearance(&self) -> WindowAppearance {
1315        self.0.state.borrow().appearance
1316    }
1317
1318    fn display(&self) -> Option<Rc<dyn PlatformDisplay>> {
1319        Some(self.0.state.borrow().display.clone())
1320    }
1321
1322    fn mouse_position(&self) -> Point<Pixels> {
1323        get_reply(
1324            || "X11 QueryPointer failed.",
1325            self.0.xcb.query_pointer(self.0.x_window),
1326        )
1327        .log_err()
1328        .map_or(Point::new(Pixels::ZERO, Pixels::ZERO), |reply| {
1329            Point::new((reply.root_x as u32).into(), (reply.root_y as u32).into())
1330        })
1331    }
1332
1333    fn modifiers(&self) -> Modifiers {
1334        self.0
1335            .state
1336            .borrow()
1337            .client
1338            .0
1339            .upgrade()
1340            .map(|ref_cell| ref_cell.borrow().modifiers)
1341            .unwrap_or_default()
1342    }
1343
1344    fn capslock(&self) -> gpui::Capslock {
1345        self.0
1346            .state
1347            .borrow()
1348            .client
1349            .0
1350            .upgrade()
1351            .map(|ref_cell| ref_cell.borrow().capslock)
1352            .unwrap_or_default()
1353    }
1354
1355    fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
1356        self.0.state.borrow_mut().input_handler = Some(input_handler);
1357    }
1358
1359    fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
1360        self.0.state.borrow_mut().input_handler.take()
1361    }
1362
1363    fn prompt(
1364        &self,
1365        _level: PromptLevel,
1366        _msg: &str,
1367        _detail: Option<&str>,
1368        _answers: &[PromptButton],
1369    ) -> Option<futures::channel::oneshot::Receiver<usize>> {
1370        None
1371    }
1372
1373    fn activate(&self) {
1374        let data = [1, xproto::Time::CURRENT_TIME.into(), 0, 0, 0];
1375        let message = xproto::ClientMessageEvent::new(
1376            32,
1377            self.0.x_window,
1378            self.0.state.borrow().atoms._NET_ACTIVE_WINDOW,
1379            data,
1380        );
1381        self.0
1382            .xcb
1383            .send_event(
1384                false,
1385                self.0.state.borrow().x_root_window,
1386                xproto::EventMask::SUBSTRUCTURE_REDIRECT | xproto::EventMask::SUBSTRUCTURE_NOTIFY,
1387                message,
1388            )
1389            .log_err();
1390        self.0
1391            .xcb
1392            .set_input_focus(
1393                xproto::InputFocus::POINTER_ROOT,
1394                self.0.x_window,
1395                xproto::Time::CURRENT_TIME,
1396            )
1397            .log_err();
1398        xcb_flush(&self.0.xcb);
1399    }
1400
1401    fn is_active(&self) -> bool {
1402        self.0.state.borrow().active
1403    }
1404
1405    fn is_hovered(&self) -> bool {
1406        self.0.state.borrow().hovered
1407    }
1408
1409    fn set_title(&mut self, title: &str) {
1410        check_reply(
1411            || "X11 ChangeProperty8 on WM_NAME failed.",
1412            self.0.xcb.change_property8(
1413                xproto::PropMode::REPLACE,
1414                self.0.x_window,
1415                xproto::AtomEnum::WM_NAME,
1416                xproto::AtomEnum::STRING,
1417                title.as_bytes(),
1418            ),
1419        )
1420        .log_err();
1421
1422        check_reply(
1423            || "X11 ChangeProperty8 on _NET_WM_NAME failed.",
1424            self.0.xcb.change_property8(
1425                xproto::PropMode::REPLACE,
1426                self.0.x_window,
1427                self.0.state.borrow().atoms._NET_WM_NAME,
1428                self.0.state.borrow().atoms.UTF8_STRING,
1429                title.as_bytes(),
1430            ),
1431        )
1432        .log_err();
1433        xcb_flush(&self.0.xcb);
1434    }
1435
1436    fn set_app_id(&mut self, app_id: &str) {
1437        let mut data = Vec::with_capacity(app_id.len() * 2 + 1);
1438        data.extend(app_id.bytes()); // instance https://unix.stackexchange.com/a/494170
1439        data.push(b'\0');
1440        data.extend(app_id.bytes()); // class
1441
1442        check_reply(
1443            || "X11 ChangeProperty8 for WM_CLASS failed.",
1444            self.0.xcb.change_property8(
1445                xproto::PropMode::REPLACE,
1446                self.0.x_window,
1447                xproto::AtomEnum::WM_CLASS,
1448                xproto::AtomEnum::STRING,
1449                &data,
1450            ),
1451        )
1452        .log_err();
1453    }
1454
1455    fn map_window(&mut self) -> anyhow::Result<()> {
1456        check_reply(
1457            || "X11 MapWindow failed.",
1458            self.0.xcb.map_window(self.0.x_window),
1459        )?;
1460        Ok(())
1461    }
1462
1463    fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance) {
1464        let mut state = self.0.state.borrow_mut();
1465        state.background_appearance = background_appearance;
1466        let transparent = state.is_transparent();
1467        state.renderer.update_transparency(transparent);
1468    }
1469
1470    fn background_appearance(&self) -> WindowBackgroundAppearance {
1471        self.0.state.borrow().background_appearance
1472    }
1473
1474    fn is_subpixel_rendering_supported(&self) -> bool {
1475        self.0
1476            .state
1477            .borrow()
1478            .client
1479            .0
1480            .upgrade()
1481            .map(|ref_cell| {
1482                let state = ref_cell.borrow();
1483                state
1484                    .gpu_context
1485                    .borrow()
1486                    .as_ref()
1487                    .is_some_and(|ctx| ctx.supports_dual_source_blending())
1488            })
1489            .unwrap_or_default()
1490    }
1491
1492    fn minimize(&self) {
1493        let state = self.0.state.borrow();
1494        const WINDOW_ICONIC_STATE: u32 = 3;
1495        let message = ClientMessageEvent::new(
1496            32,
1497            self.0.x_window,
1498            state.atoms.WM_CHANGE_STATE,
1499            [WINDOW_ICONIC_STATE, 0, 0, 0, 0],
1500        );
1501        check_reply(
1502            || "X11 SendEvent to minimize window failed.",
1503            self.0.xcb.send_event(
1504                false,
1505                state.x_root_window,
1506                xproto::EventMask::SUBSTRUCTURE_REDIRECT | xproto::EventMask::SUBSTRUCTURE_NOTIFY,
1507                message,
1508            ),
1509        )
1510        .log_err();
1511    }
1512
1513    fn zoom(&self) {
1514        let state = self.0.state.borrow();
1515        self.set_wm_hints(
1516            || "X11 SendEvent to maximize a window failed.",
1517            WmHintPropertyState::Toggle,
1518            state.atoms._NET_WM_STATE_MAXIMIZED_VERT,
1519            state.atoms._NET_WM_STATE_MAXIMIZED_HORZ,
1520        )
1521        .log_err();
1522    }
1523
1524    fn toggle_fullscreen(&self) {
1525        let state = self.0.state.borrow();
1526        self.set_wm_hints(
1527            || "X11 SendEvent to fullscreen a window failed.",
1528            WmHintPropertyState::Toggle,
1529            state.atoms._NET_WM_STATE_FULLSCREEN,
1530            xproto::AtomEnum::NONE.into(),
1531        )
1532        .log_err();
1533    }
1534
1535    fn is_fullscreen(&self) -> bool {
1536        self.0.state.borrow().fullscreen
1537    }
1538
1539    fn on_request_frame(&self, callback: Box<dyn FnMut(RequestFrameOptions)>) {
1540        self.0.callbacks.borrow_mut().request_frame = Some(callback);
1541    }
1542
1543    fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> gpui::DispatchEventResult>) {
1544        self.0.callbacks.borrow_mut().input = Some(callback);
1545    }
1546
1547    fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
1548        self.0.callbacks.borrow_mut().active_status_change = Some(callback);
1549    }
1550
1551    fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>) {
1552        self.0.callbacks.borrow_mut().hovered_status_change = Some(callback);
1553    }
1554
1555    fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
1556        self.0.callbacks.borrow_mut().resize = Some(callback);
1557    }
1558
1559    fn on_moved(&self, callback: Box<dyn FnMut()>) {
1560        self.0.callbacks.borrow_mut().moved = Some(callback);
1561    }
1562
1563    fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
1564        self.0.callbacks.borrow_mut().should_close = Some(callback);
1565    }
1566
1567    fn on_close(&self, callback: Box<dyn FnOnce()>) {
1568        self.0.callbacks.borrow_mut().close = Some(callback);
1569    }
1570
1571    fn on_hit_test_window_control(&self, _callback: Box<dyn FnMut() -> Option<WindowControlArea>>) {
1572    }
1573
1574    fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
1575        self.0.callbacks.borrow_mut().appearance_changed = Some(callback);
1576    }
1577
1578    fn draw(&self, scene: &Scene) {
1579        let mut inner = self.0.state.borrow_mut();
1580
1581        if inner.renderer.device_lost() {
1582            let raw_window = RawWindow {
1583                connection: as_raw_xcb_connection::AsRawXcbConnection::as_raw_xcb_connection(
1584                    &*self.0.xcb,
1585                ) as *mut _,
1586                screen_id: inner.x_screen_index,
1587                window_id: self.0.x_window,
1588                visual_id: inner.visual_id,
1589            };
1590            let display_handle = rwh::HasDisplayHandle::display_handle(&raw_window)
1591                .unwrap()
1592                .as_raw();
1593            let window_handle = rwh::HasWindowHandle::window_handle(&raw_window)
1594                .unwrap()
1595                .as_raw();
1596
1597            inner
1598                .renderer
1599                .recover(display_handle, window_handle)
1600                .unwrap_or_else(|err| {
1601                    panic!(
1602                        "GPU device lost and recovery failed. \
1603                        This may happen after system suspend/resume. \
1604                        Please restart the application.\n\nError: {err}"
1605                    )
1606                });
1607
1608            // The current scene references atlas textures that were cleared during recovery.
1609            // Skip this frame and let the next frame rebuild the scene with fresh textures.
1610            return;
1611        }
1612
1613        inner.renderer.draw(scene);
1614    }
1615
1616    fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
1617        let inner = self.0.state.borrow();
1618        inner.renderer.sprite_atlas().clone()
1619    }
1620
1621    fn show_window_menu(&self, position: Point<Pixels>) {
1622        let state = self.0.state.borrow();
1623
1624        check_reply(
1625            || "X11 UngrabPointer failed.",
1626            self.0.xcb.ungrab_pointer(x11rb::CURRENT_TIME),
1627        )
1628        .log_err();
1629
1630        let Some(coords) = self.get_root_position(position).log_err() else {
1631            return;
1632        };
1633        let message = ClientMessageEvent::new(
1634            32,
1635            self.0.x_window,
1636            state.atoms._GTK_SHOW_WINDOW_MENU,
1637            [
1638                XINPUT_ALL_DEVICE_GROUPS as u32,
1639                coords.dst_x as u32,
1640                coords.dst_y as u32,
1641                0,
1642                0,
1643            ],
1644        );
1645        check_reply(
1646            || "X11 SendEvent to show window menu failed.",
1647            self.0.xcb.send_event(
1648                false,
1649                state.x_root_window,
1650                xproto::EventMask::SUBSTRUCTURE_REDIRECT | xproto::EventMask::SUBSTRUCTURE_NOTIFY,
1651                message,
1652            ),
1653        )
1654        .log_err();
1655    }
1656
1657    fn start_window_move(&self) {
1658        const MOVERESIZE_MOVE: u32 = 8;
1659        self.send_moveresize(MOVERESIZE_MOVE).log_err();
1660    }
1661
1662    fn start_window_resize(&self, edge: ResizeEdge) {
1663        self.send_moveresize(resize_edge_to_moveresize(edge))
1664            .log_err();
1665    }
1666
1667    fn window_decorations(&self) -> gpui::Decorations {
1668        let state = self.0.state.borrow();
1669
1670        // Client window decorations require compositor support
1671        if !state.client_side_decorations_supported {
1672            return Decorations::Server;
1673        }
1674
1675        match state.decorations {
1676            WindowDecorations::Server => Decorations::Server,
1677            WindowDecorations::Client => {
1678                let tiling = if state.fullscreen {
1679                    Tiling::tiled()
1680                } else if let Some(edge_constraints) = &state.edge_constraints {
1681                    edge_constraints.to_tiling()
1682                } else {
1683                    // https://source.chromium.org/chromium/chromium/src/+/main:ui/ozone/platform/x11/x11_window.cc;l=2519;drc=1f14cc876cc5bf899d13284a12c451498219bb2d
1684                    Tiling {
1685                        top: state.maximized_vertical,
1686                        bottom: state.maximized_vertical,
1687                        left: state.maximized_horizontal,
1688                        right: state.maximized_horizontal,
1689                    }
1690                };
1691                Decorations::Client { tiling }
1692            }
1693        }
1694    }
1695
1696    fn set_client_inset(&self, inset: Pixels) {
1697        let mut state = self.0.state.borrow_mut();
1698
1699        let dp = (f32::from(inset) * state.scale_factor) as u32;
1700
1701        let insets = if state.fullscreen {
1702            [0, 0, 0, 0]
1703        } else if let Some(edge_constraints) = &state.edge_constraints {
1704            let left = if edge_constraints.left_tiled { 0 } else { dp };
1705            let top = if edge_constraints.top_tiled { 0 } else { dp };
1706            let right = if edge_constraints.right_tiled { 0 } else { dp };
1707            let bottom = if edge_constraints.bottom_tiled { 0 } else { dp };
1708
1709            [left, right, top, bottom]
1710        } else {
1711            let (left, right) = if state.maximized_horizontal {
1712                (0, 0)
1713            } else {
1714                (dp, dp)
1715            };
1716            let (top, bottom) = if state.maximized_vertical {
1717                (0, 0)
1718            } else {
1719                (dp, dp)
1720            };
1721            [left, right, top, bottom]
1722        };
1723
1724        if state.last_insets != insets {
1725            state.last_insets = insets;
1726
1727            check_reply(
1728                || "X11 ChangeProperty for _GTK_FRAME_EXTENTS failed.",
1729                self.0.xcb.change_property(
1730                    xproto::PropMode::REPLACE,
1731                    self.0.x_window,
1732                    state.atoms._GTK_FRAME_EXTENTS,
1733                    xproto::AtomEnum::CARDINAL,
1734                    size_of::<u32>() as u8 * 8,
1735                    4,
1736                    bytemuck::cast_slice::<u32, u8>(&insets),
1737                ),
1738            )
1739            .log_err();
1740        }
1741    }
1742
1743    fn request_decorations(&self, mut decorations: gpui::WindowDecorations) {
1744        let mut state = self.0.state.borrow_mut();
1745
1746        if matches!(decorations, gpui::WindowDecorations::Client)
1747            && !state.client_side_decorations_supported
1748        {
1749            log::info!(
1750                "x11: no compositor present, falling back to server-side window decorations"
1751            );
1752            decorations = gpui::WindowDecorations::Server;
1753        }
1754
1755        // https://github.com/rust-windowing/winit/blob/master/src/platform_impl/linux/x11/util/hint.rs#L53-L87
1756        let hints_data: [u32; 5] = match decorations {
1757            WindowDecorations::Server => [1 << 1, 0, 1, 0, 0],
1758            WindowDecorations::Client => [1 << 1, 0, 0, 0, 0],
1759        };
1760
1761        let success = check_reply(
1762            || "X11 ChangeProperty for _MOTIF_WM_HINTS failed.",
1763            self.0.xcb.change_property(
1764                xproto::PropMode::REPLACE,
1765                self.0.x_window,
1766                state.atoms._MOTIF_WM_HINTS,
1767                state.atoms._MOTIF_WM_HINTS,
1768                size_of::<u32>() as u8 * 8,
1769                5,
1770                bytemuck::cast_slice::<u32, u8>(&hints_data),
1771            ),
1772        )
1773        .log_err();
1774
1775        let Some(()) = success else {
1776            return;
1777        };
1778
1779        match decorations {
1780            WindowDecorations::Server => {
1781                state.decorations = WindowDecorations::Server;
1782                let is_transparent = state.is_transparent();
1783                state.renderer.update_transparency(is_transparent);
1784            }
1785            WindowDecorations::Client => {
1786                state.decorations = WindowDecorations::Client;
1787                let is_transparent = state.is_transparent();
1788                state.renderer.update_transparency(is_transparent);
1789            }
1790        }
1791
1792        drop(state);
1793        let mut callbacks = self.0.callbacks.borrow_mut();
1794        if let Some(appearance_changed) = callbacks.appearance_changed.as_mut() {
1795            appearance_changed();
1796        }
1797    }
1798
1799    fn update_ime_position(&self, bounds: Bounds<Pixels>) {
1800        let state = self.0.state.borrow();
1801        let client = state.client.clone();
1802        drop(state);
1803        client.update_ime_position(bounds);
1804    }
1805
1806    fn gpu_specs(&self) -> Option<GpuSpecs> {
1807        self.0.state.borrow().renderer.gpu_specs().into()
1808    }
1809}