use std::ffi::CString;
use std::os::raw::c_char;
use std::ptr;
use std::sync::Mutex;
use crate::engine::Engine;
use crate::input_method::InputMethod;
use crate::mode::Mode;
static GLOBAL_ENGINE: Mutex<Option<Engine>> = Mutex::new(None);
fn with_engine<F, R>(f: F) -> R
where
F: FnOnce(&mut Engine) -> R,
R: Default,
{
let mut guard = match GLOBAL_ENGINE.lock() {
Ok(g) => g,
Err(_) => return R::default(),
};
if guard.is_none() {
*guard = Some(Engine::new(InputMethod::telex()));
}
f(guard.as_mut().unwrap())
}
#[unsafe(no_mangle)]
pub extern "C" fn bamboo_setup() {
let mut guard = GLOBAL_ENGINE.lock().unwrap();
*guard = Some(Engine::new(InputMethod::telex()));
}
#[unsafe(no_mangle)]
pub extern "C" fn bamboo_reset() {
with_engine(|e| e.reset());
}
#[unsafe(no_mangle)]
pub extern "C" fn bamboo_set_input_method(method: i32) {
let im = match method {
0 => InputMethod::telex(),
1 => InputMethod::vni(),
2 => InputMethod::viqr(),
3 => InputMethod::microsoft_layout(),
4 => InputMethod::telex_2(),
5 => InputMethod::telex_w(),
_ => return,
};
let mut guard = GLOBAL_ENGINE.lock().unwrap();
*guard = Some(Engine::new(im));
}
#[unsafe(no_mangle)]
pub extern "C" fn bamboo_process_key(key: u32, is_vietnamese: i32) -> *mut c_char {
with_engine(|e| {
let mode = if is_vietnamese != 0 { Mode::Vietnamese } else { Mode::English };
if let Some(c) = std::char::from_u32(key) {
e.process_key(c, mode);
}
let out = e.output();
CString::new(out).unwrap().into_raw()
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn bamboo_process_key_buf(
key: u32,
is_vietnamese: i32,
out_buf: *mut u8,
out_cap: usize,
out_len: *mut usize,
backspaces_chars: *mut usize,
backspaces_bytes: *mut usize,
) -> i32 {
if out_len.is_null() || backspaces_chars.is_null() || backspaces_bytes.is_null() {
return -1;
}
let out_len = unsafe { &mut *out_len };
let backspaces_chars = unsafe { &mut *backspaces_chars };
let backspaces_bytes = unsafe { &mut *backspaces_bytes };
let mode = if is_vietnamese != 0 { Mode::Vietnamese } else { Mode::English };
let mut guard = match GLOBAL_ENGINE.lock() {
Ok(g) => g,
Err(poisoned) => poisoned.into_inner(),
};
if guard.is_none() {
*guard = Some(Engine::new(InputMethod::telex()));
}
let e = guard.as_mut().unwrap();
let Some(c) = std::char::from_u32(key) else {
*out_len = 0;
*backspaces_chars = 0;
*backspaces_bytes = 0;
return 0;
};
let (bs_chars, bs_bytes, inserted) = e.process_key_delta(c, mode);
let bytes = inserted.as_bytes();
*out_len = bytes.len();
*backspaces_chars = bs_chars;
*backspaces_bytes = bs_bytes;
if bytes.len() > out_cap {
return 1;
}
if !bytes.is_empty() {
if out_buf.is_null() {
return -1;
}
unsafe {
ptr::copy_nonoverlapping(bytes.as_ptr(), out_buf, bytes.len());
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn bamboo_output() -> *mut c_char {
with_engine(|e| {
let out = e.output();
CString::new(out).unwrap().into_raw()
})
}
#[unsafe(no_mangle)]
pub extern "C" fn bamboo_remove_last_char() {
with_engine(|e| e.remove_last_char(true));
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn bamboo_free_string(s: *mut c_char) {
if !s.is_null() {
unsafe {
let _ = CString::from_raw(s);
}
}
}
pub type BambooEngine = Engine;
#[unsafe(no_mangle)]
pub extern "C" fn bamboo_engine_new(method: i32) -> *mut BambooEngine {
let im = match method {
1 => InputMethod::vni(),
2 => InputMethod::viqr(),
_ => InputMethod::telex(),
};
Box::into_raw(Box::new(Engine::new(im)))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn bamboo_engine_free(engine: *mut BambooEngine) {
if !engine.is_null() {
unsafe {
let _ = Box::from_raw(engine);
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn bamboo_engine_process(engine: *mut BambooEngine, key: u32) -> *mut c_char {
if engine.is_null() {
return ptr::null_mut();
}
let e = unsafe { &mut *engine };
if let Some(c) = std::char::from_u32(key) {
e.process_key(c, Mode::Vietnamese);
}
let out = e.output();
CString::new(out).unwrap().into_raw()
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn bamboo_engine_process_key_buf(
engine: *mut BambooEngine,
key: u32,
is_vietnamese: i32,
out_buf: *mut u8,
out_cap: usize,
out_len: *mut usize,
backspaces_chars: *mut usize,
backspaces_bytes: *mut usize,
) -> i32 {
if engine.is_null() {
return -2;
}
if out_len.is_null() || backspaces_chars.is_null() || backspaces_bytes.is_null() {
return -1;
}
let out_len = unsafe { &mut *out_len };
let backspaces_chars = unsafe { &mut *backspaces_chars };
let backspaces_bytes = unsafe { &mut *backspaces_bytes };
let mode = if is_vietnamese != 0 { Mode::Vietnamese } else { Mode::English };
let e = unsafe { &mut *engine };
let Some(c) = std::char::from_u32(key) else {
*out_len = 0;
*backspaces_chars = 0;
*backspaces_bytes = 0;
return 0;
};
let (bs_chars, bs_bytes, inserted) = e.process_key_delta(c, mode);
let bytes = inserted.as_bytes();
*out_len = bytes.len();
*backspaces_chars = bs_chars;
*backspaces_bytes = bs_bytes;
if bytes.len() > out_cap {
return 1;
}
if !bytes.is_empty() {
if out_buf.is_null() {
return -1;
}
unsafe {
ptr::copy_nonoverlapping(bytes.as_ptr(), out_buf, bytes.len());
}
}
0
}