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)]
108pub struct Keyboard {
109 connection: Arc<CdpConnection>,
111 session_id: String,
113 state: Mutex<KeyboardState>,
115}
116
117impl Keyboard {
118 pub(crate) fn new(connection: Arc<CdpConnection>, session_id: String) -> Self {
120 Self {
121 connection,
122 session_id,
123 state: Mutex::new(KeyboardState::new()),
124 }
125 }
126
127 #[instrument(level = "debug", skip(self), fields(key = %key))]
144 pub async fn press(&self, key: &str) -> Result<(), LocatorError> {
145 self.press_with_delay(key, None).await
146 }
147
148 #[instrument(level = "debug", skip(self), fields(key = %key))]
150 pub async fn press_with_delay(
151 &self,
152 key: &str,
153 delay: Option<Duration>,
154 ) -> Result<(), LocatorError> {
155 let parts: Vec<&str> = key.split('+').collect();
157 let actual_key = parts.last().copied().unwrap_or(key);
158
159 for part in &parts[..parts.len().saturating_sub(1)] {
161 let modifier_key = self.resolve_modifier(part);
162 self.down(&modifier_key).await?;
163 }
164
165 let need_shift = is_uppercase_letter(actual_key);
167 if need_shift {
168 self.down("Shift").await?;
169 }
170
171 self.down(actual_key).await?;
173
174 if let Some(d) = delay {
175 tokio::time::sleep(d).await;
176 }
177
178 self.up(actual_key).await?;
179
180 if need_shift {
182 self.up("Shift").await?;
183 }
184
185 for part in parts[..parts.len().saturating_sub(1)].iter().rev() {
187 let modifier_key = self.resolve_modifier(part);
188 self.up(&modifier_key).await?;
189 }
190
191 Ok(())
192 }
193
194 fn resolve_modifier(&self, key: &str) -> String {
196 match key {
197 "ControlOrMeta" => {
198 if cfg!(target_os = "macos") {
200 "Meta".to_string()
201 } else {
202 "Control".to_string()
203 }
204 }
205 _ => key.to_string(),
206 }
207 }
208
209 #[instrument(level = "debug", skip(self), fields(key = %key))]
221 pub async fn down(&self, key: &str) -> Result<(), LocatorError> {
222 let def = get_key_definition(key).ok_or_else(|| {
223 LocatorError::EvaluationError(format!("Unknown key: {key}"))
224 })?;
225
226 let is_repeat = {
227 let mut state = self.state.lock().await;
228 state.key_down(key)
229 };
230
231 let state = self.state.lock().await;
232 let current_modifiers = state.modifiers;
233 drop(state);
234
235 debug!(code = def.code, key = def.key, is_repeat, "Key down");
236
237 let params = DispatchKeyEventParams {
238 event_type: KeyEventType::KeyDown,
239 modifiers: Some(current_modifiers),
240 timestamp: None,
241 text: def.text.map(String::from),
242 unmodified_text: def.text.map(String::from),
243 key_identifier: None,
244 code: Some(def.code.to_string()),
245 key: Some(def.key.to_string()),
246 windows_virtual_key_code: Some(def.key_code),
247 native_virtual_key_code: Some(def.key_code),
248 auto_repeat: Some(is_repeat),
249 is_keypad: Some(def.is_keypad),
250 is_system_key: None,
251 commands: None,
252 };
253
254 self.dispatch_key_event(params).await?;
255
256 if !is_modifier_key(key) {
258 if let Some(text) = def.text {
259 let char_params = DispatchKeyEventParams {
260 event_type: KeyEventType::Char,
261 modifiers: Some(current_modifiers),
262 timestamp: None,
263 text: Some(text.to_string()),
264 unmodified_text: Some(text.to_string()),
265 key_identifier: None,
266 code: Some(def.code.to_string()),
267 key: Some(def.key.to_string()),
268 windows_virtual_key_code: Some(def.key_code),
269 native_virtual_key_code: Some(def.key_code),
270 auto_repeat: None,
271 is_keypad: Some(def.is_keypad),
272 is_system_key: None,
273 commands: None,
274 };
275 self.dispatch_key_event(char_params).await?;
276 }
277 }
278
279 Ok(())
280 }
281
282 #[instrument(level = "debug", skip(self), fields(key = %key))]
292 pub async fn up(&self, key: &str) -> Result<(), LocatorError> {
293 let def = get_key_definition(key).ok_or_else(|| {
294 LocatorError::EvaluationError(format!("Unknown key: {key}"))
295 })?;
296
297 {
298 let mut state = self.state.lock().await;
299 state.key_up(key);
300 }
301
302 let state = self.state.lock().await;
303 let current_modifiers = state.modifiers;
304 drop(state);
305
306 debug!(code = def.code, key = def.key, "Key up");
307
308 let params = DispatchKeyEventParams {
309 event_type: KeyEventType::KeyUp,
310 modifiers: Some(current_modifiers),
311 timestamp: None,
312 text: None,
313 unmodified_text: None,
314 key_identifier: None,
315 code: Some(def.code.to_string()),
316 key: Some(def.key.to_string()),
317 windows_virtual_key_code: Some(def.key_code),
318 native_virtual_key_code: Some(def.key_code),
319 auto_repeat: None,
320 is_keypad: Some(def.is_keypad),
321 is_system_key: None,
322 commands: None,
323 };
324
325 self.dispatch_key_event(params).await
326 }
327
328 #[instrument(level = "debug", skip(self), fields(text_len = text.len()))]
339 pub async fn type_text(&self, text: &str) -> Result<(), LocatorError> {
340 self.type_text_with_delay(text, None).await
341 }
342
343 #[instrument(level = "debug", skip(self), fields(text_len = text.len()))]
345 pub async fn type_text_with_delay(
346 &self,
347 text: &str,
348 delay: Option<Duration>,
349 ) -> Result<(), LocatorError> {
350 for ch in text.chars() {
351 let char_str = ch.to_string();
352
353 if let Some(_def) = get_key_definition(&char_str) {
355 let need_shift = ch.is_ascii_uppercase();
357 if need_shift {
358 self.down("Shift").await?;
359 }
360
361 self.down(&char_str).await?;
362 self.up(&char_str).await?;
363
364 if need_shift {
365 self.up("Shift").await?;
366 }
367 } else {
368 let params = DispatchKeyEventParams {
370 event_type: KeyEventType::Char,
371 modifiers: None,
372 timestamp: None,
373 text: Some(char_str.clone()),
374 unmodified_text: Some(char_str),
375 key_identifier: None,
376 code: None,
377 key: None,
378 windows_virtual_key_code: None,
379 native_virtual_key_code: None,
380 auto_repeat: None,
381 is_keypad: None,
382 is_system_key: None,
383 commands: None,
384 };
385 self.dispatch_key_event(params).await?;
386 }
387
388 if let Some(d) = delay {
389 tokio::time::sleep(d).await;
390 }
391 }
392
393 Ok(())
394 }
395
396 #[instrument(level = "debug", skip(self), fields(text_len = text.len()))]
407 pub async fn insert_text(&self, text: &str) -> Result<(), LocatorError> {
408 debug!("Inserting text directly");
409
410 self.connection
411 .send_command::<_, serde_json::Value>(
412 "Input.insertText",
413 Some(InsertTextParams {
414 text: text.to_string(),
415 }),
416 Some(&self.session_id),
417 )
418 .await?;
419
420 Ok(())
421 }
422
423 async fn dispatch_key_event(&self, params: DispatchKeyEventParams) -> Result<(), LocatorError> {
425 self.connection
426 .send_command::<_, serde_json::Value>(
427 "Input.dispatchKeyEvent",
428 Some(params),
429 Some(&self.session_id),
430 )
431 .await?;
432 Ok(())
433 }
434}