Skip to main content

cdp_core/input/
keyboard.rs

1use std::sync::Arc;
2use std::time::Duration;
3
4use rand::{RngExt, make_rng, rngs::SmallRng};
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 = (min_delay < max_delay).then(|| {
267            let rng: SmallRng = make_rng();
268            rng
269        });
270
271        for ch in text.chars() {
272            self.press_character(ch).await?;
273
274            let delay = rng
275                .as_mut()
276                .map(|rng| rng.random_range(min_delay..=max_delay))
277                .unwrap_or(max_delay);
278            if delay > 0 {
279                sleep(Duration::from_millis(delay)).await;
280            }
281        }
282
283        Ok(())
284    }
285}
286
287/// Represents a key definition that can be dispatched to the browser.
288#[derive(Debug, Clone)]
289pub struct KeyInput {
290    /// Display value for the key, for example `a` or `Enter`.
291    pub key: String,
292    /// Optional character produced by the key, forwarded to `text`/`unmodifiedText`.
293    pub text: Option<String>,
294    /// DOM `code` value used by the browser, when known.
295    pub code: Option<String>,
296    /// Platform-specific virtual key code (Windows/native).
297    pub key_code: Option<u32>,
298    /// Keyboard location (0 = Standard, 1 = Left, 2 = Right, 3 = Numpad).
299    pub location: Option<u32>,
300    /// Marks whether the key originates from the numeric keypad.
301    pub is_keypad: bool,
302}
303
304impl KeyInput {
305    /// Builds a key definition with the given display value.
306    pub fn new(key: impl Into<String>) -> Self {
307        Self {
308            key: key.into(),
309            text: None,
310            code: None,
311            key_code: None,
312            location: None,
313            is_keypad: false,
314        }
315    }
316
317    /// Builds a key definition from a single character.
318    pub fn from_char(ch: char) -> Self {
319        let key = ch.to_string();
320        Self {
321            key: key.clone(),
322            text: Some(key),
323            code: None,
324            key_code: Some(ch as u32),
325            location: None,
326            is_keypad: false,
327        }
328    }
329
330    /// Sets the character returned by the key.
331    pub fn with_text(mut self, text: impl Into<String>) -> Self {
332        self.text = Some(text.into());
333        self
334    }
335
336    /// Sets the DOM `code` value for the key.
337    pub fn with_code(mut self, code: impl Into<String>) -> Self {
338        self.code = Some(code.into());
339        self
340    }
341
342    /// Populates the virtual key code.
343    pub fn with_key_code(mut self, key_code: u32) -> Self {
344        self.key_code = Some(key_code);
345        self
346    }
347
348    /// Sets the keyboard location value.
349    pub fn with_location(mut self, location: u32) -> Self {
350        self.location = Some(location);
351        self
352    }
353
354    /// Marks the key as originating from the numeric keypad.
355    pub fn from_keypad(mut self) -> Self {
356        self.is_keypad = true;
357        self
358    }
359}
360
361impl From<&str> for KeyInput {
362    fn from(value: &str) -> Self {
363        let mut chars = value.chars();
364        if let Some(first) = chars.next()
365            && chars.next().is_none()
366        {
367            return KeyInput::from_char(first);
368        }
369        KeyInput::new(value)
370    }
371}
372
373impl From<String> for KeyInput {
374    fn from(value: String) -> Self {
375        KeyInput::from(value.as_str())
376    }
377}
378
379impl From<char> for KeyInput {
380    fn from(value: char) -> Self {
381        KeyInput::from_char(value)
382    }
383}
384
385/// Supported keyboard modifiers.
386#[derive(Debug, Clone, Copy, PartialEq, Eq)]
387pub enum KeyboardModifier {
388    /// Alt key.
389    Alt,
390    /// Control key.
391    Control,
392    /// Meta key (Command on macOS).
393    Meta,
394    /// Shift key.
395    Shift,
396}
397
398impl KeyboardModifier {
399    const fn bit(self) -> u32 {
400        match self {
401            KeyboardModifier::Alt => 1,
402            KeyboardModifier::Control => 2,
403            KeyboardModifier::Meta => 4,
404            KeyboardModifier::Shift => 8,
405        }
406    }
407
408    fn key_input(self) -> KeyInput {
409        match self {
410            KeyboardModifier::Alt => KeyInput::new("Alt")
411                .with_code("AltLeft")
412                .with_key_code(18)
413                .with_location(1),
414            KeyboardModifier::Control => KeyInput::new("Control")
415                .with_code("ControlLeft")
416                .with_key_code(17)
417                .with_location(1),
418            KeyboardModifier::Meta => KeyInput::new("Meta")
419                .with_code("MetaLeft")
420                .with_key_code(91)
421                .with_location(1),
422            KeyboardModifier::Shift => KeyInput::new("Shift")
423                .with_code("ShiftLeft")
424                .with_key_code(16)
425                .with_location(1),
426        }
427    }
428}
429
430/// Bitmask wrapper that tracks active modifiers.
431#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
432pub struct KeyModifiers(u32);
433
434impl KeyModifiers {
435    /// Creates an empty modifier set.
436    pub const fn empty() -> Self {
437        Self(0)
438    }
439
440    /// Returns true if no modifiers are active.
441    pub const fn is_empty(self) -> bool {
442        self.0 == 0
443    }
444
445    /// Returns a new set with the provided modifier enabled.
446    pub const fn with(self, modifier: KeyboardModifier) -> Self {
447        Self(self.0 | modifier.bit())
448    }
449
450    /// Returns a new set without the provided modifier.
451    pub const fn without(self, modifier: KeyboardModifier) -> Self {
452        Self(self.0 & !modifier.bit())
453    }
454
455    /// Produces the bitmask expected by CDP commands.
456    pub const fn bits(self) -> Option<u32> {
457        if self.0 == 0 { None } else { Some(self.0) }
458    }
459}
460
461impl FromIterator<KeyboardModifier> for KeyModifiers {
462    fn from_iter<T: IntoIterator<Item = KeyboardModifier>>(iter: T) -> Self {
463        let mut modifiers = Self::empty();
464        for modifier in iter {
465            modifiers = modifiers.with(modifier);
466        }
467        modifiers
468    }
469}