🔒 Repository is read-only – file editing is disabled.

PaganDE/smithay-0.7.0/src/wayland/shell/xdg/handlers/surface.rs main

361 linii Raw ← Powrót
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::sync::Mutex;

use indexmap::IndexSet;

use crate::utils::alive_tracker::{AliveTracker, IsAlive};
use crate::wayland::shell::xdg::{XdgPopupSurfaceData, XdgToplevelSurfaceData};
use crate::{
    utils::{Rectangle, Serial},
    wayland::{
        compositor,
        shell::xdg::{PopupState, XdgShellState, XDG_POPUP_ROLE, XDG_TOPLEVEL_ROLE},
    },
};

use wayland_protocols::{
    xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1,
    xdg::dialog::v1::server::xdg_dialog_v1,
    xdg::shell::server::{
        xdg_popup::XdgPopup, xdg_surface, xdg_surface::XdgSurface, xdg_toplevel::XdgToplevel, xdg_wm_base,
    },
};

use wayland_server::{protocol::wl_surface, DataInit, Dispatch, DisplayHandle, Resource, Weak};

use super::{
    PopupConfigure, SurfaceCachedState, ToplevelConfigure, XdgPopupSurfaceRoleAttributes,
    XdgPositionerUserData, XdgShellHandler, XdgToplevelSurfaceRoleAttributes,
};

mod toplevel;
use toplevel::make_toplevel_handle;
pub use toplevel::{get_parent, send_toplevel_configure};

mod popup;
pub use popup::{make_popup_handle, send_popup_configure};

/// User data of XdgSurface
#[derive(Debug)]
pub struct XdgSurfaceUserData {
    pub(crate) known_surfaces: Arc<Mutex<IndexSet<Weak<xdg_surface::XdgSurface>>>>,
    pub(crate) wl_surface: wl_surface::WlSurface,
    pub(crate) wm_base: xdg_wm_base::XdgWmBase,
    pub(crate) has_active_role: AtomicBool,
}

