hyprwire 0.2.7

A fast and consistent wire protocol for IPC
Documentation
mod test_protocol_v1 {
    hyprwire::include_protocol!("test_protocol_v1");
    pub use spec::*;
}

use hyprwire::client;
use hyprwire::implementation::client::ProtocolImplementations;
use hyprwire::implementation::types::ProtocolSpec;
use hyprwire::server;
use nix::libc;
use std::io;
use std::io::Read;
use std::os::fd;
use std::os::fd::{AsRawFd, FromRawFd};
use std::{fs, path};

const TEST_PROTOCOL_VERSION: u32 = 1;

#[derive(Default)]
struct ServerApp {
    quit: bool,
    manager: Option<test_protocol_v1::server::MyManagerV1Object>,
    objects: Vec<test_protocol_v1::server::MyObjectV1Object>,
}

impl hyprwire::Dispatch<test_protocol_v1::server::MyManagerV1Object> for ServerApp {
    fn event(
        &mut self,
        object: &test_protocol_v1::server::MyManagerV1Object,
        event: <test_protocol_v1::server::MyManagerV1Object as hyprwire::Object>::Event<'_>,
    ) {
        match event {
            test_protocol_v1::server::MyManagerV1Event::SendMessage { message } => {
                println!("Recvd message: {}", message);
            }
            test_protocol_v1::server::MyManagerV1Event::SendMessageArrayFd { message } => {
                println!("Received {} fds", message.len());

                for fd in message {
                    let raw_fd = fd.as_raw_fd();
                    let mut file = fs::File::from(fd);
                    let mut buf = [0u8; 64];
                    let n = file.read(&mut buf).unwrap_or(0);
                    let data = String::from_utf8_lossy(&buf[..n]);
                    println!("fd {} with data: {}", raw_fd, data);
                }
            }
            test_protocol_v1::server::MyManagerV1Event::SendMessageFd { message } => {
                let raw_fd = message.as_raw_fd();
                let mut file = fs::File::from(message);
                let mut buf = [0u8; 64];
                let n = file.read(&mut buf).unwrap_or(0);
                let data = String::from_utf8_lossy(&buf[..n]);
                println!("Recvd fd {} with data: {}", raw_fd, data);
            }
            test_protocol_v1::server::MyManagerV1Event::SendMessageArray { message } => {
                let data: Vec<&str> = message.iter().map(|s| s.as_str()).collect();
                println!("Got array message: \"{}\"", data.join(", "));
            }
            test_protocol_v1::server::MyManagerV1Event::SendMessageArrayUint { message } => {
                let data: Vec<String> = message.iter().map(u32::to_string).collect();
                println!("Got uint array message: \"{}\"", data.join(", "));
            }
            test_protocol_v1::server::MyManagerV1Event::MakeObject { seq } => {
                let object = object
                    .make_object::<Self>(seq)
                    .expect("failed to create object");
                object.send_send_message("Hello object");
                self.objects.push(object);
            }
        }
    }
}

impl hyprwire::Dispatch<test_protocol_v1::server::MyObjectV1Object> for ServerApp {
    fn event(
        &mut self,
        object: &test_protocol_v1::server::MyObjectV1Object,
        event: <test_protocol_v1::server::MyObjectV1Object as hyprwire::Object>::Event<'_>,
    ) {
        match event {
            test_protocol_v1::server::MyObjectV1Event::SendMessage { message } => {
                println!("Object says hello: {}", message);
            }
            test_protocol_v1::server::MyObjectV1Event::SendEnum { message } => {
                println!("Object sent enum: {:?}", message);
                println!("Erroring out the client!");

                self.quit = true;
                object.error(
                    test_protocol_v1::MyErrorEnum::ErrorImportant as u32,
                    "Important error occurred!",
                );
            }
            test_protocol_v1::server::MyObjectV1Event::MakeObject { seq } => {
                let object = object
                    .make_object::<Self>(seq)
                    .expect("failed to create nested object");
                object.send_send_message("Hello object");
                self.objects.push(object);
            }
            test_protocol_v1::server::MyObjectV1Event::Destroy => {}
        }
    }
}

impl test_protocol_v1::server::TestProtocolV1Handler for ServerApp {
    fn bind(&mut self, object: test_protocol_v1::server::MyManagerV1Object) {
        println!("Object bound XD");
        object.send_send_message("Hello manager");
        self.manager = Some(object);
    }
}

#[derive(Default)]
struct ClientApp {
    quit: bool,
    object: Option<test_protocol_v1::client::MyObjectV1Object>,
    object2: Option<test_protocol_v1::client::MyObjectV1Object>,
}

impl hyprwire::Dispatch<test_protocol_v1::client::MyManagerV1Object> for ClientApp {
    fn event(
        &mut self,
        object: &test_protocol_v1::client::MyManagerV1Object,
        event: <test_protocol_v1::client::MyManagerV1Object as hyprwire::Object>::Event<'_>,
    ) {
        let _ = object;
        match event {
            test_protocol_v1::client::MyManagerV1Event::SendMessage { message } => {
                println!("Server says {}", message);
            }
            test_protocol_v1::client::MyManagerV1Event::RecvMessageArrayUint { message } => {
                println!("Server sent uint array {:?}", message);
            }
        }
    }
}

impl hyprwire::Dispatch<test_protocol_v1::client::MyObjectV1Object> for ClientApp {
    fn event(
        &mut self,
        object: &test_protocol_v1::client::MyObjectV1Object,
        event: <test_protocol_v1::client::MyObjectV1Object as hyprwire::Object>::Event<'_>,
    ) {
        let test_protocol_v1::client::MyObjectV1Event::SendMessage { message } = event;
        println!("Server says on object {}", message);

        if self.object2.as_ref() == Some(object) {
            if let Some(object) = self.object.as_ref() {
                object.send_send_enum(test_protocol_v1::MyEnum::World);
            }
            self.quit = true;
        }
    }
}

