#![allow(clippy::missing_safety_doc)]
use std::ffi::{CStr, CString};
use std::os::raw::{c_char, c_int, c_void};
use crate::{CaptureKind, Event, MeetingListener, Permission, PermissionGranted};
#[inline]
unsafe fn as_listener<'a>(h: *const c_void) -> &'a MeetingListener {
assert!(!h.is_null(), "side_huddle: null handle");
&*(h as *const MeetingListener)
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub enum SHEventKind {
PermissionStatus = 0,
PermissionsGranted = 1,
MeetingDetected = 2,
MeetingUpdated = 3,
MeetingEnded = 4,
RecordingStarted = 5,
RecordingEnded = 6,
RecordingReady = 7,
CaptureStatus = 8,
Error = 9,
SpeakerChanged = 10,
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub enum SHPermission {
Microphone = 0,
ScreenCapture = 1,
Accessibility = 2,
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub enum SHPermissionStatus {
Granted = 0,
NotRequested = 1,
Denied = 2,
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub enum SHCaptureKind {
Audio = 0,
Video = 1,
}
#[repr(C)]
pub struct SHEvent {
pub kind: SHEventKind,
pub app: *const c_char, pub title: *const c_char, pub path: *const c_char, pub others_path: *const c_char, pub self_path: *const c_char, pub message: *const c_char, pub participant: *const c_char,
pub permission: SHPermission,
pub perm_status: SHPermissionStatus,
pub capture_kind: SHCaptureKind,
pub capturing: c_int, }
pub type SHEventCallback =
unsafe extern "C" fn(event: *const SHEvent, userdata: *mut c_void);
struct Userdata(usize);
unsafe impl Send for Userdata {}
unsafe impl Sync for Userdata {}
fn str_ptr(s: &str, buf: &mut Option<CString>) -> *const c_char {
let cs = CString::new(s).unwrap_or_default();
let p = cs.as_ptr();
*buf = Some(cs);
p
}
fn dispatch(cb: SHEventCallback, ud: usize, event: &Event) {
let (mut s1, mut s2, mut s3, mut s4, mut s5, mut s6):
(Option<CString>, Option<CString>, Option<CString>,
Option<CString>, Option<CString>, Option<CString>) = Default::default();
let null: *const c_char = std::ptr::null();
let ev = match event {
Event::PermissionStatus { permission, status } => SHEvent {
kind: SHEventKind::PermissionStatus,
app: null, title: null, path: null, others_path: null,
self_path: null, message: null, participant: null,
permission: match permission {
Permission::Microphone => SHPermission::Microphone,
Permission::ScreenCapture => SHPermission::ScreenCapture,
Permission::Accessibility => SHPermission::Accessibility,
},
perm_status: match status {
PermissionGranted::Granted => SHPermissionStatus::Granted,
PermissionGranted::NotRequested => SHPermissionStatus::NotRequested,
PermissionGranted::Denied => SHPermissionStatus::Denied,
},
capture_kind: SHCaptureKind::Audio,
capturing: 0,
},
Event::PermissionsGranted => SHEvent {
kind: SHEventKind::PermissionsGranted,
app: null, title: null, path: null, others_path: null,
self_path: null, message: null, participant: null,
permission: SHPermission::Microphone, perm_status: SHPermissionStatus::Granted,
capture_kind: SHCaptureKind::Audio, capturing: 0,
},
Event::MeetingDetected { app, .. } => SHEvent {
kind: SHEventKind::MeetingDetected,
app: str_ptr(app, &mut s1), title: null, path: null, others_path: null,
self_path: null, message: null, participant: null,
permission: SHPermission::Microphone, perm_status: SHPermissionStatus::Granted,
capture_kind: SHCaptureKind::Audio, capturing: 0,
},
Event::MeetingUpdated { app, title } => SHEvent {
kind: SHEventKind::MeetingUpdated,
app: str_ptr(app, &mut s1), title: str_ptr(title, &mut s2),
path: null, others_path: null, self_path: null, message: null, participant: null,
permission: SHPermission::Microphone, perm_status: SHPermissionStatus::Granted,
capture_kind: SHCaptureKind::Audio, capturing: 0,
},
Event::MeetingEnded { app } => SHEvent {
kind: SHEventKind::MeetingEnded,
app: str_ptr(app, &mut s1), title: null, path: null, others_path: null,
self_path: null, message: null, participant: null,
permission: SHPermission::Microphone, perm_status: SHPermissionStatus::Granted,
capture_kind: SHCaptureKind::Audio, capturing: 0,
},
Event::RecordingStarted { app } => SHEvent {
kind: SHEventKind::RecordingStarted,
app: str_ptr(app, &mut s1), title: null, path: null, others_path: null,
self_path: null, message: null, participant: null,
permission: SHPermission::Microphone, perm_status: SHPermissionStatus::Granted,
capture_kind: SHCaptureKind::Audio, capturing: 0,
},
Event::RecordingEnded { app } => SHEvent {
kind: SHEventKind::RecordingEnded,
app: str_ptr(app, &mut s1), title: null, path: null, others_path: null,
self_path: null, message: null, participant: null,
permission: SHPermission::Microphone, perm_status: SHPermissionStatus::Granted,
capture_kind: SHCaptureKind::Audio, capturing: 0,
},
Event::RecordingReady { mixed_path, others_path, self_path, app } => SHEvent {
kind: SHEventKind::RecordingReady,
app: str_ptr(app, &mut s1),
path: str_ptr(mixed_path.to_str().unwrap_or(""), &mut s2),
others_path: str_ptr(others_path.to_str().unwrap_or(""), &mut s3),
self_path: str_ptr(self_path.to_str().unwrap_or(""), &mut s4),
title: null, message: null, participant: null,
permission: SHPermission::Microphone, perm_status: SHPermissionStatus::Granted,
capture_kind: SHCaptureKind::Audio, capturing: 0,
},
Event::CaptureStatus { kind, capturing } => SHEvent {
kind: SHEventKind::CaptureStatus,
app: null, title: null, path: null, others_path: null,
self_path: null, message: null, participant: null,
permission: SHPermission::Microphone, perm_status: SHPermissionStatus::Granted,
capture_kind: match kind {
CaptureKind::Audio => SHCaptureKind::Audio,
CaptureKind::Video => SHCaptureKind::Video,
},
capturing: if *capturing { 1 } else { 0 },
},
Event::Error { message } => SHEvent {
kind: SHEventKind::Error,
message: str_ptr(message, &mut s5),
app: null, title: null, path: null, others_path: null,
self_path: null, participant: null,
permission: SHPermission::Microphone, perm_status: SHPermissionStatus::Granted,
capture_kind: SHCaptureKind::Audio, capturing: 0,
},
Event::SpeakerChanged { speakers, app } => SHEvent {
kind: SHEventKind::SpeakerChanged,
app: str_ptr(app, &mut s1),
participant: str_ptr(&speakers.join("\t"), &mut s6),
title: null, path: null, others_path: null,
self_path: null, message: null,
permission: SHPermission::Microphone, perm_status: SHPermissionStatus::Granted,
capture_kind: SHCaptureKind::Audio, capturing: 0,
},
};
unsafe { cb(&ev, ud as *mut c_void) };
drop((s1, s2, s3, s4, s5, s6));
}
#[no_mangle]
pub extern "C" fn side_huddle_new() -> *mut c_void {
Box::into_raw(Box::new(MeetingListener::new())) as *mut c_void
}
#[no_mangle]
pub unsafe extern "C" fn side_huddle_free(handle: *mut c_void) {
if !handle.is_null() {
drop(Box::from_raw(handle as *mut MeetingListener));
}
}
#[no_mangle]
pub unsafe extern "C" fn side_huddle_on(
handle: *const c_void,
callback: SHEventCallback,
userdata: *mut c_void,
) {
let ud = Userdata(userdata as usize);
as_listener(handle).on(move |event| dispatch(callback, ud.0, event));
}
#[no_mangle]
pub unsafe extern "C" fn side_huddle_auto_record(handle: *const c_void) {
as_listener(handle).auto_record();
}
#[no_mangle]
pub unsafe extern "C" fn side_huddle_record(handle: *const c_void) {
as_listener(handle).record();
}
#[no_mangle]
pub unsafe extern "C" fn side_huddle_set_sample_rate(
handle: *const c_void,
hz: u32,
) {
as_listener(handle).sample_rate(hz);
}
#[no_mangle]
pub unsafe extern "C" fn side_huddle_set_output_dir(
handle: *const c_void,
dir: *const c_char,
) {
if dir.is_null() { return; }
if let Ok(s) = CStr::from_ptr(dir).to_str() {
as_listener(handle).output_dir(s);
}
}
#[no_mangle]
pub unsafe extern "C" fn side_huddle_start(handle: *const c_void) -> c_int {
match as_listener(handle).start() {
Ok(()) => 0,
Err(_) => -1,
}
}
#[no_mangle]
pub unsafe extern "C" fn side_huddle_stop(handle: *const c_void) {
as_listener(handle).stop();
}
#[no_mangle]
pub extern "C" fn side_huddle_version() -> *const c_char {
static VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), "\0");
VERSION.as_ptr() as *const c_char
}