impl<D> Dispatch<XdgSurface, XdgSurfaceUserData, D> for XdgShellState
where
    D: Dispatch<XdgSurface, XdgSurfaceUserData>,
    D: Dispatch<XdgToplevel, XdgShellSurfaceUserData>,
    D: Dispatch<XdgPopup, XdgShellSurfaceUserData>,
    D: XdgShellHandler,
    D: 'static,
{
    fn request(
        state: &mut D,
        _client: &wayland_server::Client,
        xdg_surface: &XdgSurface,
        request: xdg_surface::Request,
        data: &XdgSurfaceUserData,
        _dh: &DisplayHandle,
        data_init: &mut DataInit<'_, D>,
    ) {
        match request {
            xdg_surface::Request::Destroy => {
                data.known_surfaces
                    .lock()
                    .unwrap()
                    .swap_remove(&xdg_surface.downgrade());

                if !data.wl_surface.alive() {
                    // the wl_surface is destroyed, this means the client is not
                    // trying to change the role but it's a cleanup (possibly a
                    // disconnecting client), ignore the protocol check.
                    return;
                }

                if compositor::get_role(&data.wl_surface).is_none() {
                    // No role assigned to the surface, we can exit early.
                    return;
                }

                if data.has_active_role.load(Ordering::Acquire) {
                    data.wm_base.post_error(
                        xdg_wm_base::Error::Role,
                        "xdg_surface was destroyed before its role object",
                    );
                }
            }
            xdg_surface::Request::GetToplevel { id } => {
                // We now can assign a role to the surface
                let surface = &data.wl_surface;
                let shell = &data.wm_base;

                if compositor::give_role(surface, XDG_TOPLEVEL_ROLE).is_err() {
                    shell.post_error(xdg_wm_base::Error::Role, "Surface already has a role.");
                    return;
                }

                data.has_active_role.store(true, Ordering::Release);

                let initial = compositor::with_states(surface, |states| {
                    let initial = states.data_map.insert_if_missing_threadsafe(|| {
                        Mutex::new(XdgToplevelSurfaceRoleAttributes::default())
                    });

                    // Initialize the toplevel capabilities from the default capabilities
                    let default_capabilities = &state.xdg_shell_state().default_capabilities;
                    let current_capabilties = &mut states
                        .data_map
                        .get::<Mutex<XdgToplevelSurfaceRoleAttributes>>()
                        .unwrap()
                        .lock()
                        .unwrap()
                        .current
                        .capabilities;
                    current_capabilties.replace(default_capabilities.capabilities.iter().copied());

                    initial
                });

                if initial {
                    compositor::add_post_commit_hook::<D, _>(
                        surface,
                        super::super::ToplevelSurface::commit_hook,
                    );
                }

                let toplevel = data_init.init(
                    id,
                    XdgShellSurfaceUserData {
                        wl_surface: data.wl_surface.clone(),
                        xdg_surface: xdg_surface.clone(),
                        wm_base: data.wm_base.clone(),
                        decoration: Default::default(),
                        dialog: Default::default(),
                        alive_tracker: Default::default(),
                    },
                );

                state
                    .xdg_shell_state()
                    .known_toplevels
                    .push(make_toplevel_handle(&toplevel));

                let handle = make_toplevel_handle(&toplevel);

                XdgShellHandler::new_toplevel(state, handle);
            }
            xdg_surface::Request::GetPopup {
                id,
                parent,
                positioner,
            } => {
                let positioner_data = *positioner
                    .data::<XdgPositionerUserData>()
                    .unwrap()
                    .inner
                    .lock()
                    .unwrap();

                let parent_surface = parent.map(|parent| {
                    let parent_data = parent.data::<XdgSurfaceUserData>().unwrap();
                    parent_data.wl_surface.clone()
                });

                // We now can assign a role to the surface
                let surface = &data.wl_surface;
                let shell = &data.wm_base;

                let attributes = XdgPopupSurfaceRoleAttributes {
                    parent: parent_surface,
                    server_pending: Some(PopupState {
                        // Set the positioner data as the popup geometry
                        geometry: positioner_data.get_geometry(),
                        positioner: positioner_data,
                    }),
                    ..Default::default()
                };
                if compositor::give_role(surface, XDG_POPUP_ROLE).is_err() {
                    shell.post_error(xdg_wm_base::Error::Role, "Surface already has a role.");
                    return;
                }

                data.has_active_role.store(true, Ordering::Release);

                let initial = compositor::with_states(surface, |states| {
                    let inserted = states.data_map.insert_if_missing_threadsafe(|| {
                        Mutex::new(XdgPopupSurfaceRoleAttributes::default())
                    });
                    *states
                        .data_map
                        .get::<XdgPopupSurfaceData>()
                        .unwrap()
                        .lock()
                        .unwrap() = attributes;
                    inserted
                });

                if initial {
                    compositor::add_pre_commit_hook::<D, _>(
                        surface,
                        super::super::PopupSurface::pre_commit_hook,
                    );
                    compositor::add_post_commit_hook::<D, _>(
                        surface,
                        super::super::PopupSurface::post_commit_hook,
                    );
                }

                let popup = data_init.init(
                    id,
                    XdgShellSurfaceUserData {
                        wl_surface: data.wl_surface.clone(),
                        xdg_surface: xdg_surface.clone(),
                        wm_base: data.wm_base.clone(),
                        decoration: Default::default(),
                        dialog: Default::default(),
                        alive_tracker: Default::default(),
                    },
                );

                state
                    .xdg_shell_state()
                    .known_popups
                    .push(make_popup_handle(&popup));

                let handle = make_popup_handle(&popup);

                XdgShellHandler::new_popup(state, handle, positioner_data);
            }
            xdg_surface::Request::SetWindowGeometry { x, y, width, height } => {
                // Check the role of the surface, this can be either xdg_toplevel
                // or xdg_popup. If none of the role matches the xdg_surface has no role set
                // which is a protocol error.
                let surface = &data.wl_surface;

                let role = compositor::get_role(surface);

                if role.is_none() {
                    xdg_surface.post_error(
                        xdg_surface::Error::NotConstructed,
                        "xdg_surface must have a role.",
                    );
                    return;
                }

                if role != Some(XDG_TOPLEVEL_ROLE) && role != Some(XDG_POPUP_ROLE) {
                    data.wm_base.post_error(
                        xdg_wm_base::Error::Role,
                        "xdg_surface must have a role of xdg_toplevel or xdg_popup.",
                    );
                }

                compositor::with_states(surface, |states| {
                    states.cached_state.get::<SurfaceCachedState>().pending().geometry =
                        Some(Rectangle::new((x, y).into(), (width, height).into()));
                });
            }
            xdg_surface::Request::AckConfigure { serial } => {
                let serial = Serial::from(serial);
                let surface = &data.wl_surface;

                // Check the role of the surface, this can be either xdg_toplevel
                // or xdg_popup. If none of the role matches the xdg_surface has no role set
                // which is a protocol error.
                if compositor::get_role(surface).is_none() {
                    xdg_surface.post_error(
                        xdg_surface::Error::NotConstructed,
                        "xdg_surface must have a role.",
                    );
                    return;
                }

                // Find the correct configure state for the provided serial
                // discard all configure states that are older than the provided
                // serial.
                // If no matching serial can be found raise a protocol error
                //
                // Invoke the user impl with the found configuration
                // This has to include the serial and the role specific data.
                // - For xdg_popup there is no data.
                // - For xdg_toplevel send the state data including
                //   width, height, min/max size, maximized, fullscreen, resizing, activated
                //
                // This can be used to integrate custom protocol extensions
                let found_configure = compositor::with_states(surface, |states| {
                    if states.role == Some(XDG_TOPLEVEL_ROLE) {
                        Ok(states
                            .data_map
                            .get::<XdgToplevelSurfaceData>()
                            .unwrap()
                            .lock()
                            .unwrap()
                            .ack_configure(serial))
                    } else if states.role == Some(XDG_POPUP_ROLE) {
                        Ok(states
                            .data_map
                            .get::<XdgPopupSurfaceData>()
                            .unwrap()
                            .lock()
                            .unwrap()
                            .ack_configure(serial))
                    } else {
                        Err(())
                    }
                });

                let configure = match found_configure {
                    Ok(Some(configure)) => configure,
                    Ok(None) => {
                        data.wm_base.post_error(
                            xdg_wm_base::Error::InvalidSurfaceState,
                            format!("wrong configure serial: {}", <u32>::from(serial)),
                        );
                        return;
                    }
                    Err(()) => {
                        data.wm_base.post_error(
                            xdg_wm_base::Error::Role as u32,
                            "xdg_surface must have a role of xdg_toplevel or xdg_popup.",
                        );
                        return;
                    }
                };

                XdgShellHandler::ack_configure(state, surface.clone(), configure);
            }
            _ => unreachable!(),
        }
    }
}

/// User data of xdg toplevel surface
#[derive(Debug)]
pub struct XdgShellSurfaceUserData {
    pub(crate) wl_surface: wl_surface::WlSurface,
    pub(crate) wm_base: xdg_wm_base::XdgWmBase,
    pub(crate) xdg_surface: xdg_surface::XdgSurface,
    pub(crate) decoration: Mutex<Option<zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1>>,
    pub(crate) dialog: Mutex<Option<xdg_dialog_v1::XdgDialogV1>>,

    pub(crate) alive_tracker: AliveTracker,
}

impl IsAlive for XdgToplevel {
    #[inline]
    fn alive(&self) -> bool {
        let data: &XdgShellSurfaceUserData = self.data().unwrap();
        data.alive_tracker.alive()
    }
}

impl IsAlive for XdgPopup {
    #[inline]
    fn alive(&self) -> bool {
        let data: &XdgShellSurfaceUserData = self.data().unwrap();
        data.alive_tracker.alive()
    }
}