<NME> mod.rs
<BEF> //! Utilities for handling the `wp_viewporter` protocol
//!
//! ## How to use it
//!
//! ### Initialization
//!
//! To initialize this implementation, create [`ViewporterState`], store it in your `State` struct and
//! implement the required traits, as shown in this example:
//!
//! ```
//! use smithay::wayland::viewporter::ViewporterState;
//! use smithay::delegate_viewporter;
//!
//! # struct State;
//! # let mut display = wayland_server::Display::<State>::new();
//!
//! // Create the viewporter state:
//! let viewporter_state = ViewporterState::new(
//!     &display.handle(), // the display
//!     None // provide a logger, if you want
//! );
//!
//! // implement Dispatch for the Viewporter types
//! delegate_viewporter!(State);
//!
//! // You're now ready to go!
//! ```
//!
//! ### Use the viewport state
//!
//! The [`viewport state`](ViewportCachedState) is double-buffered and
//! can be accessed by using the [`with_states`] function
//!
//! ```no_compile
//! let viewport = with_states(surface, |states| {
//!     states.cached_state.current::<ViewportCachedState>();
//! });
//! ```
//!
//! Before accessing the state you should call [`ensure_viewport_valid`]
//! to ensure the viewport is valid.
//!
//! Note: If you already hand over buffer management to smithay by using
//! [`on_commit_buffer_handler`](crate::backend::renderer::utils::on_commit_buffer_handler)
//! the implementation will already call [`ensure_viewport_valid`] for you.

use std::cell::RefCell;

use wayland_protocols::wp::viewporter::server::{wp_viewport, wp_viewporter};
use wayland_server::{
    backend::GlobalId, protocol::wl_surface, Dispatch, DisplayHandle, GlobalDispatch, Resource,
};

use crate::utils::{IsAlive, Logical, Rectangle, Size};

use super::compositor::{self, with_states, Cacheable, SurfaceData};

/// State of the wp_viewporter Global
#[derive(Debug)]
pub struct ViewporterState {
    global: GlobalId,
}

impl ViewporterState {
    /// Create new [`wp_viewporter`](wayland_protocols::viewporter::server::wp_viewporter) global.
    ///
    /// It returns the viewporter state, which you can drop to remove these global from
    /// the event loop in the future.
    pub fn new<D, L>(display: &DisplayHandle, log: L) -> ViewporterState
    where
        D: GlobalDispatch<wp_viewporter::WpViewporter, slog::Logger>
            + Dispatch<wp_viewporter::WpViewporter, slog::Logger>
            + Dispatch<wp_viewport::WpViewport, ViewportState>
            + 'static,
        L: Into<Option<slog::Logger>>,
    {
        ViewporterState {
            global: display.create_global::<D, wp_viewporter::WpViewporter, slog::Logger>(
                1,
                crate::slog_or_fallback(log).new(slog::o!("smithay_module" => "wp_viewporter")),
            ),
        }
    }

    /// Returns the viewporter global.
    pub fn global(&self) -> GlobalId {
        self.global.clone()
    }
}

impl<D> GlobalDispatch<wp_viewporter::WpViewporter, slog::Logger, D> for ViewporterState
where
    D: GlobalDispatch<wp_viewporter::WpViewporter, slog::Logger>,
    D: Dispatch<wp_viewporter::WpViewporter, slog::Logger>,
    D: Dispatch<wp_viewport::WpViewport, ViewportState>,
{
    fn bind(
        _state: &mut D,
        _handle: &DisplayHandle,
        _client: &wayland_server::Client,
        resource: wayland_server::New<wp_viewporter::WpViewporter>,
        global_data: &slog::Logger,
        data_init: &mut wayland_server::DataInit<'_, D>,
    ) {
        data_init.init(resource, global_data.clone());
    }
}

impl<D> Dispatch<wp_viewporter::WpViewporter, slog::Logger, D> for ViewporterState
where
    D: GlobalDispatch<wp_viewporter::WpViewporter, slog::Logger>,
    D: Dispatch<wp_viewporter::WpViewporter, slog::Logger>,
    D: Dispatch<wp_viewport::WpViewport, ViewportState>,
{
    fn request(
        _state: &mut D,
        _client: &wayland_server::Client,
        _resource: &wp_viewporter::WpViewporter,
        request: <wp_viewporter::WpViewporter as wayland_server::Resource>::Request,
        data: &slog::Logger,
        _dhandle: &DisplayHandle,
        data_init: &mut wayland_server::DataInit<'_, D>,
    ) {
        match request {
            wp_viewporter::Request::GetViewport { id, surface } => {
                let already_has_viewport = with_states(&surface, |states| {
                    states
                        .data_map
                        .get::<RefCell<Option<ViewportMarker>>>()
                        .map(|v| v.borrow().is_some())
                        .unwrap_or(false)
                });

                if already_has_viewport {
                    surface.post_error(
                        wp_viewporter::Error::ViewportExists as u32,
                        "the surface already has a viewport object associated".to_string(),
                    );
                    return;
                }

                compositor::add_pre_commit_hook(&surface, viewport_commit_hook);

                let viewport = data_init.init(
                    id,
                    ViewportState {
                        surface: surface.clone(),
                        log: data.new(slog::o!("surface" => format!("{:?}", surface))),
                    },
                );
                with_states(&surface, |states| {
                    states
                        .data_map
                        .insert_if_missing(|| RefCell::new(Some(ViewportMarker(viewport))));
                })
            }
            wp_viewporter::Request::Destroy => {
                // All is already handled by our destructor
            }
            _ => unreachable!(),
        }
    }
}

