hyprwire 0.3.0

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;

/// Core protocol types, message encoding, and trait definitions re-exported
/// from `hyprwire-core`.
pub use hyprwire_core as core;

/// Client-side APIs for connecting to a Hyprwire server and dispatching
/// generated protocol events.
pub mod client;
/// Error types returned by the public Hyprwire API.
pub mod error;
pub use error::Error;
/// A `Result` type alias that uses [`Error`] as the error type.
pub type Result<T> = std::result::Result<T, Error>;
pub(crate) mod helpers;
pub use helpers::reset_trace_cache;
/// 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;

#[cfg(feature = "log")]
#[allow(unused_imports)]
use log::{debug as log_debug, error as log_error, info as log_info, warn as log_warn};

#[cfg(not(feature = "log"))]
#[allow(unused_imports)]
use std::{
    eprintln as log_debug, eprintln as log_error, eprintln as log_info, eprintln as log_warn,
};

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

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

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

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

        let buf = message.data();
        let iov = [io::IoSlice::new(buf)];
        let fds = message.fds();
        let borrowed: Vec<rustix::fd::BorrowedFd<'_>> = fds
            .iter()
            .map(|&fd| unsafe { rustix::fd::BorrowedFd::borrow_raw(fd) })
            .collect();
        let mut space =
            vec![std::mem::MaybeUninit::<u8>::uninit(); rustix::cmsg_space!(ScmRights(fds.len()))];
        let mut ancillary = rustix::net::SendAncillaryBuffer::new(&mut space);
        ancillary.push(rustix::net::SendAncillaryMessage::ScmRights(&borrowed));

        loop {
            match rustix::net::sendmsg(
                &self.stream,
                &iov,
                &mut ancillary,
                rustix::net::SendFlags::empty(),
            ) {
                Ok(_) => break,
                Err(e) if e == rustix::io::Errno::AGAIN => {
                    let mut pfd = [rustix::event::PollFd::new(
                        &self.stream,
                        rustix::event::PollFlags::OUT,
                    )];
                    if let Err(e) = rustix::event::poll(&mut pfd, None) {
                        crate::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 mut client = hyprwire::client::Client::connect("/tmp/test-hw.sock").unwrap();
/// client.add_implementation::<test_protocol_v1::client::TestProtocolV1Impl>();
/// ```
#[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> + 'static>(object: rc::Rc<dyn impl_object::Object>) -> Self;
}

/// Trait for receiving events from a Hyprwire interface.
///
/// Implement this on your application state type for every interface object
/// you want to receive events from. The [`delegate_noop!`] macro can be used
/// to opt out of events you do not care about.
///
/// # Example
///
/// ```ignore
/// mod my_protocol_v1 {
///     hyprwire::include_protocol!("my_protocol_v1");
///     pub use client::*;
/// }
/// use my_protocol_v1::my_manager_v1;
///
/// struct App;
///
/// impl hyprwire::Dispatch<my_manager_v1::MyManagerV1> for App {
///     fn event(
///         &mut self,
///         _object: &my_manager_v1::MyManagerV1,
///         event: <my_manager_v1::MyManagerV1 as hyprwire::Object>::Event<'_>,
///     ) {
///         // handle events here
///     }
/// }
/// ```
pub trait Dispatch<O: crate::Object> {
    /// Called when the interface emits an event.
    fn event(&mut self, object: &O, event: <O 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:
/// use test_protocol_v1::client::my_manager_v1;
/// hyprwire::delegate_noop!(ExampleApp: ignore my_manager_v1::MyManagerV1);
///
/// // This interface should not emit events:
/// use test_protocol_v1::client::my_object_v1;
/// hyprwire::delegate_noop!(ExampleApp: my_object_v1::MyObjectV1);
/// ```
///
/// 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<'_>) {
            }
        }
    };
}

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
}