viewpoint_core/page/keyboard/
mod.rs1mod builder;
7mod keys;
8mod state;
9
10use std::sync::Arc;
11use std::time::Duration;
12
13use tokio::sync::Mutex;
14use tracing::{debug, instrument};
15use viewpoint_cdp::CdpConnection;
16use viewpoint_cdp::protocol::input::{DispatchKeyEventParams, InsertTextParams, KeyEventType};
17
18use crate::error::LocatorError;
19
20pub use builder::KeyboardPressBuilder;
21pub use keys::{KeyDefinition, get_key_definition};
22use state::{KeyboardState, is_modifier_key, is_uppercase_letter};
23
24#[derive(Debug)]
55pub struct Keyboard {
56 connection: Arc<CdpConnection>,
58 session_id: String,
60 frame_id: String,
62 state: Mutex<KeyboardState>,
64}
65
66impl Keyboard {
67 pub(crate) fn new(
69 connection: Arc<CdpConnection>,
70 session_id: String,
71 frame_id: String,
72 ) -> Self {
73 Self {
74 connection,
75 session_id,
76 frame_id,
77 state: Mutex::new(KeyboardState::new()),
78 }
79 }
80
81 pub(crate) fn connection(&self) -> &Arc<CdpConnection> {
83 &self.connection
84 }
85
86 pub(crate) fn session_id(&self) -> &str {
88 &self.session_id
89 }
90
91 pub(crate) fn frame_id(&self) -> &str {
93 &self.frame_id
94 }
95
96 pub fn press(&self, key: &str) -> KeyboardPressBuilder<'_> {
108 KeyboardPressBuilder::new(self, key)
109 }
110
111 pub(crate) async fn press_internal(
113 &self,
114 key: &str,
115 delay: Option<Duration>,
116 ) -> Result<(), LocatorError> {
117 let parts: Vec<&str> = key.split('+').collect();
119 let actual_key = parts.last().copied().unwrap_or(key);
120
121 for part in &parts[..parts.len().saturating_sub(1)] {
123 let modifier_key = self.resolve_modifier(part);
124 self.down(&modifier_key).await?;
125 }
126
127 let need_shift = is_uppercase_letter(actual_key);
129 if need_shift {
130 self.down("Shift").await?;
131 }
132
133 self.down(actual_key).await?;
135
136 if let Some(d) = delay {
137 tokio::time::sleep(d).await;
138 }
139
140 self.up(actual_key).await?;
141
142 if need_shift {
144 self.up("Shift").await?;
145 }
146
147 for part in parts[..parts.len().saturating_sub(1)].iter().rev() {
149 let modifier_key = self.resolve_modifier(part);
150 self.up(&modifier_key).await?;
151 }
152
153 Ok(())
154 }
155
156 fn resolve_modifier(&self, key: &str) -> String {
158 match key {
159 "ControlOrMeta" => {
160 if cfg!(target_os = "macos") {
162 "Meta".to_string()
163 } else {
164 "Control".to_string()
165 }
166 }
167 _ => key.to_string(),
168 }
169 }
170
171 #[instrument(level = "debug", skip(self), fields(key = %key))]
175 pub async fn down(&self, key: &str) -> Result<(), LocatorError> {
176 let def = get_key_definition(key)
177 .ok_or_else(|| LocatorError::EvaluationError(format!("Unknown key: {key}")))?;
178
179 let is_repeat = {
180 let mut state = self.state.lock().await;
181 state.key_down(key)
182 };
183
184 let state = self.state.lock().await;
185 let current_modifiers = state.modifiers;
186 drop(state);
187
188 debug!(code = def.code, key = def.key, is_repeat, "Key down");
189
190 let params = DispatchKeyEventParams {
191 event_type: KeyEventType::KeyDown,
192 modifiers: Some(current_modifiers),
193 timestamp: None,
194 text: def.text.map(String::from),
195 unmodified_text: def.text.map(String::from),
196 key_identifier: None,
197 code: Some(def.code.to_string()),
198 key: Some(def.key.to_string()),
199 windows_virtual_key_code: Some(def.key_code),
200 native_virtual_key_code: Some(def.key_code),
201 auto_repeat: Some(is_repeat),
202 is_keypad: Some(def.is_keypad),
203 is_system_key: None,
204 commands: None,
205 };
206
207 self.dispatch_key_event(params).await?;
208
209 if !is_modifier_key(key) {
211 if let Some(text) = def.text {
212 let char_params = DispatchKeyEventParams {
213 event_type: KeyEventType::Char,
214 modifiers: Some(current_modifiers),
215 timestamp: None,
216 text: Some(text.to_string()),
217 unmodified_text: Some(text.to_string()),
218 key_identifier: None,
219 code: Some(def.code.to_string()),
220 key: Some(def.key.to_string()),
221 windows_virtual_key_code: Some(def.key_code),
222 native_virtual_key_code: Some(def.key_code),
223 auto_repeat: None,
224 is_keypad: Some(def.is_keypad),
225 is_system_key: None,
226 commands: None,
227 };
228 self.dispatch_key_event(char_params).await?;
229 }
230 }
231
232 Ok(())
233 }
234
235 #[instrument(level = "debug", skip(self), fields(key = %key))]
237 pub async fn up(&self, key: &str) -> Result<(), LocatorError> {
238 let def = get_key_definition(key)
239 .ok_or_else(|| LocatorError::EvaluationError(format!("Unknown key: {key}")))?;
240
241 {
242 let mut state = self.state.lock().await;
243 state.key_up(key);
244 }
245
246 let state = self.state.lock().await;
247 let current_modifiers = state.modifiers;
248 drop(state);
249
250 debug!(code = def.code, key = def.key, "Key up");
251
252 let params = DispatchKeyEventParams {
253 event_type: KeyEventType::KeyUp,
254 modifiers: Some(current_modifiers),
255 timestamp: None,
256 text: None,
257 unmodified_text: None,
258 key_identifier: None,
259 code: Some(def.code.to_string()),
260 key: Some(def.key.to_string()),
261 windows_virtual_key_code: Some(def.key_code),
262 native_virtual_key_code: Some(def.key_code),
263 auto_repeat: None,
264 is_keypad: Some(def.is_keypad),
265 is_system_key: None,
266 commands: None,
267 };
268
269 self.dispatch_key_event(params).await
270 }
271
272 #[instrument(level = "debug", skip(self), fields(text_len = text.len()))]
277 pub async fn type_text(&self, text: &str) -> Result<(), LocatorError> {
278 self.type_text_with_delay(text, None).await
279 }
280
281 #[instrument(level = "debug", skip(self), fields(text_len = text.len()))]
283 pub async fn type_text_with_delay(
284 &self,
285 text: &str,
286 delay: Option<Duration>,
287 ) -> Result<(), LocatorError> {
288 for ch in text.chars() {
289 let char_str = ch.to_string();
290
291 if get_key_definition(&char_str).is_some() {
293 let need_shift = ch.is_ascii_uppercase();
295 if need_shift {
296 self.down("Shift").await?;
297 }
298
299 self.down(&char_str).await?;
300 self.up(&char_str).await?;
301
302 if need_shift {
303 self.up("Shift").await?;
304 }
305 } else {
306 let params = DispatchKeyEventParams {
308 event_type: KeyEventType::Char,
309 modifiers: None,
310 timestamp: None,
311 text: Some(char_str.clone()),
312 unmodified_text: Some(char_str),
313 key_identifier: None,
314 code: None,
315 key: None,
316 windows_virtual_key_code: None,
317 native_virtual_key_code: None,
318 auto_repeat: None,
319 is_keypad: None,
320 is_system_key: None,
321 commands: None,
322 };
323 self.dispatch_key_event(params).await?;
324 }
325
326 if let Some(d) = delay {
327 tokio::time::sleep(d).await;
328 }
329 }
330
331 Ok(())
332 }
333
334 #[instrument(level = "debug", skip(self), fields(text_len = text.len()))]
339 pub async fn insert_text(&self, text: &str) -> Result<(), LocatorError> {
340 debug!("Inserting text directly");
341
342 self.connection
343 .send_command::<_, serde_json::Value>(
344 "Input.insertText",
345 Some(InsertTextParams {
346 text: text.to_string(),
347 }),
348 Some(&self.session_id),
349 )
350 .await?;
351
352 Ok(())
353 }
354
355 async fn dispatch_key_event(&self, params: DispatchKeyEventParams) -> Result<(), LocatorError> {
357 self.connection
358 .send_command::<_, serde_json::Value>(
359 "Input.dispatchKeyEvent",
360 Some(params),
361 Some(&self.session_id),
362 )
363 .await?;
364 Ok(())
365 }
366}