use crate::cskkstate::PreCompositionData;
use crate::dictionary::CskkDictionary;
use crate::keyevent::CskkKeyEvent;
use crate::skk_modes::{CommaStyle, CompositionMode, InputMode, PeriodStyle};
use crate::CskkError::Error;
use crate::{
get_available_rules, skk_context_confirm_candidate_at_rs, skk_context_get_composition_mode_rs,
skk_context_get_current_candidate_count_rs,
skk_context_get_current_candidate_cursor_position_rs, skk_context_get_current_candidates_rs,
skk_context_get_current_to_composite_rs, skk_context_get_input_mode_rs,
skk_context_poll_output_rs, skk_context_reset_rs, skk_context_select_candidate_at_rs,
skk_context_set_auto_start_henkan_keywords_rs, skk_context_set_comma_style_rs,
skk_context_set_dictionaries_rs, skk_context_set_input_mode_rs,
skk_context_set_period_style_rs, CskkContext, CskkError, CskkStateInfo,
};
use std::convert::TryFrom;
use std::ffi::{CStr, CString};
use std::mem::ManuallyDrop;
use std::os::raw::{c_char, c_int, c_uint};
use std::sync::Arc;
use std::{ptr, slice};
pub struct CskkDictionaryFfi {
dictionary: Arc<CskkDictionary>,
}
#[repr(C)]
pub struct CskkRulesFfi {
id: *mut c_char,
name: *mut c_char,
description: *mut c_char,
}
impl CskkRulesFfi {
#[allow(clippy::result_unit_err)]
pub fn new(rust_id: &str, rust_name: &str, rust_description: &str) -> Result<Self, CskkError> {
let id = CString::new(rust_id.to_string())?;
let name = CString::new(rust_name.to_string())?;
let description = CString::new(rust_description.to_string())?;
Ok(CskkRulesFfi {
id: id.into_raw(),
name: name.into_raw(),
description: description.into_raw(),
})
}
}
impl Drop for CskkRulesFfi {
fn drop(&mut self) {
unsafe {
drop(CString::from_raw(self.id));
drop(CString::from_raw(self.name));
drop(CString::from_raw(self.description));
}
}
}
#[repr(C)]
pub enum CskkStateInfoFfi {
DirectStateInfo(DirectDataFfi),
PreCompositionStateInfo(PreCompositionDataFfi),
PreCompositionOkuriganaStateInfo(PreCompositionDataFfi),
CompositionSelectionStateInfo(CompositionSelectionDataFfi),
RegisterStateInfo(RegisterDataFfi),
CompleteStateInfo(CompleteDataFfi),
}
#[repr(C)]
pub struct DirectDataFfi {
pub confirmed: *mut c_char,
pub unconverted: *mut c_char,
}
impl Drop for DirectDataFfi {
fn drop(&mut self) {
unsafe {
if !self.confirmed.is_null() {
drop(CString::from_raw(self.confirmed));
}
if !self.unconverted.is_null() {
drop(CString::from_raw(self.unconverted));
}
}
}
}
#[repr(C)]
pub struct CompositionSelectionDataFfi {
pub confirmed: *mut c_char,
pub composited: *mut c_char,
pub okuri: *mut c_char,
pub annotation: *mut c_char,
}
impl Drop for CompositionSelectionDataFfi {
fn drop(&mut self) {
unsafe {
if !self.confirmed.is_null() {
drop(CString::from_raw(self.confirmed));
}
if !self.composited.is_null() {
drop(CString::from_raw(self.composited));
}
if !self.okuri.is_null() {
drop(CString::from_raw(self.okuri));
}
if !self.annotation.is_null() {
drop(CString::from_raw(self.annotation));
}
}
}
}
#[repr(C)]
pub struct PreCompositionDataFfi {
pub confirmed: *mut c_char,
pub kana_to_composite: *mut c_char,
pub okuri: *mut c_char,
pub unconverted: *mut c_char,
}
impl Drop for PreCompositionDataFfi {
fn drop(&mut self) {
unsafe {
if !self.confirmed.is_null() {
drop(CString::from_raw(self.confirmed));
}
if !self.kana_to_composite.is_null() {
drop(CString::from_raw(self.kana_to_composite));
}
if !self.okuri.is_null() {
drop(CString::from_raw(self.okuri));
}
if !self.unconverted.is_null() {
drop(CString::from_raw(self.unconverted));
}
}
}
}
#[repr(C)]
pub struct RegisterDataFfi {
pub confirmed: *mut c_char,
pub kana_to_composite: *mut c_char,
pub okuri: *mut c_char,
pub postfix: *mut c_char,
}
impl Drop for RegisterDataFfi {
fn drop(&mut self) {
unsafe {
if !self.confirmed.is_null() {
drop(CString::from_raw(self.confirmed));
}
if !self.kana_to_composite.is_null() {
drop(CString::from_raw(self.kana_to_composite));
}
if !self.okuri.is_null() {
drop(CString::from_raw(self.okuri));
}
if !self.postfix.is_null() {
drop(CString::from_raw(self.postfix));
}
}
}
}
#[repr(C)]
pub struct CompleteDataFfi {
pub confirmed: *mut c_char,
pub complete_origin: *mut c_char,
pub origin_okuri: *mut c_char,
pub completed_midashi: *mut c_char,
pub completed: *mut c_char,
pub okuri: *mut c_char,
pub annotation: *mut c_char,
}
impl Drop for CompleteDataFfi {
fn drop(&mut self) {
unsafe {
if !self.confirmed.is_null() {
drop(CString::from_raw(self.confirmed));
}
if !self.complete_origin.is_null() {
drop(CString::from_raw(self.complete_origin));
}
if !self.origin_okuri.is_null() {
drop(CString::from_raw(self.origin_okuri));
}
if !self.completed.is_null() {
drop(CString::from_raw(self.completed));
}
if !self.okuri.is_null() {
drop(CString::from_raw(self.okuri));
}
if !self.annotation.is_null() {
drop(CString::from_raw(self.annotation));
}
}
}
}
#[no_mangle]
pub unsafe extern "C" fn skk_context_new(
dictionary_array: &*mut CskkDictionaryFfi,
dictionary_count: usize,
) -> *mut CskkContext {
let dict_array = dictionaries_from_c_repr(dictionary_array, dictionary_count);
let maybe_context = CskkContext::new(InputMode::Hiragana, CompositionMode::Direct, dict_array);
if let Ok(context) = maybe_context {
Box::into_raw(Box::new(context))
} else {
ptr::null_mut()
}
}
#[no_mangle]
pub unsafe extern "C" fn skk_context_new_with_empty_fallback(
dictionary_array: &*mut CskkDictionaryFfi,
dictionary_count: usize,
) -> *mut CskkContext {
let dict_array = dictionaries_from_c_repr(dictionary_array, dictionary_count);
let context = CskkContext::new_with_empty_fallback(
InputMode::Hiragana,
CompositionMode::Direct,
dict_array,
);
Box::into_raw(Box::new(context))
}
#[no_mangle]
pub unsafe extern "C" fn skk_file_dict_new(
c_path_string: *const c_char,
c_encoding: *const c_char,
use_for_completion: bool,
) -> *mut CskkDictionaryFfi {
let maybe_dictionary = (|| -> anyhow::Result<CskkDictionaryFfi> {
let path = CStr::from_ptr(c_path_string).to_str()?;
let encoding = CStr::from_ptr(c_encoding).to_str()?;
let dictionary = CskkDictionary::new_static_dict(path, encoding, use_for_completion)?;
Ok(CskkDictionaryFfi {
dictionary: Arc::new(dictionary),
})
})();
if let Ok(ffi_dictionary) = maybe_dictionary {
Box::into_raw(Box::new(ffi_dictionary))
} else {
ptr::null_mut()
}
}
#[no_mangle]
pub unsafe extern "C" fn skk_user_dict_new(
c_path_string: *const c_char,
c_encoding: *const c_char,
use_for_completion: bool,
) -> *mut CskkDictionaryFfi {
let maybe_dictionary = (|| -> anyhow::Result<CskkDictionaryFfi> {
let path = CStr::from_ptr(c_path_string).to_str()?;
let encoding = CStr::from_ptr(c_encoding).to_str()?;
let dictionary = CskkDictionary::new_user_dict(path, encoding, use_for_completion)?;
Ok(CskkDictionaryFfi {
dictionary: Arc::new(dictionary),
})
})();
if let Ok(ffi_dictionary) = maybe_dictionary {
Box::into_raw(Box::new(ffi_dictionary))
} else {
ptr::null_mut()
}
}
#[no_mangle]
pub unsafe extern "C" fn skk_empty_dict_new() -> *mut CskkDictionaryFfi {
let maybe_result = (|| -> anyhow::Result<CskkDictionaryFfi> {
Ok(CskkDictionaryFfi {
dictionary: Arc::new(CskkDictionary::new_empty_dict()?),
})
})();
if let Ok(dict) = maybe_result {
Box::into_raw(Box::new(dict))
} else {
ptr::null_mut()
}
}
#[no_mangle]
pub extern "C" fn skk_context_set_input_mode(context: &mut CskkContext, input_mode: InputMode) {
skk_context_set_input_mode_rs(context, input_mode)
}
#[no_mangle]
pub extern "C" fn skk_context_get_input_mode(context: &mut CskkContext) -> InputMode {
context.get_current_input_mode()
}
#[no_mangle]
pub extern "C" fn skk_context_get_composition_mode(context: &mut CskkContext) -> CompositionMode {
context.get_current_composition_mode()
}
#[no_mangle]
pub unsafe extern "C" fn skk_context_set_rule(
context: &mut CskkContext,
rule_name: *const c_char,
) -> c_int {
let any_error = (|| -> anyhow::Result<()> {
let rule_name_str = CStr::from_ptr(rule_name);
let rule_name_str = rule_name_str.to_str()?;
context.set_rule(rule_name_str)?;
Ok(())
})();
if any_error.is_err() {
return -1;
}
0
}
#[no_mangle]
pub unsafe extern "C" fn skk_context_process_key_events(
context: &mut CskkContext,
keyevents_cstring: *mut c_char,
) -> bool {
let maybe_result = (|| -> anyhow::Result<bool> {
let keyevents = CStr::from_ptr(keyevents_cstring);
Ok(context.process_key_events_string(keyevents.to_str()?))
})();
if let Ok(result) = maybe_result {
result
} else {
false
}
}
#[no_mangle]
pub unsafe extern "C" fn skk_context_process_key_event(
context: &mut CskkContext,
keyevent: *mut CskkKeyEvent,
) -> bool {
let raw_keyevent = Box::from_raw(keyevent);
context.process_key_event(raw_keyevent.as_ref())
}
#[no_mangle]
pub extern "C" fn skk_context_poll_output(context: &mut CskkContext) -> *mut c_char {
let maybe_result =
(|| -> anyhow::Result<CString> { Ok(CString::new(skk_context_poll_output_rs(context))?) })(
);
if let Ok(result) = maybe_result {
result.into_raw()
} else {
ptr::null_mut()
}
}
#[no_mangle]
pub extern "C" fn skk_context_get_preedit(context: &CskkContext) -> *mut c_char {
let maybe_result = (|| -> anyhow::Result<CString> {
let maybe_preedit = context.get_preedit();
if let Some(preedit) = maybe_preedit {
Ok(CString::new(preedit)?)
} else {
Err(Error("no preedit".to_string()).into())
}
})();
if let Ok(result) = maybe_result {
result.into_raw()
} else {
ptr::null_mut()
}
}
#[no_mangle]
pub unsafe extern "C" fn skk_context_get_preedit_detail(
context: &CskkContext,
state_stack_len: *mut c_uint,
) -> *mut CskkStateInfoFfi {
let preedit = context.get_preedit_detail();
let mut converted = preedit
.into_iter()
.map(convert_state_info)
.collect::<Vec<CskkStateInfoFfi>>();
*state_stack_len = u32::try_from(converted.len()).unwrap_or_default();
if !converted.is_empty() {
converted.set_len(converted.len());
let mut retval = ManuallyDrop::new(converted);
retval.as_mut_ptr()
} else {
ptr::null_mut()
}
}
fn convert_state_info(state_info: CskkStateInfo) -> CskkStateInfoFfi {
match state_info {
CskkStateInfo::Direct(direct_data) => {
let confirmed = CString::new(direct_data.confirmed)
.unwrap_or_default()
.into_raw();
let unconverted = if direct_data.unconverted.is_some() {
CString::new(direct_data.unconverted.unwrap())
.unwrap()
.into_raw()
} else {
ptr::null_mut()
};
CskkStateInfoFfi::DirectStateInfo(DirectDataFfi {
confirmed,
unconverted,
})
}
CskkStateInfo::PreComposition(precomposition_data) => {
CskkStateInfoFfi::PreCompositionStateInfo(convert_precomposition_data(
precomposition_data,
))
}
CskkStateInfo::PreCompositionOkurigana(precomposition_data) => {
CskkStateInfoFfi::PreCompositionOkuriganaStateInfo(convert_precomposition_data(
precomposition_data,
))
}
CskkStateInfo::Register(register_data) => {
let confirmed = CString::new(register_data.confirmed)
.unwrap_or_default()
.into_raw();
let kana_to_composite = CString::new(register_data.kana_to_composite)
.unwrap_or_default()
.into_raw();
let okuri = if let Some(okuri_string) = register_data.okuri {
CString::new(okuri_string).unwrap_or_default().into_raw()
} else {
ptr::null_mut()
};
let postfix = if let Some(unconverted_string) = register_data.postfix {
CString::new(unconverted_string).unwrap().into_raw()
} else {
ptr::null_mut()
};
CskkStateInfoFfi::RegisterStateInfo(RegisterDataFfi {
confirmed,
kana_to_composite,
okuri,
postfix,
})
}
CskkStateInfo::CompositionSelection(composition_selection_data) => {
let confirmed = CString::new(composition_selection_data.confirmed)
.unwrap()
.into_raw();
let composited = CString::new(composition_selection_data.composited)
.unwrap()
.into_raw();
let okuri = if let Some(okuri_string) = composition_selection_data.okuri {
CString::new(okuri_string).unwrap_or_default().into_raw()
} else {
ptr::null_mut()
};
let annotation = if let Some(annotation_string) = composition_selection_data.annotation
{
CString::new(annotation_string)
.unwrap_or_default()
.into_raw()
} else {
ptr::null_mut()
};
CskkStateInfoFfi::CompositionSelectionStateInfo(CompositionSelectionDataFfi {
confirmed,
composited,
okuri,
annotation,
})
}
CskkStateInfo::Complete(complete_data) => {
let confirmed = CString::new(complete_data.confirmed).unwrap().into_raw();
let complete_origin = CString::new(complete_data.complete_origin)
.unwrap()
.into_raw();
let completed_midashi = CString::new(complete_data.completed_midashi)
.unwrap()
.into_raw();
let completed = CString::new(complete_data.completed).unwrap().into_raw();
let annotation = if let Some(annotation) = complete_data.annotation {
CString::new(annotation).unwrap().into_raw()
} else {
ptr::null_mut()
};
CskkStateInfoFfi::CompleteStateInfo(CompleteDataFfi {
confirmed,
complete_origin,
origin_okuri: ptr::null_mut(),
completed_midashi,
completed,
okuri: ptr::null_mut(),
annotation,
})
}
}
}
fn convert_precomposition_data(precomposition_data: PreCompositionData) -> PreCompositionDataFfi {
let confirmed = CString::new(precomposition_data.confirmed)
.unwrap_or_default()
.into_raw();
let kana_to_composite = CString::new(precomposition_data.kana_to_composite)
.unwrap_or_default()
.into_raw();
let okuri = if let Some(okuri_string) = precomposition_data.okuri {
CString::new(okuri_string).unwrap_or_default().into_raw()
} else {
ptr::null_mut()
};
let unconverted = if let Some(unconverted_string) = precomposition_data.unconverted {
CString::new(unconverted_string).unwrap().into_raw()
} else {
ptr::null_mut()
};
PreCompositionDataFfi {
confirmed,
kana_to_composite,
okuri,
unconverted,
}
}
#[no_mangle]
pub unsafe extern "C" fn skk_free_preedit_detail(ptr: *mut CskkStateInfoFfi, length: c_uint) {
if ptr.is_null() {
return;
}
let length = length as usize;
drop(Vec::from_raw_parts(ptr, length, length))
}
#[no_mangle]
#[allow(unused_must_use)]
pub unsafe extern "C" fn skk_free_string(ptr: *mut c_char) {
if ptr.is_null() {
return;
}
drop(CString::from_raw(ptr));
}
#[no_mangle]
pub extern "C" fn skk_context_save_dictionaries(context: &mut CskkContext) {
context.save_dictionary();
}
#[no_mangle]
pub unsafe extern "C" fn skk_free_context(context_ptr: *mut CskkContext) {
if context_ptr.is_null() {
return;
}
drop(Box::from_raw(context_ptr));
}
#[no_mangle]
pub unsafe extern "C" fn skk_free_dictionary(dictionary_ptr: *mut CskkDictionaryFfi) {
if dictionary_ptr.is_null() {
return;
}
drop(Box::from_raw(dictionary_ptr));
}
#[no_mangle]
pub unsafe extern "C" fn skk_free_rules(rules_ptr: *mut CskkRulesFfi, length: c_uint) {
let length = length as usize;
drop(Vec::from_raw_parts(rules_ptr, length, length));
}
#[no_mangle]
pub unsafe extern "C" fn skk_context_get_preedit_underline(
context: &mut CskkContext,
offset: *mut c_int,
nchars: *mut c_int,
) {
let (offset_size, nchars_size) = context.get_preedit_underline();
*offset = c_int::try_from(offset_size).unwrap_or(0);
*nchars = c_int::try_from(nchars_size).unwrap_or(0);
}
#[no_mangle]
pub unsafe extern "C" fn skk_context_set_dictionaries(
context: &mut CskkContext,
dictionary_array: &*mut CskkDictionaryFfi,
dictionary_count: usize,
) {
let dict_array = dictionaries_from_c_repr(dictionary_array, dictionary_count);
skk_context_set_dictionaries_rs(context, dict_array);
}
#[no_mangle]
pub extern "C" fn skk_key_event_new_from_fcitx_keyevent(
keysym: u32,
modifier: u32,
is_release: bool,
) -> *mut CskkKeyEvent {
Box::into_raw(Box::new(CskkKeyEvent::from_fcitx_keyevent(
keysym, modifier, is_release,
)))
}
#[no_mangle]
pub extern "C" fn skk_context_reset(context: &mut CskkContext) {
skk_context_reset_rs(context);
}
#[no_mangle]
pub unsafe extern "C" fn skk_context_get_current_to_composite(
context: &CskkContext,
) -> *mut c_char {
let maybe_result = { CString::new(skk_context_get_current_to_composite_rs(context)) };
if let Ok(result) = maybe_result {
result.into_raw()
} else {
ptr::null_mut()
}
}
#[no_mangle]
pub extern "C" fn skk_context_get_current_candidate_count(context: &CskkContext) -> c_uint {
skk_context_get_current_candidate_count_rs(context) as c_uint
}
#[no_mangle]
pub unsafe extern "C" fn skk_context_get_current_candidates(
context: &CskkContext,
candidate_buf: *mut *mut c_char,
buf_size: c_uint,
offset: c_uint,
) -> c_int {
let candidates = skk_context_get_current_candidates_rs(context);
let buffer = slice::from_raw_parts_mut(candidate_buf, buf_size as usize);
let offset = offset as usize;
let buf_size = buf_size as usize;
let returning_list = candidates.iter().skip(offset).take(buf_size).enumerate();
let count = returning_list.len();
for (i, candidate) in returning_list {
let maybe_c_string = CString::new(candidate.output.to_string());
if let Ok(c_string) = maybe_c_string {
buffer[i] = c_string.into_raw();
} else {
#[allow(clippy::needless_range_loop)]
for j in 0..i {
drop(CString::from_raw(buffer[j]))
}
return -1;
}
}
count as c_int
}
#[no_mangle]
#[allow(unused_must_use)]
pub unsafe extern "C" fn skk_free_candidate_list(
candidate_list_ptr: *mut *mut c_char,
size: c_uint,
) {
if candidate_list_ptr.is_null() {
return;
}
let list = slice::from_raw_parts_mut(candidate_list_ptr, size as usize);
for candidate in list.iter() {
drop(CString::from_raw(*candidate));
}
}
#[no_mangle]
pub extern "C" fn skk_context_get_current_candidate_cursor_position(
context: &mut CskkContext,
) -> c_int {
if let Ok(selection) = skk_context_get_current_candidate_cursor_position_rs(context) {
if selection > c_int::MAX as usize {
-2
} else {
selection as c_int
}
} else {
-1
}
}
#[no_mangle]
pub extern "C" fn skk_context_select_candidate_at(context: &mut CskkContext, i: c_int) -> bool {
skk_context_select_candidate_at_rs(context, i)
}
#[no_mangle]
pub extern "C" fn skk_context_confirm_candidate_at(context: &mut CskkContext, i: c_uint) -> bool {
skk_context_confirm_candidate_at_rs(context, i as usize)
}
#[no_mangle]
pub unsafe extern "C" fn skk_context_set_auto_start_henkan_keywords(
context: &mut CskkContext,
keywords_array: &*const c_char,
keywords_count: usize,
) {
let mut keywords = vec![];
if keywords_count < 1 || keywords_array.is_null() {
skk_context_set_auto_start_henkan_keywords_rs(context, keywords);
return;
}
let tmp_array = slice::from_raw_parts(keywords_array, keywords_count);
for raw_c_keyword in tmp_array {
let ckeyword = CStr::from_ptr(*raw_c_keyword);
if let Ok(keyword_str) = ckeyword.to_str() {
keywords.push(keyword_str.to_string())
}
}
skk_context_set_auto_start_henkan_keywords_rs(context, keywords);
}
#[no_mangle]
pub extern "C" fn skk_context_set_period_style(
context: &mut CskkContext,
period_style: PeriodStyle,
) {
skk_context_set_period_style_rs(context, period_style)
}
#[no_mangle]
pub extern "C" fn skk_context_set_comma_style(context: &mut CskkContext, comma_style: CommaStyle) {
skk_context_set_comma_style_rs(context, comma_style)
}
#[no_mangle]
pub extern "C" fn skk_library_get_version() -> *mut c_char {
let maybe_result = CString::new(CskkContext::get_version());
if let Ok(result) = maybe_result {
result.into_raw()
} else {
ptr::null_mut()
}
}
#[no_mangle]
pub unsafe extern "C" fn skk_get_rules(length: *mut c_uint) -> *mut CskkRulesFfi {
let mut retval_stack = vec![];
let maybe_count = (|| -> anyhow::Result<usize> {
let rulemap = get_available_rules()?;
let count = rulemap.len();
for (key, metadataentry) in rulemap {
let rule = CskkRulesFfi::new(&key, &metadataentry.name, &metadataentry.description)?;
retval_stack.push(rule);
}
retval_stack.set_len(count);
*length = u32::try_from(count)?;
Ok(count)
})();
if let Ok(count) = maybe_count {
if count > 0 {
let mut retval = ManuallyDrop::new(retval_stack);
retval.as_mut_ptr()
} else {
ptr::null_mut()
}
} else {
ptr::null_mut()
}
}
unsafe fn dictionaries_from_c_repr(
dictionary_array: &*mut CskkDictionaryFfi,
dictionary_count: usize,
) -> Vec<Arc<CskkDictionary>> {
let mut dict_array = vec![];
if dictionary_count < 1 || dictionary_array.is_null() {
return dict_array;
}
let tmp_array = slice::from_raw_parts(dictionary_array, dictionary_count);
for dictref in tmp_array {
let cskkdict = Box::from_raw(*dictref);
dict_array.push(cskkdict.dictionary.clone());
Box::into_raw(cskkdict);
}
dict_array
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn rulesffi() {
let rule = CskkRulesFfi::new("id", "name", "description").unwrap();
unsafe {
assert_eq!(b'i', *rule.id as u8);
assert_eq!(b'd', *rule.id.offset(1) as u8);
assert_eq!(b'\0', *rule.id.offset(2) as u8);
}
}
}