use std::collections::HashMap; use syscall::data::Stat; use syscall::error::{Error, Result, EBADF, EINVAL, ENOENT}; use syscall::flag::{MODE_DIR, MODE_FILE}; #[derive(Clone, Debug, Default)] struct InputState { composing: bool, preedit: String, committed: String, } enum HandleKind { Root, State, Compose, Candidates, } struct Handle { kind: HandleKind, offset: usize, } pub struct ImeScheme { next_id: usize, handles: HashMap, state: InputState, engine: Box, } trait ImeEngine { fn feed_key(&mut self, scancode: u8, pressed: bool, shift: bool, altgr: bool) -> ImeResult; fn reset(&mut self); fn candidates(&self) -> &[String]; } #[derive(Clone, Debug)] struct ImeResult { committed: Option, preedit: String, candidates_changed: bool, } struct BasicLatinEngine { dead_key: Option, candidates: Vec, compose_scancode: u8, dead_key_index: usize, } impl BasicLatinEngine { fn new() -> Self { BasicLatinEngine { dead_key: None, candidates: Vec::new(), compose_scancode: 0x5D, dead_key_index: 0, } } fn compose_dead(&self, base: char) -> Option { let dk = self.dead_key?; match (dk, base) { ('\u{0300}', 'a') => Some('à'), ('\u{0300}', 'e') => Some('è'), ('\u{0300}', 'i') => Some('ì'), ('\u{0300}', 'o') => Some('ò'), ('\u{0300}', 'u') => Some('ù'), ('\u{0301}', 'a') => Some('á'), ('\u{0301}', 'e') => Some('é'), ('\u{0301}', 'i') => Some('í'), ('\u{0301}', 'o') => Some('ó'), ('\u{0301}', 'u') => Some('ú'), ('\u{0302}', 'a') => Some('â'), ('\u{0302}', 'e') => Some('ê'), ('\u{0302}', 'i') => Some('î'), ('\u{0302}', 'o') => Some('ô'), ('\u{0302}', 'u') => Some('û'), ('\u{0308}', 'a') => Some('ä'), ('\u{0308}', 'e') => Some('ë'), ('\u{0308}', 'i') => Some('ï'), ('\u{0308}', 'o') => Some('ö'), ('\u{0308}', 'u') => Some('ü'), ('\u{030C}', 'a') => Some('ǎ'), ('\u{030C}', 'e') => Some('ě'), ('\u{030C}', 'i') => Some('ǐ'), ('\u{030C}', 'o') => Some('ǒ'), ('\u{030C}', 'u') => Some('ǔ'), _ => None, } } fn scancode_to_char(scancode: u8, shift: bool) -> Option { let lower = match scancode { 0x02 => '1', 0x03 => '2', 0x04 => '3', 0x05 => '4', 0x06 => '5', 0x07 => '6', 0x08 => '7', 0x09 => '8', 0x0A => '9', 0x0B => '0', 0x10 => 'q', 0x11 => 'w', 0x12 => 'e', 0x13 => 'r', 0x14 => 't', 0x15 => 'y', 0x16 => 'u', 0x17 => 'i', 0x18 => 'o', 0x19 => 'p', 0x1E => 'a', 0x1F => 's', 0x20 => 'd', 0x21 => 'f', 0x22 => 'g', 0x23 => 'h', 0x24 => 'j', 0x25 => 'k', 0x26 => 'l', 0x2C => 'z', 0x2D => 'x', 0x2E => 'c', 0x2F => 'v', 0x30 => 'b', 0x31 => 'n', 0x32 => 'm', 0x27 => ';', 0x28 => '\'', 0x29 => '`', 0x33 => ',', 0x34 => '.', 0x35 => '/', 0x0C => '-', 0x0D => '=', _ => return None, }; if shift { Some(lower.to_ascii_uppercase()) } else { Some(lower) } } } impl ImeEngine for BasicLatinEngine { fn feed_key(&mut self, scancode: u8, pressed: bool, shift: bool, _altgr: bool) -> ImeResult { if !pressed { return ImeResult { committed: None, preedit: String::new(), candidates_changed: false, }; } if let Some(current_dead) = self.dead_key.take() { if let Some(base) = Self::scancode_to_char(scancode, shift) { match self.compose_dead(base) { Some(composed) => { self.candidates.clear(); return ImeResult { committed: Some(composed), preedit: String::new(), candidates_changed: true, }; } None => { self.dead_key = None; return ImeResult { committed: Some(current_dead), preedit: String::new(), candidates_changed: false, }; } } } else { self.dead_key = Some(current_dead); return ImeResult { committed: None, preedit: current_dead.to_string(), candidates_changed: false, }; } } let is_dead_trigger = scancode == self.compose_scancode; if is_dead_trigger { let dead_keys = [ ('\u{0300}', "grave"), ('\u{0301}', "acute"), ('\u{0302}', "circumflex"), ('\u{0308}', "diaeresis"), ('\u{030C}', "caron"), ]; let (dk, _name) = dead_keys[self.dead_key_index % dead_keys.len()]; self.dead_key_index = (self.dead_key_index + 1) % dead_keys.len(); self.dead_key = Some(dk); self.candidates = vec![dk.to_string()]; return ImeResult { committed: None, preedit: dk.to_string(), candidates_changed: true, }; } ImeResult { committed: None, preedit: String::new(), candidates_changed: false, } } fn reset(&mut self) { self.dead_key = None; self.candidates.clear(); } fn candidates(&self) -> &[String] { &self.candidates } } impl ImeScheme { pub fn new() -> Self { ImeScheme { next_id: 1, handles: HashMap::new(), state: InputState::default(), engine: Box::new(BasicLatinEngine::new()), } } pub fn feed(&mut self, scancode: u8, pressed: bool, shift: bool, altgr: bool) { let result = self.engine.feed_key(scancode, pressed, shift, altgr); if let Some(ch) = result.committed { self.state.committed.push(ch); self.state.composing = false; self.state.preedit.clear(); } if result.candidates_changed { self.state.composing = !self.engine.candidates().is_empty(); } if !result.preedit.is_empty() { self.state.composing = true; self.state.preedit = result.preedit; } } } impl redox_scheme::SchemeBlockMut for ImeScheme { fn open(&mut self, path: &str, _flags: usize, _uid: u32, _gid: u32) -> Result> { let cleaned = path.trim_matches('/'); let kind = match cleaned { "" => HandleKind::Root, "state" => HandleKind::State, "compose" => HandleKind::Compose, "candidates" => HandleKind::Candidates, _ => return Err(Error::new(ENOENT)), }; let id = self.next_id; self.next_id += 1; self.handles.insert(id, Handle { kind, offset: 0 }); Ok(Some(id)) } fn read(&mut self, id: usize, buf: &mut [u8]) -> Result> { let handle = self.handles.get_mut(&id).ok_or(Error::new(EBADF))?; let content: Vec = match &handle.kind { HandleKind::Root => "state\ncompose\ncandidates\n".as_bytes().to_vec(), HandleKind::State => format!( "composing={}\npreedit={}\ncommitted={}\n", self.state.composing, self.state.preedit, self.state.committed ) .into_bytes(), HandleKind::Compose => "basic-latin\n".as_bytes().to_vec(), HandleKind::Candidates => { let mut out = String::new(); for (i, c) in self.engine.candidates().iter().enumerate() { out.push_str(&format!("{}\t{}\n", i, c)); } out.into_bytes() } }; if handle.offset >= content.len() { return Ok(Some(0)); } let remaining = &content[handle.offset..]; let to_copy = remaining.len().min(buf.len()); buf[..to_copy].copy_from_slice(&remaining[..to_copy]); handle.offset += to_copy; Ok(Some(to_copy)) } fn write(&mut self, id: usize, buf: &[u8]) -> Result> { let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?; match &handle.kind { HandleKind::Compose => { let input = String::from_utf8_lossy(buf); for line in input.lines() { let line = line.trim(); if line == "reset" { self.engine.reset(); self.state = InputState::default(); continue; } let mut fields = line.split_whitespace(); let Some("feed") = fields.next() else { continue; }; let Some(scancode) = fields.next().and_then(|field| field.parse::().ok()) else { continue; }; let Some(pressed) = fields.next().and_then(|field| field.parse::().ok()) else { continue; }; let shift = fields .next() .and_then(|field| field.parse::().ok()) .unwrap_or(false); let altgr = fields .next() .and_then(|field| field.parse::().ok()) .unwrap_or(false); self.feed(scancode, pressed, shift, altgr); } Ok(Some(buf.len())) } _ => Err(Error::new(EINVAL)), } } fn fstat(&mut self, id: usize, stat: &mut Stat) -> Result> { let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?; match &handle.kind { HandleKind::Root => stat.st_mode = MODE_DIR | 0o555, _ => stat.st_mode = MODE_FILE | 0o644, } Ok(Some(0)) } fn close(&mut self, id: usize) -> Result> { self.handles.remove(&id); Ok(Some(0)) } }