hyprwire 0.2.6

A fast and consistent wire protocol for IPC
Documentation
//! Hyprwire client and server runtime plus scanner-generated protocol bindings.

#![warn(missing_docs)]

pub(crate) const PROTOCOL_VERSION: u32 = 1;

/// Client-side APIs for connecting to a Hyprwire server and dispatching
/// generated protocol events.
pub mod client;
pub(crate) mod helpers;
/// Traits and low-level types used by generated client/server protocol
/// bindings.
pub mod implementation;
pub(crate) mod message;
/// Server-side APIs for hosting Hyprwire protocols and dispatching client
/// requests.
pub mod server;
pub(crate) mod socket;

use implementation::object as impl_object;
use nix::{errno, poll, sys};
use std::os::fd::{AsFd, AsRawFd};
use std::os::unix::net;
use std::{cell, io, rc, sync, time};

pub(crate) struct SharedState {
    pub(crate) error: cell::Cell<bool>,
    pub(crate) stream: net::UnixStream,
    pub(crate) impls: rc::Rc<Vec<Box<dyn implementation::server::ProtocolImplementations>>>,
}

impl SharedState {
    pub(crate) fn new(
        stream: net::UnixStream,
        impls: rc::Rc<Vec<Box<dyn implementation::server::ProtocolImplementations>>>,
    ) -> Self {
        Self {
            error: cell::Cell::new(false),
            stream,
            impls,
        }
    }

    pub(crate) fn send_message(&self, message: &dyn message::Message) {
        trace! { eprintln!("[hw] trace: [{} @ {:.3}] -> {}", self.stream.as_raw_fd(), steady_millis(), message.parse_data()) };

        let buf = message.data();
        let iov = [io::IoSlice::new(buf)];
        let cmsg = [sys::socket::ControlMessage::ScmRights(message.fds())];
        loop {
            match sys::socket::sendmsg::<()>(
                self.stream.as_raw_fd(),
                &iov,
                &cmsg,
                sys::socket::MsgFlags::empty(),
                None,
            ) {
                Ok(_) => break,
                Err(errno::Errno::EAGAIN) => {
                    let mut pfd = [poll::PollFd::new(
                        self.stream.as_fd(),
                        poll::PollFlags::POLLOUT | poll::PollFlags::POLLWRBAND,
                    )];
                    if let Err(e) = poll::poll(&mut pfd, poll::PollTimeout::NONE) {
                        log::error!(
                            "[{} @ {:.3}] poll error during send_message: {e}",
                            self.stream.as_raw_fd(),
                            steady_millis(),
                        );
                        break;
                    }
                }
                Err(_) => {
                    break;
                }
            }
        }
    }
}

/// Includes Rust bindings generated by `hyprwire-scanner` from `OUT_DIR`.
///
/// The macro expects a generated file named `<protocol>.rs` to already exist in the build output
/// directory, usually produced from `build.rs` with [`hyprwire_scanner::configure()`].
///
/// This is typically invoked inside a module so the generated client/server/spec items are grouped
/// under the protocol name.
///
/// # Example
///
/// ```no_run
/// mod test_protocol_v1 {
///     hyprwire::include_protocol!("test_protocol_v1");
/// }
///
/// let implementation = test_protocol_v1::client::TestProtocolV1Impl::default();
/// ```
#[macro_export]
macro_rules! include_protocol {
    ($name:expr) => {
        include!(concat!(env!("OUT_DIR"), "/", $name, ".rs"));
    };
}

/// Trait representing a hyprwire interface
#[doc(hidden)]
pub trait Object: Sized {
    /// The event enum for this interface
    type Event<'a>;

    const NAME: &str;

    fn from_object<D: Dispatch<Self>>(object: rc::Rc<dyn impl_object::RawObject>) -> Self;
}

#[doc(hidden)]
#[allow(missing_docs)]
pub trait Dispatch<I: crate::Object> {
    fn event(&mut self, object: &I, event: <I as crate::Object>::Event<'_>);
}

/// A helper macro which delegates a set of [`Dispatch`] implementations for proxies to a static handler.
///
/// # Usage
///
/// This macro is useful to implement [`Dispatch`] for interfaces where events are unimportant to
/// the current application and can be ignored.
///
/// # Example
///
/// ```
/// mod test_protocol_v1 {
///     hyprwire::include_protocol!("test_protocol_v1");
/// }
///
/// /// The application state
/// struct ExampleApp {
///     // ...
/// }
///
/// // Ignore all events for this interface:
/// hyprwire::delegate_noop!(ExampleApp: ignore test_protocol_v1::client::MyManagerV1Object);
///
/// // This interface should not emit events:
/// hyprwire::delegate_noop!(ExampleApp: test_protocol_v1::client::MyObjectV1Object);
/// ```
///
/// This last example will execute `unreachable!()` if the interface emits any events.
#[macro_export]
macro_rules! delegate_noop {
    ($(@< $( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+ >)? $dispatch_from:ty : $interface:ty) => {
        impl$(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $crate::Dispatch<$interface> for $dispatch_from {
            fn event(&mut self, _: &$interface, _: <$interface as $crate::Object>::Event<'_>) {
                unreachable!();
            }
        }
    };

    ($(@< $( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+ >)? $dispatch_from:ty : ignore $interface:ty) => {
        impl$(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $crate::Dispatch<$interface> for $dispatch_from {
            fn event(&mut self, _: &$interface, _: <$interface as $crate::Object>::Event<'_>) {
            }
        }
    };
}

#[doc(hidden)]
#[allow(missing_docs)]
pub struct DispatchData {
    pub object: *const dyn impl_object::RawObject,
}

#[doc(hidden)]
#[allow(missing_docs)]
pub struct DispatchContext<D: ?Sized> {
    pub object: *const dyn impl_object::RawObject,
    pub dispatch: *mut D,
}

static START: sync::OnceLock<time::Instant> = sync::OnceLock::new();

pub(crate) fn steady_millis() -> f64 {
    let start = START.get_or_init(time::Instant::now);
    start.elapsed().as_nanos() as f64 / 1_000_000.0
}