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