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