libopenlipc-sys 0.1.3

Wrapper around liblipc to interact with Kindle dbus-based LIPC events
Documentation
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(dead_code)]
include!("./bindings.rs");

use std::ffi::CStr;
use std::ffi::CString;
use std::os::raw::{c_char, c_int, c_void};

pub struct rLIPC {
    conn: *mut LIPC,
}

macro_rules! code_to_result {
    ($value:expr) => {
        if $value == LIPCcode_LIPC_OK {
            Ok(())
        } else {
            Err(format!(
                "Failed to subscribe: {}",
                rLIPC::code_to_string($value)
            ))
        }
    };
}

#[derive(Debug)]
pub enum LipcResult {
    NUM(i32),
    STR(String),
}

impl rLIPC {
    /// Returns a new LIPC client if a connection was successful
    /// Connects to the LIPC bus with no name.
    pub fn new() -> Result<Self, String> {
        let lipc;
        unsafe {
            lipc = LipcOpenNoName();
        }
        if lipc == (std::ptr::null_mut() as *mut c_void) {
            return Err(String::from("Failed to open a connection!"));
        }
        Ok(Self { conn: lipc })
    }

    /// Register a callback for events broadcasted by `service`. Optionally,
    /// you can filter to a single event by providing `name`.
    ///
    /// For callback, we pass (source, name, optional int param, optional str param).
    /// an example callback payload would be
    /// "com.lab126.appmgrd", "appActivating", Some(1), Some("com.lab126.booklet.reader")
    ///
    /// # Examples
    ///
    /// ```
    /// use libopenlipc_sys::rLIPC;
    /// let r = rLIPC::new().unwrap();
    /// r.subscribe("com.lab126.powerd", Some("battLevelChanged"), |_, _, _, _| ());
    /// // You will only get updates about battLevel in the callback
    /// // battLevelChanged sends <int param> with the new battery value
    /// ```
    ///
    /// ```
    /// use libopenlipc_sys::rLIPC;
    /// let r = rLIPC::new().unwrap();
    /// r.subscribe("com.lab126.powerd", None, |_, _, _, _| ());
    /// // You will get updates all power related events (screen on, off, etc)
    /// ```
    pub fn subscribe<F>(&self, service: &str, name: Option<&str>, callback: F) -> Result<(), String>
    where
        F: FnMut(&str, &str, Option<LipcResult>) + Send,
    {
        let _service = CString::new(service).unwrap();

        let owned;
        let c_name = match name {
            None => std::ptr::null(),
            Some(_name) => {
                owned = CString::new(_name).unwrap();
                owned.as_ptr()
            }
        };

        let boxed_fn: Box<dyn FnMut(&str, &str, Option<LipcResult>) + Send> =
            Box::new(callback) as _;
        let double_box = Box::new(boxed_fn);
        let ptr = Box::into_raw(double_box);
        /*
         * You can't pass a fn directly to C -- you can however pass a `Box::into_raw`
         * This box however is of dynamic size and loses metadata -- so it's not easy to free later
         * The other box (boxed_fn) is a fat pointer (which we can't pass to C) but it keeps
         * metadata
         * So we pass a thin pointer (into_raw) to a fat pointer (<dyn FnMut..>) to C
         * then we have to undo this in the callback
         */

        let result;
        unsafe {
            /* We wait to cast to .as_ptr() here
             * For a pointer to be valid, the thing it points to must still be around.
             * For a value to exist past the expression it's introduced in, it must be bound to a variable.
             * When the variable disappears, the value does too.
             * We must store the CString for _service and c_name, then independently get pointers
             * *to* them
             */
            result = code_to_result!(LipcSubscribeExt(
                self.conn,
                _service.as_ptr(),
                c_name,
                Some(ugly_callback),
                ptr as *mut c_void,
            ));
        }
        result
    }

