1mod convert;
2pub mod frontend;
3
4pub use afrim_config::Config;
5use afrim_preprocessor::{utils, Command as EventCmd, Preprocessor};
6use afrim_translator::Translator;
7use anyhow::{Context, Result};
8use enigo::{Enigo, Key, KeyboardControllable};
9use frontend::{Command as GUICmd, Frontend};
10use rdev::{self, EventType, Key as E_Key};
11use std::{rc::Rc, sync::mpsc, thread};
12
13pub fn run(
15 config: Config,
16 mut frontend: impl Frontend + std::marker::Send + 'static,
17) -> Result<()> {
18 let mut is_ctrl_released = true;
20 let mut idle = false;
21
22 let memory = utils::build_map(
24 config
25 .extract_data()
26 .iter()
27 .map(|(key, value)| vec![key.as_str(), value.as_str()])
28 .collect(),
29 );
30 let (buffer_size, auto_commit, page_size) = config
31 .core
32 .as_ref()
33 .map(|core| {
34 (
35 core.buffer_size.unwrap_or(32),
36 core.auto_commit.unwrap_or(false),
37 core.page_size.unwrap_or(10),
38 )
39 })
40 .unwrap_or((32, false, 10));
41 let mut keyboard = Enigo::new();
42 let mut preprocessor = Preprocessor::new(Rc::new(memory), buffer_size);
43 #[cfg(not(feature = "rhai"))]
44 let translator = Translator::new(config.extract_translation(), auto_commit);
45 #[cfg(feature = "rhai")]
46 let mut translator = Translator::new(config.extract_translation(), auto_commit);
47 #[cfg(feature = "rhai")]
48 config
49 .extract_translators()
50 .context("Failed to load translators.")?
51 .into_iter()
52 .for_each(|(name, ast)| translator.register(name, ast));
53
54 let (frontend_tx1, frontend_rx1) = mpsc::channel();
56 let (frontend_tx2, frontend_rx2) = mpsc::channel();
57
58 frontend_tx1.send(GUICmd::PageSize(page_size))?;
59 let screen_size = rdev::display_size().unwrap();
60 frontend_tx1.send(GUICmd::ScreenSize(screen_size))?;
61
62 let frontend_thread = thread::spawn(move || {
63 frontend
64 .init(frontend_tx2, frontend_rx1)
65 .context("Failure to initialize the frontend.")
66 .unwrap();
67 frontend
68 .listen()
69 .context("The frontend raise an unexpected error.")
70 .unwrap();
71 });
72
73 let (event_tx, event_rx) = mpsc::channel();
75 thread::spawn(move || {
76 rdev::listen(move |event| {
77 event_tx
78 .send(event)
79 .unwrap_or_else(|e| eprintln!("Could not send event {:?}", e));
80 })
81 .expect("Could not listen");
82 });
83
84 for event in event_rx.iter() {
86 match event.event_type {
87 EventType::KeyPress(E_Key::Pause) => {
89 idle = true;
90 frontend_tx1.send(GUICmd::State(idle))?;
91 }
92 EventType::KeyRelease(E_Key::Pause) => {
93 idle = false;
94 frontend_tx1.send(GUICmd::State(idle))?;
95 }
96 EventType::KeyPress(E_Key::ControlLeft | E_Key::ControlRight) => {
97 is_ctrl_released = false;
98 }
99 EventType::KeyRelease(E_Key::ControlLeft | E_Key::ControlRight) if is_ctrl_released => {
100 idle = !idle;
101 frontend_tx1.send(GUICmd::State(idle))?;
102 }
103 EventType::KeyRelease(E_Key::ControlLeft | E_Key::ControlRight) => {
104 is_ctrl_released = true;
105 }
106 _ if idle => (),
107 EventType::KeyRelease(E_Key::ShiftRight) if !is_ctrl_released => {
109 frontend_tx1.send(GUICmd::SelectNextPredicate)?;
110 }
111 EventType::KeyRelease(E_Key::ShiftLeft) if !is_ctrl_released => {
112 frontend_tx1.send(GUICmd::SelectPreviousPredicate)?;
113 }
114 EventType::KeyRelease(E_Key::Space) if !is_ctrl_released => {
115 rdev::simulate(&EventType::KeyRelease(E_Key::ControlLeft))
116 .expect("We couldn't cancel the special function key");
117
118 frontend_tx1.send(GUICmd::SelectedPredicate)?;
119 if let GUICmd::Predicate(predicate) = frontend_rx2.recv()? {
120 preprocessor.commit(
121 predicate
122 .texts
123 .first()
124 .unwrap_or(&String::default())
125 .to_owned(),
126 );
127 frontend_tx1.send(GUICmd::Clear)?;
128 }
129 }
130 _ if !is_ctrl_released => (),
131 EventType::MouseMove { x, y } => {
133 frontend_tx1.send(GUICmd::Position((x, y)))?;
134 }
135 _ => {
137 let (changed, _committed) = preprocessor.process(convert::from_event(event));
138
139 if changed {
140 let input = preprocessor.get_input();
141
142 frontend_tx1.send(GUICmd::Clear)?;
143
144 translator
145 .translate(&input)
146 .into_iter()
147 .take(page_size * 2)
148 .try_for_each(|predicate| -> Result<()> {
149 if predicate.texts.is_empty() {
150 } else if auto_commit && predicate.can_commit {
151 preprocessor.commit(predicate.texts[0].to_owned());
152 } else {
153 frontend_tx1.send(GUICmd::Predicate(predicate))?;
154 }
155
156 Ok(())
157 })?;
158
159 frontend_tx1.send(GUICmd::InputText(input))?;
160 frontend_tx1.send(GUICmd::Update)?;
161 }
162 }
163 }
164
165 while let Some(command) = preprocessor.pop_queue() {
167 match command {
168 EventCmd::CommitText(text) => {
169 keyboard.key_sequence(&text);
170 }
171 EventCmd::CleanDelete => {
172 keyboard.key_up(Key::Backspace);
173 }
174 EventCmd::Delete => {
175 keyboard.key_click(Key::Backspace);
176 }
177 EventCmd::Pause => {
178 rdev::simulate(&EventType::KeyPress(E_Key::Pause)).unwrap();
179 }
180 EventCmd::Resume => {
181 rdev::simulate(&EventType::KeyRelease(E_Key::Pause)).unwrap();
182 }
183 };
184 }
185
186 frontend_tx1.send(GUICmd::NOP)?;
188 match frontend_rx2.recv()? {
189 GUICmd::End => break,
190 GUICmd::State(state) => {
191 idle = state;
192 frontend_tx1.send(GUICmd::State(idle))?;
193 }
194 _ => (),
195 }
196 }
197
198 frontend_thread.join().unwrap();
200
201 Ok(())
202}
203
204#[cfg(test)]
205mod tests {
206 use crate::{frontend::Console, run, Config};
207 use rdev::{self, Button, EventType::*, Key::*};
208 use rstk::{self, TkPackLayout};
209 use std::{thread, time::Duration};
210
211 macro_rules! input {
212 ( $( $key:expr )*, $delay:expr ) => (
213 $(
214 thread::sleep($delay);
215 rdev::simulate(&KeyPress($key)).unwrap();
216 rdev::simulate(&KeyRelease($key)).unwrap();
217 )*
218 );
219 }
220
221 macro_rules! output {
222 ( $textfield: expr, $expected: expr ) => {
223 thread::sleep(Duration::from_millis(500));
224
225 loop {
227 let a = $textfield.get_to_end((1, 0));
228 let b = $textfield.get_to_end((1, 0));
229
230 if (a == b) {
231 let content = a.chars().filter(|c| *c != '\0').collect::<String>();
232 let content = content.trim();
233
234 assert_eq!(content, $expected);
235 break;
236 }
237 }
238 };
239 }
240
241 fn start_sandbox(start_point: &str) -> rstk::TkText {
242 let root = rstk::trace_with("wish").unwrap();
243 root.title("Afrim Test Environment");
244
245 let input_field = rstk::make_text(&root);
246 input_field.width(50);
247 input_field.height(12);
248 input_field.pack().layout();
249 root.geometry(200, 200, 0, 0);
250 input_field.insert((1, 1), start_point);
251 rstk::tell_wish(
252 r#"
253 chan configure stdout -encoding utf-8;
254 wm protocol . WM_DELETE_WINDOW {destroy .};
255 "#,
256 );
257 thread::sleep(Duration::from_secs(1));
258 input_field
259 }
260
261 fn end_sandbox() {
262 rstk::end_wish();
263 }
264
265 fn start_simulation() {
266 let typing_speed_ms = Duration::from_millis(500);
267
268 const LIMIT: &str = "bbb";
270
271 let textfield = start_sandbox(LIMIT);
273
274 rdev::simulate(&MouseMove { x: 100.0, y: 100.0 }).unwrap();
275 thread::sleep(typing_speed_ms);
276 rdev::simulate(&ButtonPress(Button::Left)).unwrap();
277 thread::sleep(typing_speed_ms);
278 rdev::simulate(&ButtonRelease(Button::Left)).unwrap();
279 thread::sleep(typing_speed_ms);
280
281 input!(KeyU, typing_speed_ms);
282 #[cfg(not(feature = "inhibit"))]
283 input!(Backspace, typing_speed_ms);
284 input!(KeyU KeyU Backspace KeyU, typing_speed_ms);
285 input!(
286 KeyC Num8 KeyC KeyE KeyD
287 KeyU KeyU
288 KeyA KeyF Num3, typing_speed_ms);
289 input!(
290 KeyA KeyF KeyA KeyF
291 KeyA KeyF KeyF Num3, typing_speed_ms);
292 input!(KeyU KeyU Num3, typing_speed_ms);
293 #[cfg(feature = "inhibit")]
294 output!(textfield, format!("{LIMIT}çʉ̄ɑ̄ɑɑɑ̄ɑ̄ʉ̄"));
295 #[cfg(not(feature = "inhibit"))]
296 output!(textfield, format!("{LIMIT}uçʉ̄ɑ̄ɑɑɑ̄ɑ̄ʉ̄"));
297
298 #[cfg(not(feature = "inhibit"))]
300 (0..12).for_each(|_| {
301 input!(Backspace, typing_speed_ms);
302 });
303 #[cfg(feature = "inhibit")]
304 (0..13).for_each(|_| {
305 input!(Backspace, typing_speed_ms);
306 });
307 output!(textfield, LIMIT);
308
309 rdev::simulate(&KeyPress(ControlLeft)).unwrap();
311 rdev::simulate(&KeyPress(ControlRight)).unwrap();
312 rdev::simulate(&KeyRelease(ControlRight)).unwrap();
313 rdev::simulate(&KeyRelease(ControlLeft)).unwrap();
314 input!(KeyU KeyU, typing_speed_ms);
315
316 rdev::simulate(&KeyPress(ControlLeft)).unwrap();
317 rdev::simulate(&KeyPress(ControlRight)).unwrap();
318 rdev::simulate(&KeyRelease(ControlRight)).unwrap();
319 rdev::simulate(&KeyRelease(ControlLeft)).unwrap();
320 input!(KeyA KeyF, typing_speed_ms);
321 output!(textfield, format!("{LIMIT}uuɑ"));
322 input!(Escape, typing_speed_ms);
323
324 input!(CapsLock KeyA CapsLock KeyF, typing_speed_ms);
326 input!(CapsLock KeyA CapsLock KeyF KeyF, typing_speed_ms);
327 input!(KeyA KeyF KeyF, typing_speed_ms);
328 output!(textfield, format!("{LIMIT}uuɑαⱭⱭɑɑ"));
329 input!(Escape, typing_speed_ms);
330
331 input!(KeyH KeyE KeyL KeyL KeyO, typing_speed_ms);
333 output!(textfield, format!("{LIMIT}uuɑαⱭⱭɑɑhi"));
334 #[cfg(not(feature = "rhai"))]
335 input!(Escape KeyH Escape KeyE KeyL KeyL KeyO, typing_speed_ms);
336 #[cfg(feature = "rhai")]
337 input!(Escape KeyH KeyI, typing_speed_ms);
338 output!(textfield, format!("{LIMIT}uuɑαⱭⱭɑɑhihello"));
339 input!(Escape, typing_speed_ms);
340
341 input!(KeyH KeyE, typing_speed_ms);
343 rdev::simulate(&KeyPress(ControlLeft)).unwrap();
344 input!(ShiftLeft, typing_speed_ms);
345 input!(ShiftRight, typing_speed_ms);
346 rdev::simulate(&KeyRelease(ControlLeft)).unwrap();
347
348 input!(KeyA, typing_speed_ms);
349 rdev::simulate(&KeyPress(ControlLeft)).unwrap();
350 input!(Space, typing_speed_ms);
351 rdev::simulate(&KeyRelease(ControlLeft)).unwrap();
352 output!(textfield, format!("{LIMIT}uuɑαⱭⱭɑɑhihellohealth"));
353 input!(Escape, typing_speed_ms);
354
355 input!(KeyV KeyU KeyU KeyE, typing_speed_ms);
358 output!(textfield, format!("{LIMIT}uuɑαⱭⱭɑɑhihellohealthvʉe"));
359
360 input!(Escape Num8 KeyS KeyT KeyQ KeyT KeyE Num8, typing_speed_ms);
362 input!(Escape, typing_speed_ms);
363 rdev::simulate(&KeyPress(ShiftLeft)).unwrap();
364 input!(Minus, typing_speed_ms);
365 rdev::simulate(&KeyRelease(ShiftLeft)).unwrap();
366 input!(KeyS KeyT KeyA KeyT KeyE, typing_speed_ms);
367 rdev::simulate(&KeyPress(ShiftLeft)).unwrap();
368 input!(Minus, typing_speed_ms);
369 rdev::simulate(&KeyRelease(ShiftLeft)).unwrap();
370
371 input!(Escape Num8 KeyE KeyX KeyI KeyT Num8, typing_speed_ms);
373 input!(Escape, typing_speed_ms);
374 rdev::simulate(&KeyPress(ShiftLeft)).unwrap();
375 input!(Minus, typing_speed_ms);
376 rdev::simulate(&KeyRelease(ShiftLeft)).unwrap();
377 input!(KeyE KeyX KeyI KeyT, typing_speed_ms);
378 rdev::simulate(&KeyPress(ShiftLeft)).unwrap();
379 input!(Minus, typing_speed_ms);
380 rdev::simulate(&KeyRelease(ShiftLeft)).unwrap();
381
382 end_sandbox();
383 }
384
385 #[test]
386 fn test_afrim() {
387 use std::path::Path;
388
389 let simulation_thread = thread::spawn(start_simulation);
390
391 let test_config = Config::from_file(Path::new("./data/test.toml")).unwrap();
392 assert!(run(test_config, Console::default()).is_ok());
393
394 simulation_thread.join().unwrap();
396 }
397}