viewpoint_core/page/keyboard/
mod.rs1mod keys;
7
8use std::collections::HashSet;
9use std::sync::Arc;
10use std::time::Duration;
11
12use tokio::sync::Mutex;
13use tracing::{debug, instrument};
14use viewpoint_cdp::CdpConnection;
15use viewpoint_cdp::protocol::input::{
16 DispatchKeyEventParams, InsertTextParams, KeyEventType, modifiers,
17};
18
19use crate::error::LocatorError;
20
21pub use keys::{KeyDefinition, get_key_definition};
22
23fn is_uppercase_letter(key: &str) -> bool {
25 key.len() == 1 && key.chars().next().is_some_and(|c| c.is_ascii_uppercase())
26}
27
28fn is_modifier_key(key: &str) -> bool {
30 matches!(
31 key,
32 "Alt"
33 | "AltLeft"
34 | "AltRight"
35 | "Control"
36 | "ControlLeft"
37 | "ControlRight"
38 | "Meta"
39 | "MetaLeft"
40 | "MetaRight"
41 | "Shift"
42 | "ShiftLeft"
43 | "ShiftRight"
44 )
45}
46
47#[derive(Debug)]
49struct KeyboardState {
50 modifiers: i32,
52 pressed_keys: HashSet<String>,
54}
55
56impl KeyboardState {
57 fn new() -> Self {
58 Self {
59 modifiers: 0,
60 pressed_keys: HashSet::new(),
61 }
62 }
63
64 fn key_down(&mut self, key: &str) -> bool {
65 let is_repeat = self.pressed_keys.contains(key);
66 self.pressed_keys.insert(key.to_string());
67
68 match key {
70 "Alt" | "AltLeft" | "AltRight" => self.modifiers |= modifiers::ALT,
71 "Control" | "ControlLeft" | "ControlRight" => self.modifiers |= modifiers::CTRL,
72 "Meta" | "MetaLeft" | "MetaRight" => self.modifiers |= modifiers::META,
73 "Shift" | "ShiftLeft" | "ShiftRight" => self.modifiers |= modifiers::SHIFT,
74 _ => {}
75 }
76
77 is_repeat
78 }
79
80 fn key_up(&mut self, key: &str) {
81 self.pressed_keys.remove(key);
82
83 match key {
85 "Alt" | "AltLeft" | "AltRight" => self.modifiers &= !modifiers::ALT,
86 "Control" | "ControlLeft" | "ControlRight" => self.modifiers &= !modifiers::CTRL,
87 "Meta" | "MetaLeft" | "MetaRight" => self.modifiers &= !modifiers::META,
88 "Shift" | "ShiftLeft" | "ShiftRight" => self.modifiers &= !modifiers::SHIFT,
89 _ => {}
90 }
91 }
92}
93
94#[derive(Debug)]
125pub struct Keyboard {
126 connection: Arc<CdpConnection>,
128 session_id: String,
130 state: Mutex<KeyboardState>,
132}
133
134impl Keyboard {
135 pub(crate) fn new(connection: Arc<CdpConnection>, session_id: String) -> Self {
137 Self {
138 connection,
139 session_id,
140 state: Mutex::new(KeyboardState::new()),
141 }
142 }
143
144 #[instrument(level = "debug", skip(self), fields(key = %key))]
166 pub async fn press(&self, key: &str) -> Result<(), LocatorError> {
167 self.press_with_delay(key, None).await
168 }
169
170 #[instrument(level = "debug", skip(self), fields(key = %key))]
172 pub async fn press_with_delay(
173 &self,
174 key: &str,
175 delay: Option<Duration>,
176 ) -> Result<(), LocatorError> {
177 let parts: Vec<&str> = key.split('+').collect();
179 let actual_key = parts.last().copied().unwrap_or(key);
180
181 for part in &parts[..parts.len().saturating_sub(1)] {
183 let modifier_key = self.resolve_modifier(part);
184 self.down(&modifier_key).await?;
185 }
186
187 let need_shift = is_uppercase_letter(actual_key);
189 if need_shift {
190 self.down("Shift").await?;
191 }
192
193 self.down(actual_key).await?;
195
196 if let Some(d) = delay {
197 tokio::time::sleep(d).await;
198 }
199
200 self.up(actual_key).await?;
201
202 if need_shift {
204 self.up("Shift").await?;
205 }
206
207 for part in parts[..parts.len().saturating_sub(1)].iter().rev() {
209 let modifier_key = self.resolve_modifier(part);
210 self.up(&modifier_key).await?;
211 }
212
213 Ok(())
214 }
215
216 fn resolve_modifier(&self, key: &str) -> String {
218 match key {
219 "ControlOrMeta" => {
220 if cfg!(target_os = "macos") {
222 "Meta".to_string()
223 } else {
224 "Control".to_string()
225 }
226 }
227 _ => key.to_string(),
228 }
229 }
230
231 #[instrument(level = "debug", skip(self), fields(key = %key))]
248 pub async fn down(&self, key: &str) -> Result<(), LocatorError> {
249 let def = get_key_definition(key)
250 .ok_or_else(|| LocatorError::EvaluationError(format!("Unknown key: {key}")))?;
251
252 let is_repeat = {
253 let mut state = self.state.lock().await;
254 state.key_down(key)
255 };
256
257 let state = self.state.lock().await;
258 let current_modifiers = state.modifiers;
259 drop(state);
260
261 debug!(code = def.code, key = def.key, is_repeat, "Key down");
262
263 let params = DispatchKeyEventParams {
264 event_type: KeyEventType::KeyDown,
265 modifiers: Some(current_modifiers),
266 timestamp: None,
267 text: def.text.map(String::from),
268 unmodified_text: def.text.map(String::from),
269 key_identifier: None,
270 code: Some(def.code.to_string()),
271 key: Some(def.key.to_string()),
272 windows_virtual_key_code: Some(def.key_code),
273 native_virtual_key_code: Some(def.key_code),
274 auto_repeat: Some(is_repeat),
275 is_keypad: Some(def.is_keypad),
276 is_system_key: None,
277 commands: None,
278 };
279
280 self.dispatch_key_event(params).await?;
281
282 if !is_modifier_key(key) {
284 if let Some(text) = def.text {
285 let char_params = DispatchKeyEventParams {
286 event_type: KeyEventType::Char,
287 modifiers: Some(current_modifiers),
288 timestamp: None,
289 text: Some(text.to_string()),
290 unmodified_text: Some(text.to_string()),
291 key_identifier: None,
292 code: Some(def.code.to_string()),
293 key: Some(def.key.to_string()),
294 windows_virtual_key_code: Some(def.key_code),
295 native_virtual_key_code: Some(def.key_code),
296 auto_repeat: None,
297 is_keypad: Some(def.is_keypad),
298 is_system_key: None,
299 commands: None,
300 };
301 self.dispatch_key_event(char_params).await?;
302 }
303 }
304
305 Ok(())
306 }
307
308 #[instrument(level = "debug", skip(self), fields(key = %key))]
323 pub async fn up(&self, key: &str) -> Result<(), LocatorError> {
324 let def = get_key_definition(key)
325 .ok_or_else(|| LocatorError::EvaluationError(format!("Unknown key: {key}")))?;
326
327 {
328 let mut state = self.state.lock().await;
329 state.key_up(key);
330 }
331
332 let state = self.state.lock().await;
333 let current_modifiers = state.modifiers;
334 drop(state);
335
336 debug!(code = def.code, key = def.key, "Key up");
337
338 let params = DispatchKeyEventParams {
339 event_type: KeyEventType::KeyUp,
340 modifiers: Some(current_modifiers),
341 timestamp: None,
342 text: None,
343 unmodified_text: None,
344 key_identifier: None,
345 code: Some(def.code.to_string()),
346 key: Some(def.key.to_string()),
347 windows_virtual_key_code: Some(def.key_code),
348 native_virtual_key_code: Some(def.key_code),
349 auto_repeat: None,
350 is_keypad: Some(def.is_keypad),
351 is_system_key: None,
352 commands: None,
353 };
354
355 self.dispatch_key_event(params).await
356 }
357
358 #[instrument(level = "debug", skip(self), fields(text_len = text.len()))]
374 pub async fn type_text(&self, text: &str) -> Result<(), LocatorError> {
375 self.type_text_with_delay(text, None).await
376 }
377
378 #[instrument(level = "debug", skip(self), fields(text_len = text.len()))]
380 pub async fn type_text_with_delay(
381 &self,
382 text: &str,
383 delay: Option<Duration>,
384 ) -> Result<(), LocatorError> {
385 for ch in text.chars() {
386 let char_str = ch.to_string();
387
388 if let Some(_def) = get_key_definition(&char_str) {
390 let need_shift = ch.is_ascii_uppercase();
392 if need_shift {
393 self.down("Shift").await?;
394 }
395
396 self.down(&char_str).await?;
397 self.up(&char_str).await?;
398
399 if need_shift {
400 self.up("Shift").await?;
401 }
402 } else {
403 let params = DispatchKeyEventParams {
405 event_type: KeyEventType::Char,
406 modifiers: None,
407 timestamp: None,
408 text: Some(char_str.clone()),
409 unmodified_text: Some(char_str),
410 key_identifier: None,
411 code: None,
412 key: None,
413 windows_virtual_key_code: None,
414 native_virtual_key_code: None,
415 auto_repeat: None,
416 is_keypad: None,
417 is_system_key: None,
418 commands: None,
419 };
420 self.dispatch_key_event(params).await?;
421 }
422
423 if let Some(d) = delay {
424 tokio::time::sleep(d).await;
425 }
426 }
427
428 Ok(())
429 }
430
431 #[instrument(level = "debug", skip(self), fields(text_len = text.len()))]
447 pub async fn insert_text(&self, text: &str) -> Result<(), LocatorError> {
448 debug!("Inserting text directly");
449
450 self.connection
451 .send_command::<_, serde_json::Value>(
452 "Input.insertText",
453 Some(InsertTextParams {
454 text: text.to_string(),
455 }),
456 Some(&self.session_id),
457 )
458 .await?;
459
460 Ok(())
461 }
462
463 async fn dispatch_key_event(&self, params: DispatchKeyEventParams) -> Result<(), LocatorError> {
465 self.connection
466 .send_command::<_, serde_json::Value>(
467 "Input.dispatchKeyEvent",
468 Some(params),
469 Some(&self.session_id),
470 )
471 .await?;
472 Ok(())
473 }
474}