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