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))]
153 pub async fn press(&self, key: &str) -> Result<(), LocatorError> {
154 self.press_with_delay(key, None).await
155 }
156
157 #[instrument(level = "debug", skip(self), fields(key = %key))]
159 pub async fn press_with_delay(
160 &self,
161 key: &str,
162 delay: Option<Duration>,
163 ) -> Result<(), LocatorError> {
164 let parts: Vec<&str> = key.split('+').collect();
166 let actual_key = parts.last().copied().unwrap_or(key);
167
168 for part in &parts[..parts.len().saturating_sub(1)] {
170 let modifier_key = self.resolve_modifier(part);
171 self.down(&modifier_key).await?;
172 }
173
174 let need_shift = is_uppercase_letter(actual_key);
176 if need_shift {
177 self.down("Shift").await?;
178 }
179
180 self.down(actual_key).await?;
182
183 if let Some(d) = delay {
184 tokio::time::sleep(d).await;
185 }
186
187 self.up(actual_key).await?;
188
189 if need_shift {
191 self.up("Shift").await?;
192 }
193
194 for part in parts[..parts.len().saturating_sub(1)].iter().rev() {
196 let modifier_key = self.resolve_modifier(part);
197 self.up(&modifier_key).await?;
198 }
199
200 Ok(())
201 }
202
203 fn resolve_modifier(&self, key: &str) -> String {
205 match key {
206 "ControlOrMeta" => {
207 if cfg!(target_os = "macos") {
209 "Meta".to_string()
210 } else {
211 "Control".to_string()
212 }
213 }
214 _ => key.to_string(),
215 }
216 }
217
218 #[instrument(level = "debug", skip(self), fields(key = %key))]
230 pub async fn down(&self, key: &str) -> Result<(), LocatorError> {
231 let def = get_key_definition(key).ok_or_else(|| {
232 LocatorError::EvaluationError(format!("Unknown key: {key}"))
233 })?;
234
235 let is_repeat = {
236 let mut state = self.state.lock().await;
237 state.key_down(key)
238 };
239
240 let state = self.state.lock().await;
241 let current_modifiers = state.modifiers;
242 drop(state);
243
244 debug!(code = def.code, key = def.key, is_repeat, "Key down");
245
246 let params = DispatchKeyEventParams {
247 event_type: KeyEventType::KeyDown,
248 modifiers: Some(current_modifiers),
249 timestamp: None,
250 text: def.text.map(String::from),
251 unmodified_text: def.text.map(String::from),
252 key_identifier: None,
253 code: Some(def.code.to_string()),
254 key: Some(def.key.to_string()),
255 windows_virtual_key_code: Some(def.key_code),
256 native_virtual_key_code: Some(def.key_code),
257 auto_repeat: Some(is_repeat),
258 is_keypad: Some(def.is_keypad),
259 is_system_key: None,
260 commands: None,
261 };
262
263 self.dispatch_key_event(params).await?;
264
265 if !is_modifier_key(key) {
267 if let Some(text) = def.text {
268 let char_params = DispatchKeyEventParams {
269 event_type: KeyEventType::Char,
270 modifiers: Some(current_modifiers),
271 timestamp: None,
272 text: Some(text.to_string()),
273 unmodified_text: Some(text.to_string()),
274 key_identifier: None,
275 code: Some(def.code.to_string()),
276 key: Some(def.key.to_string()),
277 windows_virtual_key_code: Some(def.key_code),
278 native_virtual_key_code: Some(def.key_code),
279 auto_repeat: None,
280 is_keypad: Some(def.is_keypad),
281 is_system_key: None,
282 commands: None,
283 };
284 self.dispatch_key_event(char_params).await?;
285 }
286 }
287
288 Ok(())
289 }
290
291 #[instrument(level = "debug", skip(self), fields(key = %key))]
301 pub async fn up(&self, key: &str) -> Result<(), LocatorError> {
302 let def = get_key_definition(key).ok_or_else(|| {
303 LocatorError::EvaluationError(format!("Unknown key: {key}"))
304 })?;
305
306 {
307 let mut state = self.state.lock().await;
308 state.key_up(key);
309 }
310
311 let state = self.state.lock().await;
312 let current_modifiers = state.modifiers;
313 drop(state);
314
315 debug!(code = def.code, key = def.key, "Key up");
316
317 let params = DispatchKeyEventParams {
318 event_type: KeyEventType::KeyUp,
319 modifiers: Some(current_modifiers),
320 timestamp: None,
321 text: None,
322 unmodified_text: None,
323 key_identifier: None,
324 code: Some(def.code.to_string()),
325 key: Some(def.key.to_string()),
326 windows_virtual_key_code: Some(def.key_code),
327 native_virtual_key_code: Some(def.key_code),
328 auto_repeat: None,
329 is_keypad: Some(def.is_keypad),
330 is_system_key: None,
331 commands: None,
332 };
333
334 self.dispatch_key_event(params).await
335 }
336
337 #[instrument(level = "debug", skip(self), fields(text_len = text.len()))]
348 pub async fn type_text(&self, text: &str) -> Result<(), LocatorError> {
349 self.type_text_with_delay(text, None).await
350 }
351
352 #[instrument(level = "debug", skip(self), fields(text_len = text.len()))]
354 pub async fn type_text_with_delay(
355 &self,
356 text: &str,
357 delay: Option<Duration>,
358 ) -> Result<(), LocatorError> {
359 for ch in text.chars() {
360 let char_str = ch.to_string();
361
362 if let Some(_def) = get_key_definition(&char_str) {
364 let need_shift = ch.is_ascii_uppercase();
366 if need_shift {
367 self.down("Shift").await?;
368 }
369
370 self.down(&char_str).await?;
371 self.up(&char_str).await?;
372
373 if need_shift {
374 self.up("Shift").await?;
375 }
376 } else {
377 let params = DispatchKeyEventParams {
379 event_type: KeyEventType::Char,
380 modifiers: None,
381 timestamp: None,
382 text: Some(char_str.clone()),
383 unmodified_text: Some(char_str),
384 key_identifier: None,
385 code: None,
386 key: None,
387 windows_virtual_key_code: None,
388 native_virtual_key_code: None,
389 auto_repeat: None,
390 is_keypad: None,
391 is_system_key: None,
392 commands: None,
393 };
394 self.dispatch_key_event(params).await?;
395 }
396
397 if let Some(d) = delay {
398 tokio::time::sleep(d).await;
399 }
400 }
401
402 Ok(())
403 }
404
405 #[instrument(level = "debug", skip(self), fields(text_len = text.len()))]
416 pub async fn insert_text(&self, text: &str) -> Result<(), LocatorError> {
417 debug!("Inserting text directly");
418
419 self.connection
420 .send_command::<_, serde_json::Value>(
421 "Input.insertText",
422 Some(InsertTextParams {
423 text: text.to_string(),
424 }),
425 Some(&self.session_id),
426 )
427 .await?;
428
429 Ok(())
430 }
431
432 async fn dispatch_key_event(&self, params: DispatchKeyEventParams) -> Result<(), LocatorError> {
434 self.connection
435 .send_command::<_, serde_json::Value>(
436 "Input.dispatchKeyEvent",
437 Some(params),
438 Some(&self.session_id),
439 )
440 .await?;
441 Ok(())
442 }
443}