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::protocol::input::{
15 DispatchKeyEventParams, InsertTextParams, KeyEventType, modifiers,
16};
17use viewpoint_cdp::CdpConnection;
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" | "AltLeft" | "AltRight"
33 | "Control" | "ControlLeft" | "ControlRight"
34 | "Meta" | "MetaLeft" | "MetaRight"
35 | "Shift" | "ShiftLeft" | "ShiftRight"
36 )
37}
38
39#[derive(Debug)]
41struct KeyboardState {
42 modifiers: i32,
44 pressed_keys: HashSet<String>,
46}
47
48impl KeyboardState {
49 fn new() -> Self {
50 Self {
51 modifiers: 0,
52 pressed_keys: HashSet::new(),
53 }
54 }
55
56 fn key_down(&mut self, key: &str) -> bool {
57 let is_repeat = self.pressed_keys.contains(key);
58 self.pressed_keys.insert(key.to_string());
59
60 match key {
62 "Alt" | "AltLeft" | "AltRight" => self.modifiers |= modifiers::ALT,
63 "Control" | "ControlLeft" | "ControlRight" => self.modifiers |= modifiers::CTRL,
64 "Meta" | "MetaLeft" | "MetaRight" => self.modifiers |= modifiers::META,
65 "Shift" | "ShiftLeft" | "ShiftRight" => self.modifiers |= modifiers::SHIFT,
66 _ => {}
67 }
68
69 is_repeat
70 }
71
72 fn key_up(&mut self, key: &str) {
73 self.pressed_keys.remove(key);
74
75 match key {
77 "Alt" | "AltLeft" | "AltRight" => self.modifiers &= !modifiers::ALT,
78 "Control" | "ControlLeft" | "ControlRight" => self.modifiers &= !modifiers::CTRL,
79 "Meta" | "MetaLeft" | "MetaRight" => self.modifiers &= !modifiers::META,
80 "Shift" | "ShiftLeft" | "ShiftRight" => self.modifiers &= !modifiers::SHIFT,
81 _ => {}
82 }
83 }
84}
85
86#[derive(Debug)]
117pub struct Keyboard {
118 connection: Arc<CdpConnection>,
120 session_id: String,
122 state: Mutex<KeyboardState>,
124}
125
126impl Keyboard {
127 pub(crate) fn new(connection: Arc<CdpConnection>, session_id: String) -> Self {
129 Self {
130 connection,
131 session_id,
132 state: Mutex::new(KeyboardState::new()),
133 }
134 }
135
136 #[instrument(level = "debug", skip(self), fields(key = %key))]
158 pub async fn press(&self, key: &str) -> Result<(), LocatorError> {
159 self.press_with_delay(key, None).await
160 }
161
162 #[instrument(level = "debug", skip(self), fields(key = %key))]
164 pub async fn press_with_delay(
165 &self,
166 key: &str,
167 delay: Option<Duration>,
168 ) -> Result<(), LocatorError> {
169 let parts: Vec<&str> = key.split('+').collect();
171 let actual_key = parts.last().copied().unwrap_or(key);
172
173 for part in &parts[..parts.len().saturating_sub(1)] {
175 let modifier_key = self.resolve_modifier(part);
176 self.down(&modifier_key).await?;
177 }
178
179 let need_shift = is_uppercase_letter(actual_key);
181 if need_shift {
182 self.down("Shift").await?;
183 }
184
185 self.down(actual_key).await?;
187
188 if let Some(d) = delay {
189 tokio::time::sleep(d).await;
190 }
191
192 self.up(actual_key).await?;
193
194 if need_shift {
196 self.up("Shift").await?;
197 }
198
199 for part in parts[..parts.len().saturating_sub(1)].iter().rev() {
201 let modifier_key = self.resolve_modifier(part);
202 self.up(&modifier_key).await?;
203 }
204
205 Ok(())
206 }
207
208 fn resolve_modifier(&self, key: &str) -> String {
210 match key {
211 "ControlOrMeta" => {
212 if cfg!(target_os = "macos") {
214 "Meta".to_string()
215 } else {
216 "Control".to_string()
217 }
218 }
219 _ => key.to_string(),
220 }
221 }
222
223 #[instrument(level = "debug", skip(self), fields(key = %key))]
240 pub async fn down(&self, key: &str) -> Result<(), LocatorError> {
241 let def = get_key_definition(key).ok_or_else(|| {
242 LocatorError::EvaluationError(format!("Unknown key: {key}"))
243 })?;
244
245 let is_repeat = {
246 let mut state = self.state.lock().await;
247 state.key_down(key)
248 };
249
250 let state = self.state.lock().await;
251 let current_modifiers = state.modifiers;
252 drop(state);
253
254 debug!(code = def.code, key = def.key, is_repeat, "Key down");
255
256 let params = DispatchKeyEventParams {
257 event_type: KeyEventType::KeyDown,
258 modifiers: Some(current_modifiers),
259 timestamp: None,
260 text: def.text.map(String::from),
261 unmodified_text: def.text.map(String::from),
262 key_identifier: None,
263 code: Some(def.code.to_string()),
264 key: Some(def.key.to_string()),
265 windows_virtual_key_code: Some(def.key_code),
266 native_virtual_key_code: Some(def.key_code),
267 auto_repeat: Some(is_repeat),
268 is_keypad: Some(def.is_keypad),
269 is_system_key: None,
270 commands: None,
271 };
272
273 self.dispatch_key_event(params).await?;
274
275 if !is_modifier_key(key) {
277 if let Some(text) = def.text {
278 let char_params = DispatchKeyEventParams {
279 event_type: KeyEventType::Char,
280 modifiers: Some(current_modifiers),
281 timestamp: None,
282 text: Some(text.to_string()),
283 unmodified_text: Some(text.to_string()),
284 key_identifier: None,
285 code: Some(def.code.to_string()),
286 key: Some(def.key.to_string()),
287 windows_virtual_key_code: Some(def.key_code),
288 native_virtual_key_code: Some(def.key_code),
289 auto_repeat: None,
290 is_keypad: Some(def.is_keypad),
291 is_system_key: None,
292 commands: None,
293 };
294 self.dispatch_key_event(char_params).await?;
295 }
296 }
297
298 Ok(())
299 }
300
301 #[instrument(level = "debug", skip(self), fields(key = %key))]
316 pub async fn up(&self, key: &str) -> Result<(), LocatorError> {
317 let def = get_key_definition(key).ok_or_else(|| {
318 LocatorError::EvaluationError(format!("Unknown key: {key}"))
319 })?;
320
321 {
322 let mut state = self.state.lock().await;
323 state.key_up(key);
324 }
325
326 let state = self.state.lock().await;
327 let current_modifiers = state.modifiers;
328 drop(state);
329
330 debug!(code = def.code, key = def.key, "Key up");
331
332 let params = DispatchKeyEventParams {
333 event_type: KeyEventType::KeyUp,
334 modifiers: Some(current_modifiers),
335 timestamp: None,
336 text: None,
337 unmodified_text: None,
338 key_identifier: None,
339 code: Some(def.code.to_string()),
340 key: Some(def.key.to_string()),
341 windows_virtual_key_code: Some(def.key_code),
342 native_virtual_key_code: Some(def.key_code),
343 auto_repeat: None,
344 is_keypad: Some(def.is_keypad),
345 is_system_key: None,
346 commands: None,
347 };
348
349 self.dispatch_key_event(params).await
350 }
351
352 #[instrument(level = "debug", skip(self), fields(text_len = text.len()))]
368 pub async fn type_text(&self, text: &str) -> Result<(), LocatorError> {
369 self.type_text_with_delay(text, None).await
370 }
371
372 #[instrument(level = "debug", skip(self), fields(text_len = text.len()))]
374 pub async fn type_text_with_delay(
375 &self,
376 text: &str,
377 delay: Option<Duration>,
378 ) -> Result<(), LocatorError> {
379 for ch in text.chars() {
380 let char_str = ch.to_string();
381
382 if let Some(_def) = get_key_definition(&char_str) {
384 let need_shift = ch.is_ascii_uppercase();
386 if need_shift {
387 self.down("Shift").await?;
388 }
389
390 self.down(&char_str).await?;
391 self.up(&char_str).await?;
392
393 if need_shift {
394 self.up("Shift").await?;
395 }
396 } else {
397 let params = DispatchKeyEventParams {
399 event_type: KeyEventType::Char,
400 modifiers: None,
401 timestamp: None,
402 text: Some(char_str.clone()),
403 unmodified_text: Some(char_str),
404 key_identifier: None,
405 code: None,
406 key: None,
407 windows_virtual_key_code: None,
408 native_virtual_key_code: None,
409 auto_repeat: None,
410 is_keypad: None,
411 is_system_key: None,
412 commands: None,
413 };
414 self.dispatch_key_event(params).await?;
415 }
416
417 if let Some(d) = delay {
418 tokio::time::sleep(d).await;
419 }
420 }
421
422 Ok(())
423 }
424
425 #[instrument(level = "debug", skip(self), fields(text_len = text.len()))]
441 pub async fn insert_text(&self, text: &str) -> Result<(), LocatorError> {
442 debug!("Inserting text directly");
443
444 self.connection
445 .send_command::<_, serde_json::Value>(
446 "Input.insertText",
447 Some(InsertTextParams {
448 text: text.to_string(),
449 }),
450 Some(&self.session_id),
451 )
452 .await?;
453
454 Ok(())
455 }
456
457 async fn dispatch_key_event(&self, params: DispatchKeyEventParams) -> Result<(), LocatorError> {
459 self.connection
460 .send_command::<_, serde_json::Value>(
461 "Input.dispatchKeyEvent",
462 Some(params),
463 Some(&self.session_id),
464 )
465 .await?;
466 Ok(())
467 }
468}