1577 lines · 50.9 KB · Rust
RawDownload
use anyhow::{anyhow, Context as _}; use crate::platform::blade::{BladeContext, BladeRenderer, BladeSurfaceConfig};use crate::{    px, size, AnyWindowHandle, Bounds, Decorations, DevicePixels, ForegroundExecutor, GpuSpecs,    Modifiers, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler,    PlatformWindow, Point, PromptLevel, RequestFrameOptions, ResizeEdge, ScaledPixels, Scene, Size,    Tiling, WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowDecorations,    WindowKind, WindowParams, X11ClientStatePtr,}; use blade_graphics as gpu;use raw_window_handle as rwh;use util::{maybe, ResultExt};use x11rb::{    connection::Connection,    cookie::{Cookie, VoidCookie},    errors::ConnectionError,    properties::WmSizeHints,    protocol::{        sync,        xinput::{self, ConnectionExt as _},        xproto::{self, ClientMessageEvent, ConnectionExt, EventMask, TranslateCoordinatesReply},    },    wrapper::ConnectionExt as _,    xcb_ffi::XCBConnection,}; use std::{    cell::RefCell, ffi::c_void, fmt::Display, num::NonZeroU32, ops::Div, ptr::NonNull, rc::Rc,    sync::Arc,}; use super::{X11Display, XINPUT_ALL_DEVICES, XINPUT_ALL_DEVICE_GROUPS};x11rb::atom_manager! {    pub XcbAtoms: AtomsCookie {        XA_ATOM,        XdndAware,        XdndStatus,        XdndEnter,        XdndLeave,        XdndPosition,        XdndSelection,        XdndDrop,        XdndFinished,        XdndTypeList,        XdndActionCopy,        TextUriList: b"text/uri-list",        UTF8_STRING,        TEXT,        STRING,        TEXT_PLAIN_UTF8: b"text/plain;charset=utf-8",        TEXT_PLAIN: b"text/plain",        XDND_DATA,        WM_PROTOCOLS,        WM_DELETE_WINDOW,        WM_CHANGE_STATE,        _NET_WM_PID,        _NET_WM_NAME,        _NET_WM_STATE,        _NET_WM_STATE_MAXIMIZED_VERT,        _NET_WM_STATE_MAXIMIZED_HORZ,        _NET_WM_STATE_FULLSCREEN,        _NET_WM_STATE_HIDDEN,        _NET_WM_STATE_FOCUSED,        _NET_ACTIVE_WINDOW,        _NET_WM_SYNC_REQUEST,        _NET_WM_SYNC_REQUEST_COUNTER,        _NET_WM_BYPASS_COMPOSITOR,        _NET_WM_MOVERESIZE,        _NET_WM_WINDOW_TYPE,        _NET_WM_WINDOW_TYPE_NOTIFICATION,        _NET_WM_SYNC,        _NET_SUPPORTED,        _MOTIF_WM_HINTS,        _GTK_SHOW_WINDOW_MENU,        _GTK_FRAME_EXTENTS,        _GTK_EDGE_CONSTRAINTS,        _NET_CLIENT_LIST_STACKING,    }} fn query_render_extent(    xcb: &Rc<XCBConnection>,    x_window: xproto::Window,) -> anyhow::Result<gpu::Extent> {    let reply = get_reply(|| "X11 GetGeometry failed.", xcb.get_geometry(x_window))?;    Ok(gpu::Extent {        width: reply.width as u32,        height: reply.height as u32,        depth: 1,    })} impl ResizeEdge {    fn to_moveresize(&self) -> u32 {        match self {            ResizeEdge::TopLeft => 0,            ResizeEdge::Top => 1,            ResizeEdge::TopRight => 2,            ResizeEdge::Right => 3,            ResizeEdge::BottomRight => 4,            ResizeEdge::Bottom => 5,            ResizeEdge::BottomLeft => 6,            ResizeEdge::Left => 7,        }    }} #[derive(Debug)]struct EdgeConstraints {    top_tiled: bool,    #[allow(dead_code)]    top_resizable: bool,     right_tiled: bool,    #[allow(dead_code)]    right_resizable: bool,     bottom_tiled: bool,    #[allow(dead_code)]    bottom_resizable: bool,     left_tiled: bool,    #[allow(dead_code)]    left_resizable: bool,} impl EdgeConstraints {    fn from_atom(atom: u32) -> Self {        EdgeConstraints {            top_tiled: (atom & (1 << 0)) != 0,            top_resizable: (atom & (1 << 1)) != 0,            right_tiled: (atom & (1 << 2)) != 0,            right_resizable: (atom & (1 << 3)) != 0,            bottom_tiled: (atom & (1 << 4)) != 0,            bottom_resizable: (atom & (1 << 5)) != 0,            left_tiled: (atom & (1 << 6)) != 0,            left_resizable: (atom & (1 << 7)) != 0,        }    }     fn to_tiling(&self) -> Tiling {        Tiling {            top: self.top_tiled,            right: self.right_tiled,            bottom: self.bottom_tiled,            left: self.left_tiled,        }    }} #[derive(Copy, Clone, Debug)]struct Visual {    id: xproto::Visualid,    colormap: u32,    depth: u8,} struct VisualSet {    inherit: Visual,    opaque: Option<Visual>,    transparent: Option<Visual>,    root: u32,    black_pixel: u32,} fn find_visuals(xcb: &XCBConnection, screen_index: usize) -> VisualSet {    let screen = &xcb.setup().roots[screen_index];    let mut set = VisualSet {        inherit: Visual {            id: screen.root_visual,            colormap: screen.default_colormap,            depth: screen.root_depth,        },        opaque: None,        transparent: None,        root: screen.root,        black_pixel: screen.black_pixel,    };     for depth_info in screen.allowed_depths.iter() {        for visual_type in depth_info.visuals.iter() {            let visual = Visual {                id: visual_type.visual_id,                colormap: 0,                depth: depth_info.depth,            };            log::debug!("Visual id: {}, class: {:?}, depth: {}, bits_per_value: {}, masks: 0x{:x} 0x{:x} 0x{:x}",                visual_type.visual_id,                visual_type.class,                depth_info.depth,                visual_type.bits_per_rgb_value,                visual_type.red_mask, visual_type.green_mask, visual_type.blue_mask,            );             if (                visual_type.red_mask,                visual_type.green_mask,                visual_type.blue_mask,            ) != (0xFF0000, 0xFF00, 0xFF)            {                continue;            }            let color_mask = visual_type.red_mask | visual_type.green_mask | visual_type.blue_mask;            let alpha_mask = color_mask as usize ^ ((1usize << depth_info.depth) - 1);             if alpha_mask == 0 {                if set.opaque.is_none() {                    set.opaque = Some(visual);                }            } else {                if set.transparent.is_none() {                    set.transparent = Some(visual);                }            }        }    }     set} struct RawWindow {    connection: *mut c_void,    screen_id: usize,    window_id: u32,    visual_id: u32,} #[derive(Default)]pub struct Callbacks {    request_frame: Option<Box<dyn FnMut(RequestFrameOptions)>>,    input: Option<Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>>,    active_status_change: Option<Box<dyn FnMut(bool)>>,    hovered_status_change: Option<Box<dyn FnMut(bool)>>,    resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,    moved: Option<Box<dyn FnMut()>>,    should_close: Option<Box<dyn FnMut() -> bool>>,    close: Option<Box<dyn FnOnce()>>,    appearance_changed: Option<Box<dyn FnMut()>>,} pub struct X11WindowState {    pub destroyed: bool,    client: X11ClientStatePtr,    executor: ForegroundExecutor,    atoms: XcbAtoms,    x_root_window: xproto::Window,    pub(crate) counter_id: sync::Counter,    pub(crate) last_sync_counter: Option<sync::Int64>,    bounds: Bounds<Pixels>,    scale_factor: f32,    renderer: BladeRenderer,    display: Rc<dyn PlatformDisplay>,    input_handler: Option<PlatformInputHandler>,    appearance: WindowAppearance,    background_appearance: WindowBackgroundAppearance,    maximized_vertical: bool,    maximized_horizontal: bool,    hidden: bool,    active: bool,    hovered: bool,    fullscreen: bool,    client_side_decorations_supported: bool,    decorations: WindowDecorations,    edge_constraints: Option<EdgeConstraints>,    pub handle: AnyWindowHandle,    last_insets: [u32; 4],} impl X11WindowState {    fn is_transparent(&self) -> bool {        self.background_appearance != WindowBackgroundAppearance::Opaque    }} #[derive(Clone)]pub(crate) struct X11WindowStatePtr {    pub state: Rc<RefCell<X11WindowState>>,    pub(crate) callbacks: Rc<RefCell<Callbacks>>,    xcb: Rc<XCBConnection>,    x_window: xproto::Window,} impl rwh::HasWindowHandle for RawWindow {    fn window_handle(&self) -> Result<rwh::WindowHandle, rwh::HandleError> {        let Some(non_zero) = NonZeroU32::new(self.window_id) else {            log::error!("RawWindow.window_id zero when getting window handle.");            return Err(rwh::HandleError::Unavailable);        };        let mut handle = rwh::XcbWindowHandle::new(non_zero);        handle.visual_id = NonZeroU32::new(self.visual_id);        Ok(unsafe { rwh::WindowHandle::borrow_raw(handle.into()) })    }}impl rwh::HasDisplayHandle for RawWindow {    fn display_handle(&self) -> Result<rwh::DisplayHandle, rwh::HandleError> {        let Some(non_zero) = NonNull::new(self.connection) else {            log::error!("Null RawWindow.connection when getting display handle.");            return Err(rwh::HandleError::Unavailable);        };        let handle = rwh::XcbDisplayHandle::new(Some(non_zero), self.screen_id as i32);        Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) })    }} impl rwh::HasWindowHandle for X11Window {    fn window_handle(&self) -> Result<rwh::WindowHandle, rwh::HandleError> {        unimplemented!()    }}impl rwh::HasDisplayHandle for X11Window {    fn display_handle(&self) -> Result<rwh::DisplayHandle, rwh::HandleError> {        unimplemented!()    }} fn check_reply<C, F>(    failure_context: F,    result: Result<VoidCookie<'_, Rc<XCBConnection>>, ConnectionError>,) -> anyhow::Result<()>where    C: Display + Send + Sync + 'static,    F: FnOnce() -> C,{    result        .map_err(|connection_error| anyhow!(connection_error))        .and_then(|response| {            response                .check()                .map_err(|error_response| anyhow!(error_response))        })        .with_context(failure_context)} fn get_reply<C, F, O>(    failure_context: F,    result: Result<Cookie<'_, Rc<XCBConnection>, O>, ConnectionError>,) -> anyhow::Result<O>where    C: Display + Send + Sync + 'static,    F: FnOnce() -> C,    O: x11rb::x11_utils::TryParse,{    result        .map_err(|connection_error| anyhow!(connection_error))        .and_then(|response| {            response                .reply()                .map_err(|error_response| anyhow!(error_response))        })        .with_context(failure_context)} impl X11WindowState {    #[allow(clippy::too_many_arguments)]    pub fn new(        handle: AnyWindowHandle,        client: X11ClientStatePtr,        executor: ForegroundExecutor,        gpu_context: &BladeContext,        params: WindowParams,        xcb: &Rc<XCBConnection>,        client_side_decorations_supported: bool,        x_main_screen_index: usize,        x_window: xproto::Window,        atoms: &XcbAtoms,        scale_factor: f32,        appearance: WindowAppearance,    ) -> anyhow::Result<Self> {        let x_screen_index = params            .display_id            .map_or(x_main_screen_index, |did| did.0 as usize);         let visual_set = find_visuals(&xcb, x_screen_index);         let visual = match visual_set.transparent {            Some(visual) => visual,            None => {                log::warn!("Unable to find a transparent visual",);                visual_set.inherit            }        };        log::info!("Using {:?}", visual);         let colormap = if visual.colormap != 0 {            visual.colormap        } else {            let id = xcb.generate_id()?;            log::info!("Creating colormap {}", id);            check_reply(                || format!("X11 CreateColormap failed. id: {}", id),                xcb.create_colormap(xproto::ColormapAlloc::NONE, id, visual_set.root, visual.id),            )?;            id        };         let win_aux = xproto::CreateWindowAux::new()            // https://stackoverflow.com/questions/43218127/x11-xlib-xcb-creating-a-window-requires-border-pixel-if-specifying-colormap-wh            .border_pixel(visual_set.black_pixel)            .colormap(colormap)            .event_mask(                xproto::EventMask::EXPOSURE                    | xproto::EventMask::STRUCTURE_NOTIFY                    | xproto::EventMask::FOCUS_CHANGE                    | xproto::EventMask::KEY_PRESS                    | xproto::EventMask::KEY_RELEASE                    | EventMask::PROPERTY_CHANGE,            );         let mut bounds = params.bounds.to_device_pixels(scale_factor);        if bounds.size.width.0 == 0 || bounds.size.height.0 == 0 {            log::warn!("Window bounds contain a zero value. height={}, width={}. Falling back to defaults.", bounds.size.height.0, bounds.size.width.0);            bounds.size.width = 800.into();            bounds.size.height = 600.into();        }         check_reply(            || {                format!("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: {}",                                visual.depth, x_window, visual_set.root, bounds.origin.x.0 + 2, bounds.origin.y.0, bounds.size.width.0, bounds.size.height.0)            },            xcb.create_window(                visual.depth,                x_window,                visual_set.root,                (bounds.origin.x.0 + 2) as i16,                bounds.origin.y.0 as i16,                bounds.size.width.0 as u16,                bounds.size.height.0 as u16,                0,                xproto::WindowClass::INPUT_OUTPUT,                visual.id,                &win_aux,            ),        )?;         // Collect errors during setup, so that window can be destroyed on failure.        let setup_result = maybe!({            let pid = std::process::id();            check_reply(                || "X11 ChangeProperty for _NET_WM_PID failed.",                xcb.change_property32(                    xproto::PropMode::REPLACE,                    x_window,                    atoms._NET_WM_PID,                    xproto::AtomEnum::CARDINAL,                    &[pid],                ),            )?;             if let Some(size) = params.window_min_size {                let mut size_hints = WmSizeHints::new();                let min_size = (size.width.0 as i32, size.height.0 as i32);                size_hints.min_size = Some(min_size);                check_reply(                    || {                        format!(                            "X11 change of WM_SIZE_HINTS failed. min_size: {:?}",                            min_size                        )                    },                    size_hints.set_normal_hints(xcb, x_window),                )?;            }             let reply = get_reply(|| "X11 GetGeometry failed.", xcb.get_geometry(x_window))?;            if reply.x == 0 && reply.y == 0 {                bounds.origin.x.0 += 2;                // Work around a bug where our rendered content appears                // outside the window bounds when opened at the default position                // (14px, 49px on X + Gnome + Ubuntu 22).                let x = bounds.origin.x.0;                let y = bounds.origin.y.0;                check_reply(                    || format!("X11 ConfigureWindow failed. x: {}, y: {}", x, y),                    xcb.configure_window(x_window, &xproto::ConfigureWindowAux::new().x(x).y(y)),                )?;            }            if let Some(titlebar) = params.titlebar {                if let Some(title) = titlebar.title {                    check_reply(                        || "X11 ChangeProperty8 on window title failed.",                        xcb.change_property8(                            xproto::PropMode::REPLACE,                            x_window,                            xproto::AtomEnum::WM_NAME,                            xproto::AtomEnum::STRING,                            title.as_bytes(),                        ),                    )?;                }            }            if params.kind == WindowKind::PopUp {                check_reply(                    || "X11 ChangeProperty32 setting window type for pop-up failed.",                    xcb.change_property32(                        xproto::PropMode::REPLACE,                        x_window,                        atoms._NET_WM_WINDOW_TYPE,

Lines 1–500 of 1577.