1use rand::Rng;
2use rustenium_bidi_definitions::browsing_context::types::BrowsingContext;
3use rustenium_bidi_definitions::input::command_builders::PerformActionsBuilder;
4use rustenium_bidi_definitions::input::type_builders::{
5 KeySourceActionsBuilder, KeyDownActionBuilder, KeyUpActionBuilder, PauseActionBuilder,
6};
7use rustenium_bidi_definitions::input::types::{
8 KeySourceActionsType, KeyDownActionType, KeyUpActionType, PauseActionType,
9};
10use rustenium_core::error::CommandResultError;
11use rustenium_core::BidiSession;
12use rustenium_core::transport::ConnectionTransport;
13use crate::error::bidi::InputError;
14use std::sync::Arc;
15use tokio::sync::Mutex;
16
17use super::KEYBOARD_ID;
18
19#[derive(Debug, Clone, Copy)]
30pub struct DelayRange {
31 pub min: u64,
32 pub max: u64,
33}
34
35impl DelayRange {
36 pub fn new(min: u64, max: u64) -> Option<Self> {
38 if max == 0 || min > max { return None; }
39 Some(Self { min, max })
40 }
41}
42
43#[derive(Debug, Clone, Default)]
47pub struct KeyPressOptions {
48 pub delay: Option<DelayRange>,
49}
50
51#[derive(Default, Clone)]
52pub struct KeyPressOptionsBuilder {
53 delay: Option<DelayRange>,
54}
55
56impl KeyPressOptionsBuilder {
57 pub fn delay(mut self, min: u64, max: u64) -> Self { self.delay = DelayRange::new(min, max); self }
58 pub fn build(self) -> KeyPressOptions { KeyPressOptions { delay: self.delay } }
59}
60
61#[derive(Debug, Clone)]
71pub struct KeyboardTypeOptions {
72 pub delay: Option<DelayRange>,
73 pub gap_multiplier: f64,
74}
75
76impl Default for KeyboardTypeOptions {
77 fn default() -> Self {
78 Self { delay: None, gap_multiplier: 1.2 }
79 }
80}
81
82#[derive(Clone)]
83pub struct KeyboardTypeOptionsBuilder {
84 delay: Option<DelayRange>,
85 gap_multiplier: f64,
86}
87
88impl Default for KeyboardTypeOptionsBuilder {
89 fn default() -> Self {
90 Self { delay: None, gap_multiplier: 1.2 }
91 }
92}
93
94impl KeyboardTypeOptionsBuilder {
95 pub fn delay(mut self, min: u64, max: u64) -> Self { self.delay = DelayRange::new(min, max); self }
96 pub fn gap_multiplier(mut self, v: f64) -> Self { self.gap_multiplier = v; self }
97 pub fn build(self) -> KeyboardTypeOptions {
98 KeyboardTypeOptions { delay: self.delay, gap_multiplier: self.gap_multiplier }
99 }
100}
101
102pub(crate) fn get_bidi_key_value(key: &str) -> Result<String, InputError> {
103 let key = match key {
104 "\r" | "\n" => "Enter",
105 _ => key,
106 };
107
108 if key.chars().count() == 1 {
109 return Ok(key.to_string());
110 }
111
112 let value = match key {
113 "Cancel" => "\u{E001}",
114 "Help" => "\u{E002}",
115 "Backspace" => "\u{E003}",
116 "Tab" => "\u{E004}",
117 "Clear" => "\u{E005}",
118 "Enter" => "\u{E007}",
119 "Shift" | "ShiftLeft" => "\u{E008}",
120 "Control" | "ControlLeft" => "\u{E009}",
121 "Alt" | "AltLeft" => "\u{E00A}",
122 "Pause" => "\u{E00B}",
123 "Escape" => "\u{E00C}",
124 "PageUp" => "\u{E00E}",
125 "PageDown" => "\u{E00F}",
126 "End" => "\u{E010}",
127 "Home" => "\u{E011}",
128 "ArrowLeft" => "\u{E012}",
129 "ArrowUp" => "\u{E013}",
130 "ArrowRight" => "\u{E014}",
131 "ArrowDown" => "\u{E015}",
132 "Insert" => "\u{E016}",
133 "Delete" => "\u{E017}",
134 "NumpadEqual" => "\u{E019}",
135 "Numpad0" => "\u{E01A}",
136 "Numpad1" => "\u{E01B}",
137 "Numpad2" => "\u{E01C}",
138 "Numpad3" => "\u{E01D}",
139 "Numpad4" => "\u{E01E}",
140 "Numpad5" => "\u{E01F}",
141 "Numpad6" => "\u{E020}",
142 "Numpad7" => "\u{E021}",
143 "Numpad8" => "\u{E022}",
144 "Numpad9" => "\u{E023}",
145 "NumpadMultiply" => "\u{E024}",
146 "NumpadAdd" => "\u{E025}",
147 "NumpadSubtract" => "\u{E027}",
148 "NumpadDecimal" => "\u{E028}",
149 "NumpadDivide" => "\u{E029}",
150 "F1" => "\u{E031}",
151 "F2" => "\u{E032}",
152 "F3" => "\u{E033}",
153 "F4" => "\u{E034}",
154 "F5" => "\u{E035}",
155 "F6" => "\u{E036}",
156 "F7" => "\u{E037}",
157 "F8" => "\u{E038}",
158 "F9" => "\u{E039}",
159 "F10" => "\u{E03A}",
160 "F11" => "\u{E03B}",
161 "F12" => "\u{E03C}",
162 "Meta" | "MetaLeft" => "\u{E03D}",
163 "ShiftRight" => "\u{E050}",
164 "ControlRight" => "\u{E051}",
165 "AltRight" => "\u{E052}",
166 "MetaRight" => "\u{E053}",
167 "Digit0" => "0",
168 "Digit1" => "1",
169 "Digit2" => "2",
170 "Digit3" => "3",
171 "Digit4" => "4",
172 "Digit5" => "5",
173 "Digit6" => "6",
174 "Digit7" => "7",
175 "Digit8" => "8",
176 "Digit9" => "9",
177 "KeyA" => "a",
178 "KeyB" => "b",
179 "KeyC" => "c",
180 "KeyD" => "d",
181 "KeyE" => "e",
182 "KeyF" => "f",
183 "KeyG" => "g",
184 "KeyH" => "h",
185 "KeyI" => "i",
186 "KeyJ" => "j",
187 "KeyK" => "k",
188 "KeyL" => "l",
189 "KeyM" => "m",
190 "KeyN" => "n",
191 "KeyO" => "o",
192 "KeyP" => "p",
193 "KeyQ" => "q",
194 "KeyR" => "r",
195 "KeyS" => "s",
196 "KeyT" => "t",
197 "KeyU" => "u",
198 "KeyV" => "v",
199 "KeyW" => "w",
200 "KeyX" => "x",
201 "KeyY" => "y",
202 "KeyZ" => "z",
203 "Semicolon" => ";",
204 "Equal" => "=",
205 "Comma" => ",",
206 "Minus" => "-",
207 "Period" => ".",
208 "Slash" => "/",
209 "Backquote" => "`",
210 "BracketLeft" => "[",
211 "Backslash" => "\\",
212 "BracketRight" => "]",
213 "Quote" => "\"",
214 _ => return Err(InputError::UnknownKey(key.to_string())),
215 };
216
217 Ok(value.to_string())
218}
219
220pub struct BidiKeyboard<OT: ConnectionTransport> {
221 session: Arc<Mutex<BidiSession<OT>>>,
222}
223
224impl<OT: ConnectionTransport> BidiKeyboard<OT> {
225 pub fn new(session: Arc<Mutex<BidiSession<OT>>>) -> Self {
226 Self { session }
227 }
228
229 pub async fn down(&self, key: &str, context: &BrowsingContext) -> Result<(), InputError> {
230 tracing::debug!(key, "keyboard down start");
231 let key_value = get_bidi_key_value(key)?;
232
233 let command = PerformActionsBuilder::default()
234 .context(context.clone())
235 .action(
236 KeySourceActionsBuilder::default()
237 .r#type(KeySourceActionsType::Key)
238 .id(KEYBOARD_ID)
239 .action(KeyDownActionBuilder::default()
240 .r#type(KeyDownActionType::KeyDown)
241 .value(key_value)
242 .build().unwrap())
243 .build().unwrap()
244 )
245 .build().unwrap();
246
247 self.session.lock().await.send(command).await
248 .map_err(|e| InputError::CommandResultError(CommandResultError::SessionSendError(e)))?;
249 tracing::debug!(key, "keyboard down done");
250 Ok(())
251 }
252
253 pub async fn up(&self, key: &str, context: &BrowsingContext) -> Result<(), InputError> {
254 tracing::debug!(key, "keyboard up start");
255 let key_value = get_bidi_key_value(key)?;
256
257 let command = PerformActionsBuilder::default()
258 .context(context.clone())
259 .action(
260 KeySourceActionsBuilder::default()
261 .r#type(KeySourceActionsType::Key)
262 .id(KEYBOARD_ID)
263 .action(KeyUpActionBuilder::default()
264 .r#type(KeyUpActionType::KeyUp)
265 .value(key_value)
266 .build().unwrap())
267 .build().unwrap()
268 )
269 .build().unwrap();
270
271 self.session.lock().await.send(command).await
272 .map_err(|e| InputError::CommandResultError(CommandResultError::SessionSendError(e)))?;
273 tracing::debug!(key, "keyboard up done");
274 Ok(())
275 }
276
277 pub async fn press(
278 &self,
279 key: &str,
280 context: &BrowsingContext,
281 options: Option<KeyPressOptions>,
282 ) -> Result<(), InputError> {
283 tracing::debug!(key, "keyboard press start");
284 let key_value = get_bidi_key_value(key)?;
285 let delay = options.and_then(|o| o.delay);
286 let hold = delay.map(|range| {
287 let mut rng = rand::rng();
288 rng.random_range(range.min..=range.max)
289 });
290
291 let mut key_actions = KeySourceActionsBuilder::default()
292 .r#type(KeySourceActionsType::Key)
293 .id(KEYBOARD_ID)
294 .action(KeyDownActionBuilder::default()
295 .r#type(KeyDownActionType::KeyDown)
296 .value(key_value.clone())
297 .build().unwrap());
298
299 if let Some(h) = hold {
300 if h > 0 {
301 key_actions = key_actions.action(PauseActionBuilder::default()
302 .r#type(PauseActionType::Pause)
303 .duration(h)
304 .build().unwrap());
305 }
306 }
307
308 key_actions = key_actions.action(KeyUpActionBuilder::default()
309 .r#type(KeyUpActionType::KeyUp)
310 .value(key_value)
311 .build().unwrap());
312
313 let command = PerformActionsBuilder::default()
314 .context(context.clone())
315 .action(key_actions.build().unwrap())
316 .build().unwrap();
317
318 self.session.lock().await.send(command).await
319 .map_err(|e| InputError::CommandResultError(CommandResultError::SessionSendError(e)))?;
320 tracing::debug!(key, "keyboard press done");
321 Ok(())
322 }
323
324 pub async fn type_text(
325 &self,
326 text: &str,
327 context: &BrowsingContext,
328 options: Option<KeyboardTypeOptions>,
329 ) -> Result<(), InputError> {
330 tracing::debug!(text, "keyboard type_text start");
331 let options = options.unwrap_or_default();
332 let delay = options.delay;
333 let gap_multiplier = options.gap_multiplier;
334
335 let timings: Vec<(u64, u64)> = match delay {
336 None => vec![],
337 Some(range) => {
338 let mut rng = rand::rng();
339 text.chars().map(|_| {
340 let hold = rng.random_range(range.min..=range.max);
341 let gap = ((hold as f64) * gap_multiplier).round() as u64;
342 (hold, gap)
343 }).collect()
344 }
345 };
346
347 let mut key_actions = KeySourceActionsBuilder::default()
348 .r#type(KeySourceActionsType::Key)
349 .id(KEYBOARD_ID);
350
351 for (i, ch) in text.chars().enumerate() {
352 tracing::debug!(key = %ch, "keyboard type_text key");
353 let key_value = get_bidi_key_value(&ch.to_string())?;
354
355 key_actions = key_actions.action(KeyDownActionBuilder::default()
356 .r#type(KeyDownActionType::KeyDown)
357 .value(key_value.clone())
358 .build().unwrap());
359
360 if let Some(&(hold, _)) = timings.get(i) {
361 if hold > 0 {
362 key_actions = key_actions.action(PauseActionBuilder::default()
363 .r#type(PauseActionType::Pause)
364 .duration(hold)
365 .build().unwrap());
366 }
367 }
368
369 key_actions = key_actions.action(KeyUpActionBuilder::default()
370 .r#type(KeyUpActionType::KeyUp)
371 .value(key_value)
372 .build().unwrap());
373
374 if let Some(&(_, gap)) = timings.get(i) {
375 if gap > 0 {
376 key_actions = key_actions.action(PauseActionBuilder::default()
377 .r#type(PauseActionType::Pause)
378 .duration(gap)
379 .build().unwrap());
380 }
381 }
382 }
383
384 let command = PerformActionsBuilder::default()
385 .context(context.clone())
386 .action(key_actions.build().unwrap())
387 .build().unwrap();
388
389 self.session.lock().await.send(command).await
390 .map_err(|e| InputError::CommandResultError(CommandResultError::SessionSendError(e)))?;
391 tracing::debug!("keyboard type_text done");
392 Ok(())
393 }
394}
395
396impl<OT: ConnectionTransport> crate::input::keyboard::Keyboard for BidiKeyboard<OT> {
397 async fn down(&self, key: &str, context: &BrowsingContext) -> Result<(), InputError> {
398 self.down(key, context).await
399 }
400 async fn up(&self, key: &str, context: &BrowsingContext) -> Result<(), InputError> {
401 self.up(key, context).await
402 }
403 async fn press(&self, key: &str, context: &BrowsingContext, options: Option<KeyPressOptions>) -> Result<(), InputError> {
404 self.press(key, context, options).await
405 }
406 async fn type_text(&self, text: &str, context: &BrowsingContext, options: Option<KeyboardTypeOptions>) -> Result<(), InputError> {
407 self.type_text(text, context, options).await
408 }
409}
410
411#[cfg(test)]
412mod tests {
413 use super::get_bidi_key_value;
414
415 #[test]
416 fn single_char_keys_pass_through() {
417 for ch in ['a', 'Z', '5', '!', ' '] {
418 let result = get_bidi_key_value(&ch.to_string()).unwrap();
419 assert_eq!(result, ch.to_string());
420 }
421 }
422
423 #[test]
424 fn special_keys_map_correctly() {
425 let cases = vec![
426 ("Enter", "\u{E007}"),
427 ("Tab", "\u{E004}"),
428 ("Backspace", "\u{E003}"),
429 ("Escape", "\u{E00C}"),
430 ("ArrowUp", "\u{E013}"),
431 ("ArrowDown", "\u{E015}"),
432 ("ArrowLeft", "\u{E012}"),
433 ("ArrowRight", "\u{E014}"),
434 ("F1", "\u{E031}"),
435 ("F12", "\u{E03C}"),
436 ("Shift", "\u{E008}"),
437 ("Control", "\u{E009}"),
438 ("Alt", "\u{E00A}"),
439 ("Meta", "\u{E03D}"),
440 ("Delete", "\u{E017}"),
441 ("Home", "\u{E011}"),
442 ("End", "\u{E010}"),
443 ("PageUp", "\u{E00E}"),
444 ("PageDown", "\u{E00F}"),
445 ];
446 for (key, expected) in cases {
447 assert_eq!(get_bidi_key_value(key).unwrap(), expected, "failed for key: {}", key);
448 }
449 }
450
451 #[test]
452 fn code_style_keys_map_to_chars() {
453 let cases = vec![
454 ("KeyA", "a"), ("KeyZ", "z"),
455 ("Digit0", "0"), ("Digit9", "9"),
456 ("Semicolon", ";"), ("Slash", "/"),
457 ("BracketLeft", "["), ("BracketRight", "]"),
458 ];
459 for (key, expected) in cases {
460 assert_eq!(get_bidi_key_value(key).unwrap(), expected, "failed for key: {}", key);
461 }
462 }
463
464 #[test]
465 fn newline_and_carriage_return_map_to_enter() {
466 assert_eq!(get_bidi_key_value("\n").unwrap(), "\u{E007}");
467 assert_eq!(get_bidi_key_value("\r").unwrap(), "\u{E007}");
468 }
469
470 #[test]
471 fn unknown_key_returns_error() {
472 let result = get_bidi_key_value("NonExistentKey");
473 assert!(result.is_err());
474 }
475
476 #[test]
477 fn left_right_modifier_variants() {
478 assert_eq!(get_bidi_key_value("ShiftLeft").unwrap(), "\u{E008}");
479 assert_eq!(get_bidi_key_value("ControlLeft").unwrap(), "\u{E009}");
480 assert_eq!(get_bidi_key_value("AltLeft").unwrap(), "\u{E00A}");
481 assert_eq!(get_bidi_key_value("MetaLeft").unwrap(), "\u{E03D}");
482 assert_eq!(get_bidi_key_value("ShiftRight").unwrap(), "\u{E050}");
483 assert_eq!(get_bidi_key_value("ControlRight").unwrap(), "\u{E051}");
484 assert_eq!(get_bidi_key_value("AltRight").unwrap(), "\u{E052}");
485 assert_eq!(get_bidi_key_value("MetaRight").unwrap(), "\u{E053}");
486 }
487}