#![allow(clippy::let_unit_value)]
use std::os::raw::c_void;
use std::sync::Once;
use objc::declare::ClassDecl;
use objc::runtime::{Class, Object, Protocol, Sel};
use objc::{class, msg_send, sel, sel_impl};
use objc_foundation::{INSArray, NSArray, NSDictionary, NSObject, NSString};
use objc_id::{Id, ShareId, Shared};
use tracing::{debug, error};
use super::types::{id, CBCharacteristic, CBDescriptor, CBL2CAPChannel, CBPeripheral, CBService, NSError, NSInteger};
#[derive(Clone)]
pub enum CentralEvent {
Connect {
peripheral: ShareId<CBPeripheral>,
},
Disconnect {
peripheral: ShareId<CBPeripheral>,
error: Option<ShareId<NSError>>,
},
ConnectFailed {
peripheral: ShareId<CBPeripheral>,
error: Option<ShareId<NSError>>,
},
ConnectionEvent {
peripheral: ShareId<CBPeripheral>,
event: CBConnectionEvent,
},
Discovered {
peripheral: ShareId<CBPeripheral>,
adv_data: ShareId<NSDictionary<NSString, NSObject>>,
rssi: i16,
},
StateChanged,
}
impl std::fmt::Debug for CentralEvent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Connect { peripheral } => f.debug_struct("Connect").field("peripheral", peripheral).finish(),
Self::Disconnect { peripheral, error } => f
.debug_struct("Disconnect")
.field("peripheral", peripheral)
.field("error", error)
.finish(),
Self::ConnectFailed { peripheral, error } => f
.debug_struct("ConnectFailed")
.field("peripheral", peripheral)
.field("error", error)
.finish(),
Self::ConnectionEvent { peripheral, event } => f
.debug_struct("ConnectionEvent")
.field("peripheral", peripheral)
.field("event", event)
.finish(),
Self::Discovered { peripheral, rssi, .. } => f
.debug_struct("Discovered")
.field("peripheral", peripheral)
.field("rssi", rssi)
.finish(),
Self::StateChanged => write!(f, "StateChanged"),
}
}
}
#[derive(Debug, Clone)]
pub enum PeripheralEvent {
Connected,
Disconnected {
error: Option<ShareId<NSError>>,
},
DiscoveredServices {
error: Option<ShareId<NSError>>,
},
DiscoveredIncludedServices {
service: ShareId<CBService>,
error: Option<ShareId<NSError>>,
},
DiscoveredCharacteristics {
service: ShareId<CBService>,
error: Option<ShareId<NSError>>,
},
DiscoveredDescriptors {
characteristic: ShareId<CBCharacteristic>,
error: Option<ShareId<NSError>>,
},
CharacteristicValueUpdate {
characteristic: ShareId<CBCharacteristic>,
error: Option<ShareId<NSError>>,
},
DescriptorValueUpdate {
descriptor: ShareId<CBDescriptor>,
error: Option<ShareId<NSError>>,
},
CharacteristicValueWrite {
characteristic: ShareId<CBCharacteristic>,
error: Option<ShareId<NSError>>,
},
DescriptorValueWrite {
descriptor: ShareId<CBDescriptor>,
error: Option<ShareId<NSError>>,
},
ReadyToWrite,
NotificationStateUpdate {
characteristic: ShareId<CBCharacteristic>,
error: Option<ShareId<NSError>>,
},
ReadRssi {
rssi: i16,
error: Option<ShareId<NSError>>,
},
NameUpdate,
ServicesChanged {
invalidated_services: Vec<ShareId<CBService>>,
},
L2CAPChannelOpened {
channel: ShareId<CBL2CAPChannel>,
error: Option<ShareId<NSError>>,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CBConnectionEvent {
Disconnected,
Connected,
}
impl TryFrom<NSInteger> for CBConnectionEvent {
type Error = NSInteger;
fn try_from(value: NSInteger) -> Result<Self, Self::Error> {
match value {
0 => Ok(CBConnectionEvent::Disconnected),
1 => Ok(CBConnectionEvent::Connected),
_ => Err(value),
}
}
}
macro_rules! delegate_method {
(@value $param:ident: Object) => {
ShareId::from_ptr($param.cast())
};
(@value $param:ident: Option) => {
(!$param.is_null()).then(|| ShareId::from_ptr($param.cast()))
};
(@value $param:ident: i16) => {
{
let n: i16 = msg_send![$param, shortValue];
n
}
};
(@value $param:ident: Vec) => {
ShareId::from_ptr($param.cast::<NSArray<_, Shared>>()).to_shared_vec()
};
($name:ident < $event:ident > ( central $(, $param:ident: $ty:ident)*)) => {
extern "C" fn $name(this: &mut Object, _sel: Sel, _central: id, $($param: id),*) {
unsafe {
let ptr = (*this.get_ivar::<*mut c_void>("sender")).cast::<tokio::sync::broadcast::Sender<CentralEvent>>();
if !ptr.is_null() {
let event = CentralEvent::$event {
$($param: delegate_method!(@value $param: $ty)),*
};
debug!("CentralDelegate received {:?}", event);
let _res = (*ptr).send(event);
}
}
}
};
($name:ident < $event:ident > ( peripheral $(, $param:ident: $ty:ident)*)) => {
extern "C" fn $name(this: &mut Object, _sel: Sel, _peripheral: id, $($param: id),*) {
unsafe {
let ptr = (*this.get_ivar::<*mut c_void>("sender")).cast::<tokio::sync::broadcast::Sender<PeripheralEvent>>();
if !ptr.is_null() {
let event = PeripheralEvent::$event {
$($param: delegate_method!(@value $param: $ty)),*
};
debug!("PeripheralDelegate received {:?}", event);
let _res = (*ptr).send(event);
}
}
}
};
}
pub struct CentralDelegate {
_private: (),
}
unsafe impl objc::Message for CentralDelegate {}
impl objc_foundation::INSObject for CentralDelegate {
fn class() -> &'static ::objc::runtime::Class {
CentralDelegate::class()
}
}
impl PartialEq for CentralDelegate {
fn eq(&self, other: &Self) -> bool {
use objc_foundation::INSObject;
self.is_equal(other)
}
}
impl Eq for CentralDelegate {}
impl std::hash::Hash for CentralDelegate {
fn hash<H>(&self, state: &mut H)
where
H: std::hash::Hasher,
{
use objc_foundation::INSObject;
self.hash_code().hash(state);
}
}
impl ::std::fmt::Debug for CentralDelegate {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
use objc_foundation::{INSObject, INSString};
::std::fmt::Debug::fmt(self.description().as_str(), f)
}
}
pub struct PeripheralDelegate {
_private: (),
}
unsafe impl objc::Message for PeripheralDelegate {}
impl objc_foundation::INSObject for PeripheralDelegate {
fn class() -> &'static ::objc::runtime::Class {
PeripheralDelegate::class()
}
}
impl PartialEq for PeripheralDelegate {
fn eq(&self, other: &Self) -> bool {
use objc_foundation::INSObject;
self.is_equal(other)
}
}
impl Eq for PeripheralDelegate {}
impl std::hash::Hash for PeripheralDelegate {
fn hash<H>(&self, state: &mut H)
where
H: std::hash::Hasher,
{
use objc_foundation::INSObject;
self.hash_code().hash(state);
}
}
impl ::std::fmt::Debug for PeripheralDelegate {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
use objc_foundation::{INSObject, INSString};
::std::fmt::Debug::fmt(self.description().as_str(), f)
}
}
impl CentralDelegate {
pub fn with_sender(sender: tokio::sync::broadcast::Sender<CentralEvent>) -> Option<Id<CentralDelegate>> {
unsafe {
let obj: *mut Self = msg_send![Self::class(), alloc];
let obj: *mut Self = msg_send![obj, initWithSender: Box::into_raw(Box::new(sender)).cast::<c_void>()];
(!obj.is_null()).then(|| Id::from_retained_ptr(obj))
}
}
pub fn sender(&self) -> &tokio::sync::broadcast::Sender<CentralEvent> {
unsafe {
let sender: *const c_void = msg_send![self, sender];
assert!(!sender.is_null());
&*(sender.cast::<tokio::sync::broadcast::Sender<CentralEvent>>())
}
}
extern "C" fn init(this: &mut Object, _sel: Sel, sender: *mut c_void) -> id {
let this: &mut Object = unsafe { msg_send![super(this, class!(NSObject)), init] };
unsafe { this.set_ivar("sender", sender) };
this
}
extern "C" fn dealloc(this: &mut Object, _sel: Sel) {
unsafe {
let sender: *mut c_void = *this.get_ivar("sender");
this.set_ivar("sender", std::ptr::null_mut::<c_void>());
if !sender.is_null() {
std::mem::drop(Box::from_raw(
sender.cast::<tokio::sync::broadcast::Sender<CentralEvent>>(),
));
}
let _: () = msg_send![super(this, class!(NSObject)), dealloc];
};
}
extern "C" fn sender_getter(this: &mut Object, _sel: Sel) -> *const c_void {
unsafe { *this.get_ivar("sender") }
}
delegate_method!(did_fail_to_connect<ConnectFailed>(central, peripheral: Object, error: Option));
delegate_method!(did_update_state<StateChanged>(central));
extern "C" fn did_connect(this: &mut Object, _sel: Sel, _central: id, peripheral: id) {
unsafe {
let ptr = (*this.get_ivar::<*mut c_void>("sender")).cast::<tokio::sync::broadcast::Sender<CentralEvent>>();
if !ptr.is_null() {
let peripheral: Id<CBPeripheral, _> = ShareId::from_ptr(peripheral.cast());
if let Some(delegate) = peripheral.delegate() {
let _res = delegate.sender().send(PeripheralEvent::Connected);
}
let event = CentralEvent::Connect { peripheral };
debug!("CentralDelegate received {:?}", event);
let _res = (*ptr).send(event);
}
}
}
extern "C" fn did_disconnect(this: &mut Object, _sel: Sel, _central: id, peripheral: id, error: id) {
unsafe {
let ptr = (*this.get_ivar::<*mut c_void>("sender")).cast::<tokio::sync::broadcast::Sender<CentralEvent>>();
if !ptr.is_null() {
let peripheral: Id<CBPeripheral, _> = ShareId::from_ptr(peripheral.cast());
let error: Option<Id<NSError, _>> = (!error.is_null()).then(|| ShareId::from_ptr(error.cast()));
if let Some(delegate) = peripheral.delegate() {
let _res = delegate
.sender()
.send(PeripheralEvent::Disconnected { error: error.clone() });
}
let event = CentralEvent::Disconnect { peripheral, error };
debug!("CentralDelegate received {:?}", event);
let _res = (*ptr).send(event);
}
}
}
extern "C" fn did_discover_peripheral(
this: &mut Object,
_sel: Sel,
_central: id,
peripheral: id,
adv_data: id,
rssi: id,
) {
unsafe {
let ptr = (*this.get_ivar::<*mut c_void>("sender")).cast::<tokio::sync::broadcast::Sender<CentralEvent>>();
if !ptr.is_null() {
let rssi: i16 = msg_send![rssi, charValue];
let event = CentralEvent::Discovered {
peripheral: ShareId::from_ptr(peripheral.cast()),
adv_data: ShareId::from_ptr(adv_data.cast()),
rssi,
};
debug!("CentralDelegate received {:?}", event);
let _res = (*ptr).send(event);
}
}
}
extern "C" fn on_connection_event(
this: &mut Object,
_sel: Sel,
_central: id,
connection_event: NSInteger,
peripheral: id,
) {
unsafe {
let ptr = (*this.get_ivar::<*mut c_void>("sender")).cast::<tokio::sync::broadcast::Sender<CentralEvent>>();
if !ptr.is_null() {
match connection_event.try_into() {
Ok(event) => {
let event = CentralEvent::ConnectionEvent {
peripheral: ShareId::from_ptr(peripheral.cast()),
event,
};
debug!("CentralDelegate received {:?}", event);
let _res = (*ptr).send(event);
}
Err(err) => {
error!("Invalid value for CBConnectionEvent: {}", err);
}
}
}
}
}
fn class() -> &'static Class {
static DELEGATE_CLASS_INIT: Once = Once::new();
DELEGATE_CLASS_INIT.call_once(|| {
let mut cls = ClassDecl::new("BluestCentralDelegate", class!(NSObject)).unwrap();
cls.add_ivar::<*mut c_void>("sender");
cls.add_protocol(Protocol::get("CBCentralManagerDelegate").unwrap());
unsafe {
cls.add_method(
sel!(initWithSender:),
Self::init as extern "C" fn(&mut Object, Sel, *mut c_void) -> id,
);
cls.add_method(sel!(dealloc), Self::dealloc as extern "C" fn(&mut Object, Sel));
cls.add_method(
sel!(sender),
Self::sender_getter as extern "C" fn(&mut Object, Sel) -> *const c_void,
);
cls.add_method(
sel!(centralManager:didConnectPeripheral:),
Self::did_connect as extern "C" fn(&mut Object, Sel, id, id),
);
cls.add_method(
sel!(centralManager:didDisconnectPeripheral:error:),
Self::did_disconnect as extern "C" fn(&mut Object, Sel, id, id, id),
);
cls.add_method(
sel!(centralManager:didFailToConnectPeripheral:error:),
Self::did_fail_to_connect as extern "C" fn(&mut Object, Sel, id, id, id),
);
cls.add_method(
sel!(centralManager:connectionEventDidOccur:forPeripheral:),
Self::on_connection_event as extern "C" fn(&mut Object, Sel, id, NSInteger, id),
);
cls.add_method(
sel!(centralManager:didDiscoverPeripheral:advertisementData:RSSI:),
Self::did_discover_peripheral as extern "C" fn(&mut Object, Sel, id, id, id, id),
);
cls.add_method(
sel!(centralManagerDidUpdateState:),
Self::did_update_state as extern "C" fn(&mut Object, Sel, id),
);
}
cls.register();
});
class!(BluestCentralDelegate)
}
}
impl PeripheralDelegate {
pub fn with_sender(sender: tokio::sync::broadcast::Sender<PeripheralEvent>) -> Id<PeripheralDelegate> {
unsafe {
let obj: *mut Self = msg_send![Self::class(), alloc];
let obj: *mut Self = msg_send![obj, initWithSender: Box::into_raw(Box::new(sender)).cast::<c_void>()];
Id::from_retained_ptr(obj)
}
}
pub fn sender(&self) -> &tokio::sync::broadcast::Sender<PeripheralEvent> {
unsafe {
let sender: *const c_void = msg_send![self, sender];
assert!(!sender.is_null());
&*(sender.cast::<tokio::sync::broadcast::Sender<PeripheralEvent>>())
}
}
extern "C" fn init(this: &mut Object, _sel: Sel, sender: *mut c_void) -> id {
let this: &mut Object = unsafe { msg_send![super(this, class!(NSObject)), init] };
unsafe { this.set_ivar("sender", sender) };
this
}
extern "C" fn dealloc(this: &mut Object, _sel: Sel) {
unsafe {
let sender: *mut c_void = *this.get_ivar("sender");
this.set_ivar("sender", std::ptr::null_mut::<c_void>());
if !sender.is_null() {
std::mem::drop(Box::from_raw(
sender.cast::<tokio::sync::broadcast::Sender<PeripheralEvent>>(),
));
}
let _: () = msg_send![super(this, class!(NSObject)), dealloc];
};
}
extern "C" fn sender_getter(this: &mut Object, _sel: Sel) -> *const c_void {
unsafe { *this.get_ivar("sender") }
}
delegate_method!(did_discover_services<DiscoveredServices>(peripheral, error: Option));
delegate_method!(did_discover_included_services<DiscoveredIncludedServices>(peripheral, service: Object, error: Option));
delegate_method!(did_discover_characteristics<DiscoveredCharacteristics>(peripheral, service: Object, error: Option));
delegate_method!(did_discover_descriptors<DiscoveredDescriptors>(peripheral, characteristic: Object, error: Option));
delegate_method!(did_update_value_for_characteristic<CharacteristicValueUpdate>(peripheral, characteristic: Object, error: Option));
delegate_method!(did_update_value_for_descriptor<DescriptorValueUpdate>(peripheral, descriptor: Object, error: Option));
delegate_method!(did_write_value_for_characteristic<CharacteristicValueWrite>(peripheral, characteristic: Object, error: Option));
delegate_method!(did_write_value_for_descriptor<DescriptorValueWrite>(peripheral, descriptor: Object, error: Option));
delegate_method!(is_ready_to_write_without_response<ReadyToWrite>(peripheral));
delegate_method!(did_update_notification_state<NotificationStateUpdate>(peripheral, characteristic: Object, error: Option));
delegate_method!(did_read_rssi<ReadRssi>(peripheral, rssi: i16, error: Option));
delegate_method!(did_update_name<NameUpdate>(peripheral));
delegate_method!(did_modify_services<ServicesChanged>(peripheral, invalidated_services: Vec));
delegate_method!(did_open_l2cap_channel<L2CAPChannelOpened>(peripheral, channel: Object, error: Option));
fn class() -> &'static Class {
static DELEGATE_CLASS_INIT: Once = Once::new();
DELEGATE_CLASS_INIT.call_once(|| {
let mut cls = ClassDecl::new("BluestPeripheralDelegate", class!(NSObject)).unwrap();
cls.add_ivar::<*mut c_void>("sender");
cls.add_protocol(Protocol::get("CBPeripheralDelegate").unwrap());
unsafe {
cls.add_method(
sel!(initWithSender:),
Self::init as extern "C" fn(&mut Object, Sel, *mut c_void) -> id,
);
cls.add_method(sel!(dealloc), Self::dealloc as extern "C" fn(&mut Object, Sel));
cls.add_method(
sel!(sender),
Self::sender_getter as extern "C" fn(&mut Object, Sel) -> *const c_void,
);
cls.add_method(
sel!(peripheral:didDiscoverServices:),
Self::did_discover_services as extern "C" fn(&mut Object, Sel, id, id),
);
cls.add_method(
sel!(peripheral:didDiscoverIncludedServicesForService:error:),
Self::did_discover_included_services as extern "C" fn(&mut Object, Sel, id, id, id),
);
cls.add_method(
sel!(peripheral:didDiscoverCharacteristicsForService:error:),
Self::did_discover_characteristics as extern "C" fn(&mut Object, Sel, id, id, id),
);
cls.add_method(
sel!(peripheral:didDiscoverDescriptorsForCharacteristic:error:),
Self::did_discover_descriptors as extern "C" fn(&mut Object, Sel, id, id, id),
);
cls.add_method(
sel!(peripheral:didUpdateValueForCharacteristic:error:),
Self::did_update_value_for_characteristic as extern "C" fn(&mut Object, Sel, id, id, id),
);
cls.add_method(
sel!(peripheral:didUpdateValueForDescriptor:error:),
Self::did_update_value_for_descriptor as extern "C" fn(&mut Object, Sel, id, id, id),
);
cls.add_method(
sel!(peripheral:didWriteValueForCharacteristic:error:),
Self::did_write_value_for_characteristic as extern "C" fn(&mut Object, Sel, id, id, id),
);
cls.add_method(
sel!(peripheral:didWriteValueForDescriptor:error:),
Self::did_write_value_for_descriptor as extern "C" fn(&mut Object, Sel, id, id, id),
);
cls.add_method(
sel!(peripheralIsReadyToSendWriteWithoutResponse:),
Self::is_ready_to_write_without_response as extern "C" fn(&mut Object, Sel, id),
);
cls.add_method(
sel!(peripheral:didUpdateNotificationStateForCharacteristic:error:),
Self::did_update_notification_state as extern "C" fn(&mut Object, Sel, id, id, id),
);
cls.add_method(
sel!(peripheral:didReadRSSI:error:),
Self::did_read_rssi as extern "C" fn(&mut Object, Sel, id, id, id),
);
cls.add_method(
sel!(peripheralDidUpdateName:),
Self::did_update_name as extern "C" fn(&mut Object, Sel, id),
);
cls.add_method(
sel!(peripheral:didModifyServices:),
Self::did_modify_services as extern "C" fn(&mut Object, Sel, id, id),
);
cls.add_method(
sel!(peripheral:didOpenL2CAPChannel:error:),
Self::did_open_l2cap_channel as extern "C" fn(&mut Object, Sel, id, id, id),
);
}
cls.register();
});
class!(BluestPeripheralDelegate)
}
}