impl<D> Dispatch<wp_viewport::WpViewport, ViewportState, D> for ViewportState
where
    D: GlobalDispatch<wp_viewporter::WpViewporter, slog::Logger>,
    D: Dispatch<wp_viewporter::WpViewporter, slog::Logger>,
    D: Dispatch<wp_viewport::WpViewport, ViewportState>,
{
    fn request(
        _state: &mut D,
        _client: &wayland_server::Client,
        resource: &wp_viewport::WpViewport,
        request: <wp_viewport::WpViewport as wayland_server::Resource>::Request,
        data: &ViewportState,
        _dhandle: &DisplayHandle,
        _data_init: &mut wayland_server::DataInit<'_, D>,
    ) {
        match request {
            wp_viewport::Request::Destroy => {
                with_states(&data.surface, |states| {
                    states
                        .data_map
                        .get::<RefCell<Option<ViewportMarker>>>()
                        .unwrap()
                        .borrow_mut()
                        .take();
                    *states.cached_state.pending::<ViewportCachedState>() = ViewportCachedState::default();
                });
            }
            wp_viewport::Request::SetSource { x, y, width, height } => {
                // If all of x, y, width and height are -1.0, the source rectangle is unset instead.
                // Any other set of values where width or height are zero or negative,
                // or x or y are negative, raise the bad_value protocol error.
                let is_unset = x == -1.0 && y == -1.0 && width == -1.0 && height == -1.0;
                let is_valid_src = x >= 0.0 && y >= 0.0 && width > 0.0 && height > 0.0;

                if !is_unset && !is_valid_src {
                    resource.post_error(
                        wp_viewport::Error::BadValue as u32,
                        "negative or zero values in width or height or negative values in x or y".to_string(),
                    );
                    return;
                }

                // If the wl_surface associated with the wp_viewport is destroyed,
                // all wp_viewport requests except 'destroy' raise the protocol error no_surface.
                if !data.surface.alive() {
                    resource.post_error(
                        wp_viewport::Error::NoSurface as u32,
                        "the wl_surface was destroyed".to_string(),
                    );
                    return;
                }

                with_states(&data.surface, |states| {
                    let mut viewport_state = states.cached_state.pending::<ViewportCachedState>();
                    let src = if is_unset {
                        None
                    } else {
                        let src = Rectangle::from_loc_and_size((x, y), (width, height));
                        slog::trace!(data.log, "New destination: {:?}", src);
                        Some(src)
                    };
                    viewport_state.src = src;
                });
            }
            wp_viewport::Request::SetDestination { width, height } => {
                // If width is -1 and height is -1, the destination size is unset instead.
                // Any other pair of values for width and height that contains zero or
                // negative values raises the bad_value protocol error.
                let is_unset = width == -1 && height == -1;
                let is_valid_size = width > 0 && height > 0;

                if !is_unset && !is_valid_size {
                    resource.post_error(
                        wp_viewport::Error::BadValue as u32,
                        "negative or zero values in width or height".to_string(),
                    );
                    return;
                }

                // If the wl_surface associated with the wp_viewport is destroyed,
                // all wp_viewport requests except 'destroy' raise the protocol error no_surface.
                if !data.surface.alive() {
                    resource.post_error(
                        wp_viewport::Error::NoSurface as u32,
                        "the wl_surface was destroyed".to_string(),
                    );
                }

                with_states(&data.surface, |states| {
                    let mut viewport_state = states.cached_state.pending::<ViewportCachedState>();
                    let size = if is_unset {
                        None
                    } else {
                        let dst = Size::from((width, height));
                        slog::trace!(data.log, "New destination: {:?}", dst);
                        Some(dst)
                    };
                    viewport_state.dst = size;
                });
            }
            _ => unreachable!(),
        }
    }
}

/// State of a single viewport attached to a surface
#[derive(Debug)]
pub struct ViewportState {
    surface: wl_surface::WlSurface,
    log: slog::Logger,
}

struct ViewportMarker(wp_viewport::WpViewport);

