wl-client 0.2.0

Safe client-side libwayland wrapper
Documentation
use {
    crate::{
        Libwayland, Queue,
        builder::prelude::UntypedOwnedProxy,
        ffi::{wl_argument, wl_interface, wl_proxy},
        proxy::low_level::{check_dispatching_proxy, check_new_proxy},
        utils::sync_ptr::SyncPtr,
    },
    parking_lot::{RwLock, RwLockReadGuard},
    std::{
        ptr::{self, NonNull},
        sync::atomic::{AtomicPtr, AtomicU32, Ordering::Relaxed},
    },
};

#[cfg(test)]
mod tests;

/// A borrowed `wl_proxy` pointer.
///
/// Most of the time you will not work with this type directly and instead use one of
/// the `*Ref` types generated by `wl-client-builder`.
///
/// This is a thin wrapper around a `wl_proxy` pointer plus a read-write lock. The
/// contained proxy pointer is at all times either null or a valid pointer.
///
/// You can access the pointer by calling [`UntypedBorrowedProxy::wl_proxy`], however,
/// this does not prevent concurrent destruction. That is, you might check that the
/// returned pointer is not null and another thread might invalidate the pointer at the
/// same time.
///
/// To prevent this, you can acquire the read lock mentioned above by calling
/// [`UntypedBorrowedProxy::lock`]:
///
/// ```
/// # use std::ptr::NonNull;
/// # use wl_client::builder::prelude::{wl_proxy, UntypedBorrowedProxy};
/// #
/// fn pass_proxy_pointer_to_external_function(proxy: &UntypedBorrowedProxy) {
///     let lock = proxy.lock();
///     // SAFETY: The lock ensures that the proxy stays valid if it is not null.
///     unsafe {
///         external_function(lock.wl_proxy());
///     }
/// }
///
/// /// # Safety
/// ///
/// /// The proxy must be valid or null.
/// unsafe fn external_function(proxy: Option<NonNull<wl_proxy>>) {
///     todo!();
/// }
/// ```
pub struct UntypedBorrowedProxy {
    /// The proxy wrapped by this object. The value must only be modified while holding
    /// the write lock. If immutable is true, then the value must not be modified.
    pub(super) proxy: AtomicPtr<wl_proxy>,
    pub(super) lock: RwLock<()>,
    pub(super) libwayland: &'static Libwayland,
    id: AtomicU32,
    immutable: bool,
}

/// A transparent wrapper around [`UntypedBorrowedProxy`].
///
/// # Safety
///
/// This type must be a transparent wrapper around [`UntypedBorrowedProxy`].
pub unsafe trait UntypedBorrowedProxyWrapper: Sized {}

// SAFETY: Self is literally UntypedBorrowedProxy
unsafe impl UntypedBorrowedProxyWrapper for UntypedBorrowedProxy {}

const MIN_SERVER_ID: u32 = 0xff000000;
const LAST_CLIENT_ID: u32 = MIN_SERVER_ID - 1;

/// A lock that ensures that a [`UntypedBorrowedProxy`] is not changed.
///
/// You can acquire it by calling [`proxy::lock`](super::super::lock).
///
/// This lock can be acquired multiple times across multiple threads.
pub struct BorrowedProxyLock<'a> {
    _lock: Option<RwLockReadGuard<'a, ()>>,
    proxy: SyncPtr<wl_proxy>,
}

impl BorrowedProxyLock<'_> {
    /// Returns the `wl_proxy` pointer or `None` if the proxy is destroyed.
    ///
    /// If this function returns a pointer, then the pointer will remain valid while the
    /// lock is being held.
    ///
    /// # Example
    ///
    /// ```
    /// # use wl_client::{proxy, Libwayland};
    /// use wl_client::test_protocols::core::wl_display::WlDisplay;
    /// #
    /// let lib = Libwayland::open().unwrap();
    /// let con = lib.connect_to_default_display().unwrap();
    /// let queue = con.create_queue(c"queue name");
    /// let display: WlDisplay = queue.display();
    ///
    /// let sync = display.sync();
    /// let lock = proxy::lock(&*sync);
    /// assert!(lock.wl_proxy().is_some());
    ///
    /// // This would deadlock.
    /// // proxy::destroy(&sync);
    /// ```
    pub fn wl_proxy(&self) -> Option<NonNull<wl_proxy>> {
        NonNull::new(self.proxy.0)
    }
}

impl UntypedBorrowedProxy {
    /// Creates a new [`UntypedBorrowedProxy`] from an immutable pointer.
    ///
    /// The pointer contained in the new object stays valid for the lifetime of the
    /// object.
    ///
    /// # Safety
    ///
    /// - `proxy` must be a valid pointer.
    /// - `proxy` must stay valid for the lifetime of this object.
    pub unsafe fn new_immutable(libwayland: &'static Libwayland, proxy: NonNull<wl_proxy>) -> Self {
        // SAFETY: The requirement is forwarded to the caller.
        unsafe { Self::new(libwayland, proxy, true) }
    }

