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