fn poll_readable(fd: fd::RawFd, timeout_ms: i32) -> io::Result<bool> {
    let mut pfd = libc::pollfd {
        fd,
        events: libc::POLLIN,
        revents: 0,
    };

    loop {
        let rc = unsafe { libc::poll(&mut pfd, 1, timeout_ms) };
        if rc >= 0 {
            return Ok(rc > 0 && (pfd.revents & libc::POLLIN) != 0);
        }

        let err = io::Error::last_os_error();
        if matches!(err.raw_os_error(), Some(libc::EINTR | libc::EAGAIN)) {
            continue;
        }
        return Err(err);
    }
}

fn make_pipe_with_message(message: &[u8]) -> io::Result<fd::OwnedFd> {
    let mut pipe_fds = [0; 2];
    let rc = unsafe { libc::pipe(pipe_fds.as_mut_ptr()) };
    if rc != 0 {
        return Err(io::Error::last_os_error());
    }

    let wrote = unsafe { libc::write(pipe_fds[1], message.as_ptr().cast(), message.len()) };
    if wrote < 0 {
        let err = io::Error::last_os_error();
        unsafe {
            libc::close(pipe_fds[0]);
            libc::close(pipe_fds[1]);
        }
        return Err(err);
    }

    let [read_fd, write_fd] = pipe_fds;
    let read_fd = unsafe { fd::OwnedFd::from_raw_fd(read_fd) };
    let write_fd = unsafe { fd::OwnedFd::from_raw_fd(write_fd) };
    drop(write_fd);

    Ok(read_fd)
}

fn server_main(client_fd: fd::RawFd) -> io::Result<()> {
    let mut socket = server::Server::open::<&path::Path>(None)?;
    let mut app = ServerApp::default();
    let implementation = test_protocol_v1::server::TestProtocolV1Impl::new(1, &mut app);
    socket.add_implementation(implementation);
    if !poll_readable(client_fd, 1000)? {
        return Err(io::Error::new(
            io::ErrorKind::TimedOut,
            "failed to wait for client hello",
        ));
    }

    socket.add_client(unsafe { fd::OwnedFd::from_raw_fd(client_fd) });

    while !app.quit {
        if !poll_readable(socket.extract_loop_fd()?.as_raw_fd(), -1)? {
            continue;
        }

        if !socket.dispatch_events(&mut app, false) {
            break;
        }
    }

    Ok(())
}

fn client_main(server_fd: fd::OwnedFd) -> io::Result<()> {
    let mut socket = client::Client::from_fd(server_fd)?;
    let mut app = ClientApp::default();
    let implementation = test_protocol_v1::client::TestProtocolV1Impl::default();
    socket.add_implementation(implementation.clone());
    socket.wait_for_handshake(&mut app)?;

    println!("OK!");

    let spec = socket
        .get_spec(implementation.protocol().spec_name())
        .ok_or_else(|| io::Error::other("test protocol unsupported"))?;

    println!(
        "test protocol supported at version {}. Binding.",
        spec.spec_ver()
    );

    let manager = socket
        .bind::<test_protocol_v1::client::MyManagerV1Object, ClientApp>(
            implementation.protocol(),
            TEST_PROTOCOL_VERSION,
            &mut app,
        )
        .map_err(io::Error::other)?;

    println!("Bound!");

    let pipe_fd = make_pipe_with_message(b"pipe!")?;
    println!("Will send fd {}", pipe_fd.as_raw_fd());

    manager.send_send_message("Hello!");
    manager.send_send_message_fd(&pipe_fd);
    manager.send_send_message_array(&["Hello", "via", "array!"]);
    manager.send_send_message_array::<&str>(&[]);
    manager.send_send_message_array_uint(&[69, 420, 2137]);

    let object = manager
        .send_make_object::<ClientApp>()
        .ok_or_else(|| io::Error::other("failed to create first object"))?;
    let object2 = object
        .send_make_object::<ClientApp>()
        .ok_or_else(|| io::Error::other("failed to create second object"))?;

    app.object = Some(object.clone());
    app.object2 = Some(object2.clone());

    object.send_send_message("Hello from object");
    object2.send_send_message("Hello from object2");

    while !app.quit {
        socket.dispatch_events(&mut app, true)?;
    }

    let _ = socket.roundtrip(&mut app);

    Ok(())
}

fn socketpair() -> io::Result<[fd::RawFd; 2]> {
    let mut fds = [0; 2];
    let rc = unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) };
    if rc != 0 {
        return Err(io::Error::last_os_error());
    }

    Ok(fds)
}

fn main() {
    env_logger::Builder::new()
        .filter(None, log::LevelFilter::Trace)
        .init();

    let [server_fd, client_fd] = socketpair().expect("failed to create socketpair");

    let pid = unsafe { libc::fork() };
    if pid < 0 {
        unsafe {
            libc::close(server_fd);
            libc::close(client_fd);
        }
        panic!("failed to fork");
    }

    if pid == 0 {
        unsafe {
            libc::close(server_fd);
        }

        if let Err(err) = client_main(unsafe { fd::OwnedFd::from_raw_fd(client_fd) }) {
            eprintln!("client error: {err}");
            unsafe { libc::_exit(1) };
        }

        unsafe { libc::_exit(0) };
    }

    unsafe {
        libc::close(client_fd);
    }

    if let Err(err) = server_main(server_fd) {
        eprintln!("server error: {err}");
    }

    unsafe {
        let mut status = 0;
        libc::waitpid(pid, &mut status, 0);
    }
}