winio-ui-android 0.1.0

Android backend for winio.
use std::{
    io,
    ops::Deref,
    os::fd::{AsFd, AsRawFd, BorrowedFd, OwnedFd},
    sync::{Arc, Mutex},
    time::Duration,
};

use android_activity::ndk::looper::{FdEvent, ForeignLooper};
use compio::{compat::Adapter, runtime::Runtime};
use rustix::{
    fs::Timespec,
    time::{
        Itimerspec, TimerfdClockId, TimerfdFlags, TimerfdTimerFlags, timerfd_create,
        timerfd_settime,
    },
};

pub struct CompioAdapter {
    runtime: Runtime,
    timer: OwnedFd,
    looper: ForeignLooper,
}

impl CompioAdapter {
    fn as_fd(&self) -> BorrowedFd<'_> {
        unsafe { BorrowedFd::borrow_raw(self.runtime.as_raw_fd()) }
    }
}

impl Deref for CompioAdapter {
    type Target = Runtime;

    fn deref(&self) -> &Self::Target {
        &self.runtime
    }
}

impl Adapter for CompioAdapter {
    fn new(runtime: Runtime) -> io::Result<Self> {
        let looper = ForeignLooper::for_thread()
            .ok_or_else(|| io::Error::other("looper not initialized"))?;
        let timer = timerfd_create(
            TimerfdClockId::Monotonic,
            TimerfdFlags::CLOEXEC | TimerfdFlags::NONBLOCK,
        )?;
        Ok(Self {
            runtime,
            timer,
            looper,
        })
    }

    async fn wait(&self, timeout: Option<Duration>) -> io::Result<()> {
        if timeout == Some(Duration::ZERO) {
            return Ok(());
        }
        let (tx, rx) = oneshot::async_channel();
        let tx = Arc::new(Mutex::new(Some(tx)));
        if let Some(timeout) = timeout {
            let new_value = Itimerspec {
                it_interval: Timespec::default(),
                it_value: Timespec::try_from(timeout)
                    .map_err(io::Error::other)?
                    .max(Timespec {
                        tv_sec: 0,
                        tv_nsec: 1,
                    }),
            };
            timerfd_settime(&self.timer, TimerfdTimerFlags::empty(), &new_value)?;
            let tx = tx.clone();
            self.looper
                .add_fd_with_callback(self.timer.as_fd(), FdEvent::INPUT, move |_, _| {
                    if let Some(tx) = tx.lock().unwrap().take() {
                        tx.send(false).ok();
                    }
                    false
                })
                .map_err(io::Error::other)?;
        }
        self.looper
            .add_fd_with_callback(self.as_fd(), FdEvent::INPUT, move |_, _| {
                if let Some(tx) = tx.lock().unwrap().take() {
                    tx.send(true).ok();
                }
                false
            })
            .map_err(io::Error::other)?;
        if !rx.await.map_err(io::Error::other)? {
            return Err(io::ErrorKind::TimedOut.into());
        }
        Ok(())
    }

    fn clear(&self) -> io::Result<()> {
        let new_value = Itimerspec {
            it_interval: Timespec::default(),
            it_value: Timespec::default(),
        };
        timerfd_settime(&self.timer, TimerfdTimerFlags::empty(), &new_value)?;
        self.looper
            .remove_fd(self.timer.as_fd())
            .map_err(io::Error::other)?;
        self.looper
            .remove_fd(self.as_fd())
            .map_err(io::Error::other)?;
        Ok(())
    }
}