bluetooth_core 0.0.1

Cross-platform Bluetooth LE (btleplug wrapper) with a small C ABI.
Documentation
//! Cross-platform Bluetooth permission status and request.

/// Authorization is unknown / not yet requested.
pub const STATUS_NOT_DETERMINED: i32 = 0;
/// Authorization granted.
pub const STATUS_GRANTED: i32 = 1;
/// Authorization denied by the user.
pub const STATUS_DENIED: i32 = 2;
/// Authorization restricted (e.g. parental controls / MDM).
pub const STATUS_RESTRICTED: i32 = 3;
/// No app-level Bluetooth permission on this platform.
#[allow(dead_code)]
pub const STATUS_UNSUPPORTED: i32 = 4;

/// Returns the current Bluetooth authorization status.
pub fn status() -> i32 {
    platform::status()
}

/// Requests Bluetooth authorization and returns the resulting status.
///
/// Calling after a denial is safe but won't re-prompt on Apple.
pub fn request() -> i32 {
    platform::request()
}

#[cfg(any(target_os = "macos", target_os = "ios"))]
mod platform {
    use std::sync::OnceLock;

    use objc2::AllocAnyThread;
    use objc2_core_bluetooth::{CBCentralManager, CBManager, CBManagerAuthorization};

    use super::*;

    fn map(auth: CBManagerAuthorization) -> i32 {
        if auth == CBManagerAuthorization::AllowedAlways {
            STATUS_GRANTED
        } else if auth == CBManagerAuthorization::Denied {
            STATUS_DENIED
        } else if auth == CBManagerAuthorization::Restricted {
            STATUS_RESTRICTED
        } else {
            STATUS_NOT_DETERMINED
        }
    }

    pub fn status() -> i32 {
        map(unsafe { CBManager::authorization_class() })
    }

    pub fn request() -> i32 {
        // Instantiating CBCentralManager triggers the system permission prompt.
        static MANAGER: OnceLock<SendManager> = OnceLock::new();
        MANAGER.get_or_init(|| {
            let mgr = unsafe {
                CBCentralManager::initWithDelegate_queue(
                    CBCentralManager::alloc(),
                    None,
                    None,
                )
            };
            SendManager(mgr)
        });
        status()
    }

    // Not Send, but only constructed once and never accessed afterward.
    struct SendManager(#[allow(dead_code)] objc2::rc::Retained<CBCentralManager>);
    unsafe impl Send for SendManager {}
    unsafe impl Sync for SendManager {}
}

#[cfg(not(any(target_os = "macos", target_os = "ios")))]
mod platform {
    use super::*;

    pub fn status() -> i32 {
        STATUS_UNSUPPORTED
    }

    pub fn request() -> i32 {
        STATUS_UNSUPPORTED
    }
}