#![allow(unsafe_op_in_unsafe_fn)]
use core::ptr::NonNull;
use std::ffi::{CStr, CString, c_void};
use std::os::raw::c_char;
use std::sync::atomic::{AtomicU64, Ordering};
use crate::ffi;
static NEXT_REG_ID: AtomicU64 = AtomicU64::new(1);
fn next_reg_id() -> u64 {
NEXT_REG_ID.fetch_add(1, Ordering::Relaxed)
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(i32)]
#[non_exhaustive]
pub enum CursorType {
None = 0,
No = 1,
Arrow = 2,
AppStarting = 3,
Cross = 4,
Help = 5,
IBeam = 6,
SizeAll = 7,
SizeNESW = 8,
SizeNS = 9,
SizeNWSE = 10,
SizeWE = 11,
UpArrow = 12,
Wait = 13,
Hand = 14,
Pen = 15,
ScrollNS = 16,
ScrollWE = 17,
ScrollAll = 18,
ScrollN = 19,
ScrollS = 20,
ScrollW = 21,
ScrollE = 22,
ScrollNW = 23,
ScrollNE = 24,
ScrollSW = 25,
ScrollSE = 26,
ArrowCD = 27,
Custom = 28,
}
impl CursorType {
#[must_use]
pub fn from_raw(raw: i32) -> Self {
match raw {
1 => Self::No,
2 => Self::Arrow,
3 => Self::AppStarting,
4 => Self::Cross,
5 => Self::Help,
6 => Self::IBeam,
7 => Self::SizeAll,
8 => Self::SizeNESW,
9 => Self::SizeNS,
10 => Self::SizeNWSE,
11 => Self::SizeWE,
12 => Self::UpArrow,
13 => Self::Wait,
14 => Self::Hand,
15 => Self::Pen,
16 => Self::ScrollNS,
17 => Self::ScrollWE,
18 => Self::ScrollAll,
19 => Self::ScrollN,
20 => Self::ScrollS,
21 => Self::ScrollW,
22 => Self::ScrollE,
23 => Self::ScrollNW,
24 => Self::ScrollNE,
25 => Self::ScrollSW,
26 => Self::ScrollSE,
27 => Self::ArrowCD,
28 => Self::Custom,
_ => Self::None,
}
}
}
fn cstr_to_str(p: *const c_char) -> String {
if p.is_null() {
String::new()
} else {
unsafe { CStr::from_ptr(p) }.to_string_lossy().into_owned()
}
}
type CursorClosure = Box<dyn Fn(*mut c_void, CursorType) + Send>;
static CURSOR_ACTIVE: AtomicU64 = AtomicU64::new(0);
unsafe extern "C" fn cursor_tramp(user: *mut c_void, view: *mut c_void, cursor_type: i32) {
crate::panic_guard::guard(|| {
let cb = &*user.cast::<CursorClosure>();
cb(view, CursorType::from_raw(cursor_type));
})
}
pub struct CursorCallback {
user: NonNull<CursorClosure>,
id: u64,
}
unsafe impl Send for CursorCallback {}
impl Drop for CursorCallback {
fn drop(&mut self) {
if CURSOR_ACTIVE
.compare_exchange(self.id, 0, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
unsafe { ffi::noesis_set_cursor_callback(core::ptr::null_mut(), None) };
}
unsafe { drop(Box::from_raw(self.user.as_ptr())) };
}
}
pub fn set_cursor_callback<F>(f: F) -> CursorCallback
where
F: Fn(*mut c_void, CursorType) + Send + 'static,
{
let boxed: Box<CursorClosure> = Box::new(Box::new(f));
let user = Box::into_raw(boxed);
let id = next_reg_id();
CURSOR_ACTIVE.store(id, Ordering::Release);
unsafe { ffi::noesis_set_cursor_callback(user.cast(), Some(cursor_tramp)) };
CursorCallback {
user: NonNull::new(user).expect("Box::into_raw returned null"),
id,
}
}
type KeyboardClosure = Box<dyn Fn(*mut c_void, bool) + Send>;
static KEYBOARD_ACTIVE: AtomicU64 = AtomicU64::new(0);
unsafe extern "C" fn keyboard_tramp(user: *mut c_void, focused: *mut c_void, open: bool) {
crate::panic_guard::guard(|| {
let cb = &*user.cast::<KeyboardClosure>();
cb(focused, open);
})
}
pub struct SoftwareKeyboardCallback {
user: NonNull<KeyboardClosure>,
id: u64,
}
unsafe impl Send for SoftwareKeyboardCallback {}
impl Drop for SoftwareKeyboardCallback {
fn drop(&mut self) {
if KEYBOARD_ACTIVE
.compare_exchange(self.id, 0, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
unsafe { ffi::noesis_set_software_keyboard_callback(core::ptr::null_mut(), None) };
}
unsafe { drop(Box::from_raw(self.user.as_ptr())) };
}
}
pub fn set_software_keyboard_callback<F>(f: F) -> SoftwareKeyboardCallback
where
F: Fn(*mut c_void, bool) + Send + 'static,
{
let boxed: Box<KeyboardClosure> = Box::new(Box::new(f));
let user = Box::into_raw(boxed);
let id = next_reg_id();
KEYBOARD_ACTIVE.store(id, Ordering::Release);
unsafe { ffi::noesis_set_software_keyboard_callback(user.cast(), Some(keyboard_tramp)) };
SoftwareKeyboardCallback {
user: NonNull::new(user).expect("Box::into_raw returned null"),
id,
}
}
type OpenUrlClosure = Box<dyn Fn(&str) + Send>;
static OPEN_URL_ACTIVE: AtomicU64 = AtomicU64::new(0);
unsafe extern "C" fn open_url_tramp(user: *mut c_void, url: *const c_char) {
crate::panic_guard::guard(|| {
let cb = &*user.cast::<OpenUrlClosure>();
cb(&cstr_to_str(url));
})
}
pub struct OpenUrlCallback {
user: NonNull<OpenUrlClosure>,
id: u64,
}
unsafe impl Send for OpenUrlCallback {}
impl Drop for OpenUrlCallback {
fn drop(&mut self) {
if OPEN_URL_ACTIVE
.compare_exchange(self.id, 0, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
unsafe { ffi::noesis_set_open_url_callback(core::ptr::null_mut(), None) };
}
unsafe { drop(Box::from_raw(self.user.as_ptr())) };
}
}
pub fn set_open_url_callback<F>(f: F) -> OpenUrlCallback
where
F: Fn(&str) + Send + 'static,
{
let boxed: Box<OpenUrlClosure> = Box::new(Box::new(f));
let user = Box::into_raw(boxed);
let id = next_reg_id();
OPEN_URL_ACTIVE.store(id, Ordering::Release);
unsafe { ffi::noesis_set_open_url_callback(user.cast(), Some(open_url_tramp)) };
OpenUrlCallback {
user: NonNull::new(user).expect("Box::into_raw returned null"),
id,
}
}
pub fn open_url(url: &str) {
let c = CString::new(url).expect("url contained interior NUL");
unsafe { ffi::noesis_open_url(c.as_ptr()) };
}
type PlayAudioClosure = Box<dyn Fn(&str, f32) + Send>;
static PLAY_AUDIO_ACTIVE: AtomicU64 = AtomicU64::new(0);
unsafe extern "C" fn play_audio_tramp(user: *mut c_void, uri: *const c_char, volume: f32) {
crate::panic_guard::guard(|| {
let cb = &*user.cast::<PlayAudioClosure>();
cb(&cstr_to_str(uri), volume);
})
}
pub struct PlayAudioCallback {
user: NonNull<PlayAudioClosure>,
id: u64,
}
unsafe impl Send for PlayAudioCallback {}
impl Drop for PlayAudioCallback {
fn drop(&mut self) {
if PLAY_AUDIO_ACTIVE
.compare_exchange(self.id, 0, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
unsafe { ffi::noesis_set_play_audio_callback(core::ptr::null_mut(), None) };
}
unsafe { drop(Box::from_raw(self.user.as_ptr())) };
}
}
pub fn set_play_audio_callback<F>(f: F) -> PlayAudioCallback
where
F: Fn(&str, f32) + Send + 'static,
{
let boxed: Box<PlayAudioClosure> = Box::new(Box::new(f));
let user = Box::into_raw(boxed);
let id = next_reg_id();
PLAY_AUDIO_ACTIVE.store(id, Ordering::Release);
unsafe { ffi::noesis_set_play_audio_callback(user.cast(), Some(play_audio_tramp)) };
PlayAudioCallback {
user: NonNull::new(user).expect("Box::into_raw returned null"),
id,
}
}
pub fn play_audio(uri: &str, volume: f32) {
let c = CString::new(uri).expect("uri contained interior NUL");
unsafe { ffi::noesis_play_audio(c.as_ptr(), volume) };
}
pub fn set_culture(name: &str) {
let c = CString::new(name).expect("culture name contained interior NUL");
unsafe { ffi::noesis_set_culture(c.as_ptr()) };
}
#[must_use]
pub fn get_culture() -> String {
let p = unsafe { ffi::noesis_get_culture() };
if p.is_null() {
String::new()
} else {
unsafe { CStr::from_ptr(p) }.to_string_lossy().into_owned()
}
}