Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
171 changes: 171 additions & 0 deletions vnkey-engine/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions vnkey-engine/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ name = "vnkey_engine"
crate-type = ["lib", "staticlib"]

[dependencies]
num_enum = "0.7"
once_cell = "1"
41 changes: 30 additions & 11 deletions vnkey-engine/src/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,40 +8,38 @@
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use std::ptr;
use std::sync::Mutex;
use std::sync::{Mutex, MutexGuard};

use crate::engine::Engine;
use crate::input::InputMethod;
use crate::charset::{self, Charset};

static ENGINE: Mutex<Option<Engine>> = Mutex::new(None);

fn lock_recover<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
mutex.lock().unwrap_or_else(|poisoned| poisoned.into_inner())
}

fn with_engine<F, R>(f: F) -> R
where
F: FnOnce(&mut Engine) -> R,
R: Default,
{
let mut guard = match ENGINE.lock() {
Ok(g) => g,
Err(_) => return R::default(),
};
if guard.is_none() {
*guard = Some(Engine::new());
}
f(guard.as_mut().unwrap())
let mut guard = lock_recover(&ENGINE);
f(guard.get_or_insert_with(Engine::new))
}

/// Khởi tạo engine. Gọi một lần khi khởi động.
#[no_mangle]
pub extern "C" fn vnkey_setup() {
let mut guard = ENGINE.lock().unwrap();
let mut guard = lock_recover(&ENGINE);
*guard = Some(Engine::new());
}

/// Tắt engine và giải phóng tài nguyên.
#[no_mangle]
pub extern "C" fn vnkey_cleanup() {
let mut guard = ENGINE.lock().unwrap();
let mut guard = lock_recover(&ENGINE);
*guard = None;
}

Expand Down Expand Up @@ -639,3 +637,24 @@ pub unsafe extern "C" fn vnkey_app_charset_update(exe_name: *const c_char) {
pub extern "C" fn vnkey_app_charset_get_current() -> i32 {
crate::app_charset::get_current_app_charset().unwrap_or(-1)
}

#[cfg(test)]
mod tests {
use super::lock_recover;
use std::sync::{Arc, Mutex};

#[test]
fn poisoned_mutex_is_recovered_without_panicking() {
let mutex = Arc::new(Mutex::new(7));
let poison_target = Arc::clone(&mutex);

let _ = std::thread::spawn(move || {
let _guard = poison_target.lock().expect("test mutex should start healthy");
panic!("poison test mutex");
})
.join();

*lock_recover(&mutex) = 9;
assert_eq!(*lock_recover(&mutex), 9);
}
}
21 changes: 14 additions & 7 deletions vnkey-engine/src/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ pub enum InputMethod {
}

/// Loại sự kiện phím
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, num_enum::TryFromPrimitive)]
#[repr(u8)]
pub enum KeyEvType {
RoofAll = 0,
Expand Down Expand Up @@ -43,12 +43,7 @@ pub enum KeyEvType {
impl KeyEvType {
/// An toàn: chuyển u8 sang KeyEvType, trả về Normal nếu ngoài phạm vi
pub fn from_u8(v: u8) -> Self {
if v <= KeyEvType::Normal as u8 {
// SAFETY: repr(u8), các variant liên tục 0..=Normal, đã kiểm tra biên
unsafe { std::mem::transmute(v) }
} else {
KeyEvType::Normal
}
Self::try_from(v).unwrap_or(Self::Normal)
}
}

Expand Down Expand Up @@ -285,3 +280,15 @@ impl Default for InputProcessor {
Self::new()
}
}

#[cfg(test)]
mod tests {
use super::KeyEvType;

#[test]
fn key_event_conversion_rejects_out_of_range_values() {
assert_eq!(KeyEvType::from_u8(KeyEvType::RoofAll as u8), KeyEvType::RoofAll);
assert_eq!(KeyEvType::from_u8(KeyEvType::Normal as u8), KeyEvType::Normal);
assert_eq!(KeyEvType::from_u8(u8::MAX), KeyEvType::Normal);
}
}
42 changes: 24 additions & 18 deletions vnkey-engine/src/vnlexi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
/// Tên ký tự tiếng Việt. Chẵn = hoa, lẻ = thường.
/// Dấu: base, 1(sắc), 2(huyền), 3(hỏi), 4(ngã), 5(nặng)
/// Hậu tố: r = mũ (â,ê,ô), b = trăng (ă), h = móc (ơ,ư)
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, num_enum::TryFromPrimitive)]
#[repr(i16)]
#[allow(non_camel_case_types)]
pub enum VnLexiName {
Expand Down Expand Up @@ -55,11 +55,9 @@ use VnLexiName::*;
impl VnLexiName {
/// An toàn: chuyển i16 sang VnLexiName, trả về NonVnChar nếu ngoài phạm vi
pub fn from_i16(val: i16) -> Self {
if val >= VnLexiName::NonVnChar as i16 && val < VnLexiName::LastChar as i16 {
// SAFETY: repr(i16), variant liên tục -1..LastChar, đã kiểm tra biên
unsafe { std::mem::transmute(val) }
} else {
VnLexiName::NonVnChar
match Self::try_from(val) {
Ok(Self::LastChar) | Err(_) => Self::NonVnChar,
Ok(value) => value,
}
}

Expand Down Expand Up @@ -177,7 +175,7 @@ pub fn get_tone(sym: VnLexiName) -> i32 {

// ============= Chuỗi nguyên âm =============

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, num_enum::TryFromPrimitive)]
#[repr(i16)]
#[allow(non_camel_case_types)]
pub enum VowelSeq {
Expand Down Expand Up @@ -215,19 +213,15 @@ pub enum VowelSeq {

impl VowelSeq {
pub fn from_i16(val: i16) -> Self {
if val >= VowelSeq::Nil as i16 && val <= VowelSeq::VS_YERU as i16 {
unsafe { std::mem::transmute(val) }
} else {
VowelSeq::Nil
}
Self::try_from(val).unwrap_or(Self::Nil)
}
}

use VowelSeq::*;

// ============= Chuỗi phụ âm =============

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, num_enum::TryFromPrimitive)]
#[repr(i16)]
#[allow(non_camel_case_types)]
pub enum ConSeq {
Expand All @@ -245,11 +239,7 @@ pub enum ConSeq {

impl ConSeq {
pub fn from_i16(val: i16) -> Self {
if val >= ConSeq::Nil as i16 && val <= ConSeq::CS_X as i16 {
unsafe { std::mem::transmute(val) }
} else {
ConSeq::Nil
}
Self::try_from(val).unwrap_or(Self::Nil)
}
}

Expand Down Expand Up @@ -590,3 +580,19 @@ pub fn iso_to_vn_lexi(key_code: u32) -> VnLexiName {
_ => NonVnChar,
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn primitive_conversions_reject_sentinels_and_out_of_range_values() {
assert_eq!(VnLexiName::from_i16(VnLexiName::A as i16), VnLexiName::A);
assert_eq!(VnLexiName::from_i16(VnLexiName::LastChar as i16), VnLexiName::NonVnChar);
assert_eq!(VnLexiName::from_i16(i16::MAX), VnLexiName::NonVnChar);
assert_eq!(VowelSeq::from_i16(VowelSeq::VS_YERU as i16), VowelSeq::VS_YERU);
assert_eq!(VowelSeq::from_i16(i16::MAX), VowelSeq::Nil);
assert_eq!(ConSeq::from_i16(ConSeq::CS_X as i16), ConSeq::CS_X);
assert_eq!(ConSeq::from_i16(i16::MAX), ConSeq::Nil);
}
}