    /// Get the current value of a string property
    /// ```
    /// use libopenlipc_sys::rLIPC;
    /// let r = rLIPC::new().unwrap();
    /// let reader_status = r.get_str_prop("com.lab126.acxreaderplugin", "allReaderData").unwrap();
    /// // reader_status would be a string containing JSON
    /// ```
    pub fn get_str_prop(&self, service: &str, prop: &str) -> Result<String, String> {
        let mut handle: *mut c_char = std::ptr::null_mut();
        let handle_ptr: *mut *mut c_char = &mut handle;

        let service = CString::new(service).unwrap();
        let prop = CString::new(prop).unwrap();
        unsafe {
            code_to_result!(LipcGetStringProperty(
                self.conn,
                service.as_ptr(),
                prop.as_ptr(),
                handle_ptr
            ))?;
        };

        let val;
        unsafe {
            val = CStr::from_ptr(handle).to_str().unwrap().into();
            // Made a copy, we can now free() the string
            LipcFreeString(handle);
        }
        Ok(val)
    }

    /// Get the current value of an int property
    /// ```
    /// use libopenlipc_sys::rLIPC;
    /// let r = rLIPC::new().unwrap();
    /// let reader_status = r.get_int_prop("com.lab126.powerd", "battLevel").unwrap();
    /// // reader_status will contain the battery charge % (ie: 75).
    /// ```
    pub fn get_int_prop(&self, service: &str, prop: &str) -> Result<i32, String> {
        let mut val: c_int = 0;
        let service = CString::new(service).unwrap();
        let prop = CString::new(prop).unwrap();
        unsafe {
            code_to_result!(LipcGetIntProperty(
                self.conn,
                service.as_ptr(),
                prop.as_ptr(),
                &mut val
            ))?;
        };

        Ok(val)
    }

    fn code_to_string(code: u32) -> String {
        unsafe {
            let cstr = CStr::from_ptr(LipcGetErrorString(code));
            return String::from(cstr.to_str().unwrap());
        }
    }
}

unsafe extern "C" fn ugly_callback(
    _: *mut LIPC,
    name: *const c_char,
    event: *mut LIPCevent,
    data: *mut c_void,
) -> LIPCcode {
    // Can't unwrap in this function
    let source = LipcGetEventSource(event);
    let _name = CStr::from_ptr(name).to_str().unwrap();
    let _source = CStr::from_ptr(source).to_str().unwrap();

    let _int_param: Option<i32>;
    let _str_param: Option<String>;

    {
        let mut int_param: c_int = 0;
        _int_param = match ReturnCodes::from_u32(LipcGetIntParam(event, &mut int_param)).unwrap() {
            ReturnCodes::OK => Some(int_param),
            ReturnCodes::ERROR_NO_SUCH_PARAM => None,
            e => {
                println!(
                    "Error getting int param: {}",
                    rLIPC::code_to_string(e as u32)
                );
                None
            }
        }
    }

    {
        let mut handle: *mut c_char = std::ptr::null_mut();
        let handle_ptr: *mut *mut c_char = &mut handle;
        _str_param = match ReturnCodes::from_u32(LipcGetStringParam(event, handle_ptr)).unwrap() {
            ReturnCodes::OK => {
                let val = CStr::from_ptr(handle).to_str().unwrap().into();
                Some(val)
            }
            ReturnCodes::ERROR_NO_SUCH_PARAM => None,
            e => {
                println!(
                    "Error getting string param: {}",
                    rLIPC::code_to_string(e as u32)
                );
                None
            }
        }
    }

    let f = data as *mut Box<dyn FnMut(&str, &str, Option<LipcResult>) + Send>;
    let _res = if let Some(val) = _int_param {
        Some(LipcResult::NUM(val))
    } else {
        _str_param.map(LipcResult::STR)
    };

    (*f)(_source, _name, _res);
    0
}

impl Drop for rLIPC {
    fn drop(&mut self) {
        unsafe {
            LipcClose(self.conn);
        }
        println!("Disconnected");
    }
}

unsafe impl Sync for rLIPC {}