cdp_core/input/
keyboard.rs

1use std::sync::Arc;
2use std::time::Duration;
3
4use rand::Rng;
5use tokio::time::sleep;
6
7use cdp_protocol::input::{
8    DispatchKeyEvent, DispatchKeyEventReturnObject, DispatchKeyEventTypeOption, InsertText,
9    InsertTextReturnObject,
10};
11
12use crate::{domain_manager::DomainManager, error::Result, session::Session};
13
14/// High-level helper for dispatching keyboard events through the CDP Input domain.
15///
16/// # Examples
17/// ```no_run
18/// # use cdp_core::Page;
19/// # use std::sync::Arc;
20/// # async fn example(page: Arc<Page>) -> anyhow::Result<()> {
21/// let keyboard = page.keyboard();
22/// keyboard.press_key("Enter").await?;
23/// # Ok(())
24/// # }
25/// ```
26#[derive(Clone)]
27pub struct Keyboard {
28    session: Arc<Session>,
29    domain_manager: Arc<DomainManager>,
30}
31
32impl Keyboard {
33    pub(crate) fn new(session: Arc<Session>, domain_manager: Arc<DomainManager>) -> Self {
34        Self {
35            session,
36            domain_manager,
37        }
38    }
39
40    async fn dispatch_key_event(
41        &self,
42        event_type: DispatchKeyEventTypeOption,
43        key: &KeyInput,
44        modifiers: KeyModifiers,
45        auto_repeat: bool,
46    ) -> Result<()> {
47        let params = DispatchKeyEvent {
48            r#type: event_type,
49            modifiers: modifiers.bits(),
50            timestamp: None,
51            text: key.text.clone(),
52            unmodified_text: key.text.clone(),
53            key_identifier: None,
54            code: key.code.clone(),
55            key: Some(key.key.clone()),
56            windows_virtual_key_code: key.key_code,
57            native_virtual_key_code: key.key_code,
58            auto_repeat: Some(auto_repeat),
59            is_keypad: Some(key.is_keypad),
60            is_system_key: Some(false),
61            location: key.location,
62            commands: None,
63        };
64
65        self.session
66            .send_command::<_, DispatchKeyEventReturnObject>(params, None)
67            .await
68            .map(|_| ())
69    }
70
71    /// Dispatches a `keyDown` event for the provided key input.
72    pub async fn key_down(&self, input: impl Into<KeyInput>) -> Result<()> {
73        self.key_down_with_modifiers(input, KeyModifiers::empty())
74            .await
75    }
76
77    /// Dispatches a `keyDown` event while applying the given modifiers.
78    pub async fn key_down_with_modifiers(
79        &self,
80        input: impl Into<KeyInput>,
81        modifiers: KeyModifiers,
82    ) -> Result<()> {
83        let key: KeyInput = input.into();
84        self.dispatch_key_event(DispatchKeyEventTypeOption::KeyDown, &key, modifiers, false)
85            .await
86    }
87
88    /// Dispatches a matching `keyUp` event.
89    pub async fn key_up(&self, input: impl Into<KeyInput>) -> Result<()> {
90        self.key_up_with_modifiers(input, KeyModifiers::empty())
91            .await
92    }
93
94    /// Dispatches a `keyUp` event while applying the given modifiers.
95    pub async fn key_up_with_modifiers(
96        &self,
97        input: impl Into<KeyInput>,
98        modifiers: KeyModifiers,
99    ) -> Result<()> {
100        let key: KeyInput = input.into();
101        self.dispatch_key_event(DispatchKeyEventTypeOption::KeyUp, &key, modifiers, false)
102            .await
103    }
104
105    /// Sends a full key press (a `keyDown` followed by a `keyUp`).
106    pub async fn press_key(&self, input: impl Into<KeyInput>) -> Result<()> {
107        self.press_key_with_modifiers(input, KeyModifiers::empty())
108            .await
109    }
110
111    /// Sends a key press while holding the provided modifiers.
112    ///
113    /// # Examples
114    /// ```no_run
115    /// # use cdp_core::{KeyboardModifier, KeyModifiers, Page};
116    /// # use std::sync::Arc;
117    /// # async fn example(page: Arc<Page>) -> anyhow::Result<()> {
118    /// let keyboard = page.keyboard();
119    /// keyboard
120    ///     .press_key_with_modifiers("r", KeyModifiers::from_iter([KeyboardModifier::Control]))
121    ///     .await?;
122    /// # Ok(())
123    /// # }
124    /// ```
125    pub async fn press_key_with_modifiers(
126        &self,
127        input: impl Into<KeyInput>,
128        modifiers: KeyModifiers,
129    ) -> Result<()> {
130        let key: KeyInput = input.into();
131        self.dispatch_key_event(DispatchKeyEventTypeOption::KeyDown, &key, modifiers, false)
132            .await?;
133        self.dispatch_key_event(DispatchKeyEventTypeOption::KeyUp, &key, modifiers, false)
134            .await
135    }
136
137    /// Presses the primary key that corresponds to the given character.
138    pub async fn press_character(&self, ch: char) -> Result<()> {
139        self.press_key(KeyInput::from(ch)).await
140    }
141
142    /// Presses modifiers in order, executes the target key once, and releases modifiers in reverse.
143    ///
144    /// # Examples
145    /// ```no_run
146    /// # use cdp_core::{KeyboardModifier, Page};
147    /// # use std::sync::Arc;
148    /// # async fn example(page: Arc<Page>) -> anyhow::Result<()> {
149    /// let keyboard = page.keyboard();
150    /// keyboard
151    ///     .press_with_modifiers("l", [KeyboardModifier::Control, KeyboardModifier::Shift])
152    ///     .await?;
153    /// # Ok(())
154    /// # }
155    /// ```
156    pub async fn press_with_modifiers(
157        &self,
158        key: impl Into<KeyInput>,
159        modifiers: impl IntoIterator<Item = KeyboardModifier>,
160    ) -> Result<()> {
161        let modifiers_vec: Vec<KeyboardModifier> = modifiers.into_iter().collect();
162        if modifiers_vec.is_empty() {
163            return self.press_key(key).await;
164        }
165
166        let mut active = KeyModifiers::empty();
167
168        // Press modifiers one by one and keep track of the active bitmask.
169        for modifier in &modifiers_vec {
170            active = active.with(*modifier);
171            let modifier_key = modifier.key_input();
172            self.dispatch_key_event(
173                DispatchKeyEventTypeOption::KeyDown,
174                &modifier_key,
175                active,
176                false,
177            )
178            .await?;
179        }
180
181        // Dispatch the target key with all modifiers applied.
182        let target_key: KeyInput = key.into();
183        self.dispatch_key_event(
184            DispatchKeyEventTypeOption::KeyDown,
185            &target_key,
186            active,
187            false,
188        )
189        .await?;
190        self.dispatch_key_event(
191            DispatchKeyEventTypeOption::KeyUp,
192            &target_key,
193            active,
194            false,
195        )
196        .await?;
197
198        // Release modifiers in reverse order to mimic user input.
199        for modifier in modifiers_vec.into_iter().rev() {
200            active = active.without(modifier);
201            let modifier_key = modifier.key_input();
202            self.dispatch_key_event(
203                DispatchKeyEventTypeOption::KeyUp,
204                &modifier_key,
205                active,
206                false,
207            )
208            .await?;
209        }
210
211        Ok(())
212    }
213
214    /// Invokes `Input.insertText` to inject the provided text in one operation.
215    ///
216    /// # Examples
217    /// ```no_run
218    /// # use cdp_core::Page;
219    /// # use std::sync::Arc;
220    /// # async fn example(page: Arc<Page>) -> anyhow::Result<()> {
221    /// let keyboard = page.keyboard();
222    /// keyboard.insert_text("Hello, CDP!").await?;
223    /// # Ok(())
224    /// # }
225    /// ```
226    pub async fn insert_text(&self, text: &str) -> Result<()> {
227        let params = InsertText {
228            text: text.to_string(),
229        };
230
231        self.session
232            .send_command::<_, InsertTextReturnObject>(params, None)
233            .await?;
234
235        Ok(())
236    }
237
238    /// Simulates human typing by adding a random delay between `keyDown`/`keyUp` pairs.
239    ///
240    /// # Examples
241    /// ```no_run
242    /// # use cdp_core::Page;
243    /// # use std::sync::Arc;
244    /// # async fn example(page: Arc<Page>) -> anyhow::Result<()> {
245    /// let keyboard = page.keyboard();
246    /// keyboard.type_text_with_delay("Hello", 40, 120).await?;
247    /// # Ok(())
248    /// # }
249    /// ```
250    pub async fn type_text_with_delay(
251        &self,
252        text: &str,
253        min_delay_ms: u64,
254        max_delay_ms: u64,
255    ) -> Result<()> {
256        if text.is_empty() {
257            return Ok(());
258        }
259
260        let (min_delay, max_delay) = if min_delay_ms <= max_delay_ms {
261            (min_delay_ms, max_delay_ms)
262        } else {
263            (max_delay_ms, min_delay_ms)
264        };
265
266        let mut rng = rand::rng();
267
268        for ch in text.chars() {
269            self.press_character(ch).await?;
270
271            if min_delay > 0 || max_delay > 0 {
272                let delay = if min_delay == max_delay {
273                    max_delay
274                } else {
275                    rng.random_range(min_delay..=max_delay)
276                };
277
278                if delay > 0 {
279                    sleep(Duration::from_millis(delay)).await;
280                }
281            }
282        }
283
284        Ok(())
285    }
286}
287
288/// Represents a key definition that can be dispatched to the browser.
289#[derive(Debug, Clone)]
290pub struct KeyInput {
291    /// Display value for the key, for example `a` or `Enter`.
292    pub key: String,
293    /// Optional character produced by the key, forwarded to `text`/`unmodifiedText`.
294    pub text: Option<String>,
295    /// DOM `code` value used by the browser, when known.
296    pub code: Option<String>,
297    /// Platform-specific virtual key code (Windows/native).
298    pub key_code: Option<u32>,
299    /// Keyboard location (0 = Standard, 1 = Left, 2 = Right, 3 = Numpad).
300    pub location: Option<u32>,
301    /// Marks whether the key originates from the numeric keypad.
302    pub is_keypad: bool,
303}
304
305impl KeyInput {
306    /// Builds a key definition with the given display value.
307    pub fn new(key: impl Into<String>) -> Self {
308        Self {
309            key: key.into(),
310            text: None,
311            code: None,
312            key_code: None,
313            location: None,
314            is_keypad: false,
315        }
316    }
317
318    /// Builds a key definition from a single character.
319    pub fn from_char(ch: char) -> Self {
320        let key = ch.to_string();
321        Self {
322            key: key.clone(),
323            text: Some(key),
324            code: None,
325            key_code: Some(ch as u32),
326            location: None,
327            is_keypad: false,
328        }
329    }
330
331    /// Sets the character returned by the key.
332    pub fn with_text(mut self, text: impl Into<String>) -> Self {
333        self.text = Some(text.into());
334        self
335    }
336
337    /// Sets the DOM `code` value for the key.
338    pub fn with_code(mut self, code: impl Into<String>) -> Self {
339        self.code = Some(code.into());
340        self
341    }
342
343    /// Populates the virtual key code.
344    pub fn with_key_code(mut self, key_code: u32) -> Self {
345        self.key_code = Some(key_code);
346        self
347    }
348
349    /// Sets the keyboard location value.
350    pub fn with_location(mut self, location: u32) -> Self {
351        self.location = Some(location);
352        self
353    }
354
355    /// Marks the key as originating from the numeric keypad.
356    pub fn from_keypad(mut self) -> Self {
357        self.is_keypad = true;
358        self
359    }
360}
361
362impl From<&str> for KeyInput {
363    fn from(value: &str) -> Self {
364        let mut chars = value.chars();
365        if let Some(first) = chars.next()
366            && chars.next().is_none()
367        {
368            return KeyInput::from_char(first);
369        }
370        KeyInput::new(value)
371    }
372}
373
374impl From<String> for KeyInput {
375    fn from(value: String) -> Self {
376        KeyInput::from(value.as_str())
377    }
378}
379
380impl From<char> for KeyInput {
381    fn from(value: char) -> Self {
382        KeyInput::from_char(value)
383    }
384}
385
386/// Supported keyboard modifiers.
387#[derive(Debug, Clone, Copy, PartialEq, Eq)]
388pub enum KeyboardModifier {
389    /// Alt key.
390    Alt,
391    /// Control key.
392    Control,
393    /// Meta key (Command on macOS).
394    Meta,
395    /// Shift key.
396    Shift,
397}
398
399impl KeyboardModifier {
400    const fn bit(self) -> u32 {
401        match self {
402            KeyboardModifier::Alt => 1,
403            KeyboardModifier::Control => 2,
404            KeyboardModifier::Meta => 4,
405            KeyboardModifier::Shift => 8,
406        }
407    }
408
409    fn key_input(self) -> KeyInput {
410        match self {
411            KeyboardModifier::Alt => KeyInput::new("Alt")
412                .with_code("AltLeft")
413                .with_key_code(18)
414                .with_location(1),
415            KeyboardModifier::Control => KeyInput::new("Control")
416                .with_code("ControlLeft")
417                .with_key_code(17)
418                .with_location(1),
419            KeyboardModifier::Meta => KeyInput::new("Meta")
420                .with_code("MetaLeft")
421                .with_key_code(91)
422                .with_location(1),
423            KeyboardModifier::Shift => KeyInput::new("Shift")
424                .with_code("ShiftLeft")
425                .with_key_code(16)
426                .with_location(1),
427        }
428    }
429}
430
431/// Bitmask wrapper that tracks active modifiers.
432#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
433pub struct KeyModifiers(u32);
434
435impl KeyModifiers {
436    /// Creates an empty modifier set.
437    pub const fn empty() -> Self {
438        Self(0)
439    }
440
441    /// Returns true if no modifiers are active.
442    pub const fn is_empty(self) -> bool {
443        self.0 == 0
444    }
445
446    /// Returns a new set with the provided modifier enabled.
447    pub const fn with(self, modifier: KeyboardModifier) -> Self {
448        Self(self.0 | modifier.bit())
449    }
450
451    /// Returns a new set without the provided modifier.
452    pub const fn without(self, modifier: KeyboardModifier) -> Self {
453        Self(self.0 & !modifier.bit())
454    }
455
456    /// Produces the bitmask expected by CDP commands.
457    pub const fn bits(self) -> Option<u32> {
458        if self.0 == 0 { None } else { Some(self.0) }
459    }
460}
461
462impl FromIterator<KeyboardModifier> for KeyModifiers {
463    fn from_iter<T: IntoIterator<Item = KeyboardModifier>>(iter: T) -> Self {
464        let mut modifiers = Self::empty();
465        for modifier in iter {
466            modifiers = modifiers.with(modifier);
467        }
468        modifiers
469    }
470}