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