    /// Creates a new [`UntypedBorrowedProxy`].
    ///
    /// # Safety
    ///
    /// - `proxy` must be a valid pointer.
    /// - The proxy must be set to null before being invalidated.
    pub(crate) unsafe fn new_internal(
        libwayland: &'static Libwayland,
        proxy: NonNull<wl_proxy>,
    ) -> Self {
        // SAFETY: The requirement is forwarded to the caller.
        unsafe { Self::new(libwayland, proxy, false) }
    }

    /// Creates a new [`UntypedBorrowedProxy`].
    ///
    /// # Safety
    ///
    /// - The proxy must either be null or a valid pointer.
    /// - This property must hold for the lifetime of this object.
    unsafe fn new(
        libwayland: &'static Libwayland,
        proxy: NonNull<wl_proxy>,
        immutable: bool,
    ) -> Self {
        Self {
            proxy: AtomicPtr::new(proxy.as_ptr()),
            lock: RwLock::new(()),
            id: AtomicU32::new(LAST_CLIENT_ID),
            libwayland,
            immutable,
        }
    }

    /// Locks the `wl_proxy` pointer in this object.
    ///
    /// While the pointer is locked, it does not change. Given the invariants of this type,
    /// this implies that the pointer will either stay the same, valid pointer, or the
    /// pointer will be the null pointer.
    ///
    /// This lock can be acquired multiple times concurrently.
    ///
    /// Trying to destroy the proxy while holding a lock will deadlock.
    #[inline]
    pub fn lock(&self) -> BorrowedProxyLock<'_> {
        let lock = match self.immutable {
            true => None,
            false => Some(self.lock.read_recursive()),
        };
        // SAFETY: If the pointer is not immutable, then we're holding a read lock.
        BorrowedProxyLock {
            _lock: lock,
            proxy: SyncPtr(self.proxy.load(Relaxed)),
        }
    }

    /// Retrieve the `wl_proxy` pointer backing this object.
    ///
    /// In a multi-threaded application, the pointer can get invalidated at any time. To
    /// prevent this, you might want to use [`UntypedBorrowedProxy::lock`] instead.
    #[inline]
    pub fn wl_proxy(&self) -> Option<NonNull<wl_proxy>> {
        NonNull::new(self.proxy.load(Relaxed))
    }

    /// Returns the wayland object ID of this proxy.
    ///
    /// If the proxy has already been destroyed, this function returns either the original
    /// ID or 0.
    #[inline]
    pub fn id(&self) -> u32 {
        let mut id = self.id.load(Relaxed);
        if id == LAST_CLIENT_ID {
            id = self.id_slow();
            self.id.store(id, Relaxed);
        }
        id
    }

    #[cold]
    fn id_slow(&self) -> u32 {
        let lock = self.lock();
        if let Some(proxy) = lock.wl_proxy() {
            // SAFETY: Since proxy is not null, the invariants guarantee that proxy is
            //         valid.
            unsafe { self.libwayland.wl_proxy_get_id(proxy.as_ptr()) }
        } else {
            0
        }
    }

    /// Sends a request on this proxy.
    ///
    /// This function cannot be used if the request creates a new object. Use
    /// [`UntypedBorrowedProxy::send_constructor`] for that purpose.
    ///
    /// # Panic
    ///
    /// Panics if this proxy has already been destroyed.
    ///
    /// # Safety
    ///
    /// - `opcode` must be a valid request opcode for the interface of the proxy.
    /// - `args` must conform to the interface + opcode of the proxy.
    /// - `args` must not contain any `new_id` element.
    #[inline]
    pub unsafe fn send_request(&self, opcode: u32, args: &mut [wl_argument]) {
        let lock = self.lock();
        let proxy = check_dispatching_proxy(lock.wl_proxy());
        // SAFETY: - we've checked that proxy is not null, therefore the invariants
        //           guarantee that it is a valid pointer
        //         - the opcode and args requirements are forwarded to the caller
        //         - in particular, interface is null and the args does not contain a
        //           new_id element
        //         - flags does not contain WL_MARSHAL_FLAG_DESTROY
        unsafe {
            self.libwayland.wl_proxy_marshal_array_flags(
                proxy.as_ptr(),
                opcode,
                ptr::null(),
                0,
                0,
                args.as_mut_ptr(),
            );
        }
    }

    /// Creates a new object by sending a request on this proxy.
    ///
    /// This function can only be used if the request creates a new object. Use
    /// [`Self::send_request`] otherwise.
    ///
    /// The new object will be attached to the given queue.
    ///
    /// # Panic
    ///
    /// This function panics if
    ///
    /// - this proxy has already been destroyed, or
    /// - the queue and this proxy don't belong to the same display.
    ///
    /// # Safety
    ///
    /// - `opcode` must be a valid request opcode for the interface of the proxy.
    /// - `args` must conform to the interface + opcode of the proxy.
    /// - `args` must contain exactly one `new_id` element.
    /// - `interface` must be a valid `wl_interface`.
    pub unsafe fn send_constructor(
        &self,
        queue: &Queue,
        opcode: u32,
        args: &mut [wl_argument],
        interface: &'static wl_interface,
        version: Option<u32>,
    ) -> UntypedOwnedProxy {
        let lib = self.libwayland;
        let lock = self.lock();
        let mut proxy = check_dispatching_proxy(lock.wl_proxy());
        // SAFETY: - we've checked that proxy is not null, therefore the invariants
        //           guarantee that it is a valid pointer
        let version =
            version.unwrap_or_else(|| unsafe { lib.wl_proxy_get_version(proxy.as_ptr()) });
        // SAFETY: - we've checked that proxy is not null, therefore the invariants
        //           guarantee that it is a valid pointer
        let parent_queue = unsafe { lib.wl_proxy_get_queue(proxy.as_ptr()) };
        let need_wrapper = parent_queue != queue.wl_event_queue().as_ptr();
        if need_wrapper {
            // SAFETY: - We've verified that the proxy is not null. UntypedBorrowedProxy
            //           requires that the pointer is valid in this case.
            let display = unsafe { self.libwayland.wl_proxy_get_display(proxy.as_ptr()) };
            if display != queue.connection().wl_display().as_ptr() {
                panic!("queue does not belong to same connection");
            }
            // SAFETY: - we've checked that proxy is not null, therefore the invariants
            //           guarantee that it is a valid pointer
            let proxy_ptr = unsafe { lib.wl_proxy_create_wrapper(proxy.as_ptr().cast()).cast() };
            proxy = check_dispatching_proxy(NonNull::new(proxy_ptr));
            // SAFETY: - we've checked that proxy is not null, therefore the invariants
            //           guarantee that it is a valid pointer
            //         - the invariants of queue guarantee that queue the wl_event_queue
            //           is valid
            //         - we've checked above that the display of the queue and the
            //           display of the proxy are the same
            //         - we will destroy the wrapper below
            unsafe {
                lib.wl_proxy_set_queue(proxy.as_ptr(), queue.wl_event_queue().as_ptr());
            }
        }
        // SAFETY: - we've checked that proxy is not null, therefore the invariants
        //           guarantee that it is a valid pointer
        //         - the opcode, args, and interface requirements are forwarded to the
        //           caller
        //         - in particular, interface is a valid wl_interface and args contains
        //           exactly one new_id element
        //         - flags does not contain WL_MARSHAL_FLAG_DESTROY
        let new_proxy = unsafe {
            lib.wl_proxy_marshal_array_flags(
                proxy.as_ptr(),
                opcode,
                interface,
                version,
                0,
                args.as_mut_ptr(),
            )
        };
        if need_wrapper {
            // SAFETY: Since need_wrapper is true, wl_proxy_create_wrapper is the return
            //         value of wl_proxy_create_wrapper above.
            unsafe {
                lib.wl_proxy_wrapper_destroy(proxy.as_ptr().cast());
            }
        }
        let new_proxy = check_new_proxy(new_proxy);
        // SAFETY: - we've just checked that new_proxy is not null
        //         - since new_proxy was returned by wl_proxy_marshal_array_flags its a
        //           valid, plain proxy that we have ownership of
        //         - we hand over ownership to the UntypedOwnedProxy
        //         - we made sure above that new_proxy would be created in `queue`
        //         - the object was created with the interface `interface`
        //         - since it was just created, it does not have an event handler assigned
        unsafe { UntypedOwnedProxy::from_plain_wl_proxy(queue, new_proxy, interface) }
    }

    /// Returns the version of this proxy object.
    ///
    /// The version of the display object is always 0.
    ///
    /// # Panic
    ///
    /// Panics if the proxy is already destroyed.
    pub fn version(&self) -> u32 {
        let lock = self.lock();
        let proxy = check_dispatching_proxy(lock.wl_proxy());
        // SAFETY: - we've checked that proxy is not null, therefore the invariants
        //           guarantee that it is a valid pointer
        unsafe { self.libwayland.wl_proxy_get_version(proxy.as_ptr()) }
    }
}

impl PartialEq for UntypedBorrowedProxy {
    fn eq(&self, other: &Self) -> bool {
        self.wl_proxy() == other.wl_proxy()
    }
}

impl Eq for UntypedBorrowedProxy {}

impl PartialEq<UntypedOwnedProxy> for UntypedBorrowedProxy {
    fn eq(&self, other: &UntypedOwnedProxy) -> bool {
        self.wl_proxy() == other.wl_proxy()
    }
}

impl PartialEq<UntypedBorrowedProxy> for UntypedOwnedProxy {
    fn eq(&self, other: &UntypedBorrowedProxy) -> bool {
        self.wl_proxy() == other.wl_proxy()
    }
}