fn viewport_commit_hook(_dh: &DisplayHandle, surface: &wl_surface::WlSurface) {
    with_states(surface, |states| {
        states
            .data_map
            .insert_if_missing(|| RefCell::new(Option::<ViewportMarker>::None));
        let viewport = states
            .data_map
            .get::<RefCell<Option<ViewportMarker>>>()
            .unwrap()
            .borrow();
        if let Some(viewport) = &*viewport {
            let viewport_state = states.cached_state.pending::<ViewportCachedState>();

            // If src_width or src_height are not integers and destination size is not set,
            // the bad_size protocol error is raised when the surface state is applied.
            let src_size = viewport_state.src.map(|src| src.size);
            if viewport_state.dst.is_none()
                && src_size != src_size.map(|s| Size::from((s.w as i32, s.h as i32)).to_f64())
            {
                viewport.0.post_error(
                    wp_viewport::Error::BadSize as u32,
                    "destination size is not integer".to_string(),
                );
            }
        }
    });
}

/// Ensures that the viewport, if any, is valid accordingly to the protocol specification.
///
/// If the viewport violates any protocol checks a protocol error will be raised and `false`
/// is returned.
pub fn ensure_viewport_valid(states: &SurfaceData, buffer_size: Size<i32, Logical>) -> bool {
    states
        .data_map
        .insert_if_missing(|| RefCell::new(Option::<ViewportMarker>::None));
    let viewport = states
        .data_map
        .get::<RefCell<Option<ViewportMarker>>>()
        .unwrap()
        .borrow();

    if let Some(viewport) = &*viewport {
        let state = states.cached_state.pending::<ViewportCachedState>();

        let buffer_rect = Rectangle::from_loc_and_size((0.0, 0.0), buffer_size.to_f64());
        let src = state.src.unwrap_or(buffer_rect);
        let valid = buffer_rect.contains_rect(src);
        if !valid {
            viewport.0.post_error(
                wp_viewport::Error::OutOfBuffer as u32,
                "source rectangle extends outside of the content area".to_string(),
            );
        }
        valid
    } else {
        true
    }
}

/// Represents the double-buffered viewport
/// state of a [`WlSurface`](wl_surface::WlSurface)
#[derive(Debug, Default, Clone, Copy)]
pub struct ViewportCachedState {
    /// Defines the source [`Rectangle`] of the [`WlSurface`](wl_surface::WlSurface) in [`Logical`]
    /// coordinates used for cropping.
    pub src: Option<Rectangle<f64, Logical>>,
    /// Defines the destination [`Size`] of the [`WlSurface`](wl_surface::WlSurface) in [`Logical`]
    /// coordinates used for scaling.
    pub dst: Option<Size<i32, Logical>>,
}

impl ViewportCachedState {
    /// Gets the actual size the [`WlSurface`](wl_surface::WlSurface) should have on screen in
    /// [`Logical`] coordinates.
    ///
    /// This will return the destination size if set or the size of the source rectangle.
    /// If both are unset `None` is returned.
    pub fn size(&self) -> Option<Size<i32, Logical>> {
        self.dst.or_else(|| {
            self.src
                .map(|src| Size::from((src.size.w as i32, src.size.h as i32)))
        })
    }
}

impl Cacheable for ViewportCachedState {
    fn commit(&mut self, _dh: &DisplayHandle) -> Self {
        ViewportCachedState {
            src: self.src,
            dst: self.dst,
        }
    }

    fn merge_into(self, into: &mut Self, _dh: &DisplayHandle) {
        into.src = self.src;
        into.dst = self.dst;
    }
}

#[allow(missing_docs)] // TODO
#[macro_export]
macro_rules! delegate_viewporter {
    ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
        $crate::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
            $crate::reexports::wayland_protocols::wp::viewporter::server::wp_viewporter::WpViewporter: slog::Logger
        ] => $crate::wayland::viewporter::ViewporterState);

        $crate::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
            $crate::reexports::wayland_protocols::wp::viewporter::server::wp_viewporter::WpViewporter: slog::Logger
        ] => $crate::wayland::viewporter::ViewporterState);
        $crate::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
            $crate::reexports::wayland_protocols::wp::viewporter::server::wp_viewport::WpViewport: $crate::wayland::viewporter::ViewportState
        ] => $crate::wayland::viewporter::ViewportState);
    };
}

<MSG> viewporter: fixup doc test

<DFF> @@ -12,7 +12,7 @@
 //! use smithay::delegate_viewporter;
 //!
 //! # struct State;
-//! # let mut display = wayland_server::Display::<State>::new();
+//! # let mut display = wayland_server::Display::<State>::new().unwrap();
 //!
 //! // Create the viewporter state:
 //! let viewporter_state = ViewporterState::new(

viewporter: fixup doc test