mod builder;
mod keys;
mod state;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::Mutex;
use tracing::{debug, instrument};
use viewpoint_cdp::CdpConnection;
use viewpoint_cdp::protocol::input::{DispatchKeyEventParams, InsertTextParams, KeyEventType};
use crate::error::LocatorError;
pub use builder::KeyboardPressBuilder;
pub use keys::{KeyDefinition, get_key_definition};
use state::{KeyboardState, is_modifier_key, is_uppercase_letter};
#[derive(Debug)]
pub struct Keyboard {
connection: Arc<CdpConnection>,
session_id: String,
frame_id: String,
state: Mutex<KeyboardState>,
}
impl Keyboard {
pub(crate) fn new(
connection: Arc<CdpConnection>,
session_id: String,
frame_id: String,
) -> Self {
Self {
connection,
session_id,
frame_id,
state: Mutex::new(KeyboardState::new()),
}
}
pub(crate) fn connection(&self) -> &Arc<CdpConnection> {
&self.connection
}
pub(crate) fn session_id(&self) -> &str {
&self.session_id
}
pub(crate) fn frame_id(&self) -> &str {
&self.frame_id
}
pub fn press(&self, key: &str) -> KeyboardPressBuilder<'_> {
KeyboardPressBuilder::new(self, key)
}
pub(crate) async fn press_internal(
&self,
key: &str,
delay: Option<Duration>,
) -> Result<(), LocatorError> {
let parts: Vec<&str> = key.split('+').collect();
let actual_key = parts.last().copied().unwrap_or(key);
for part in &parts[..parts.len().saturating_sub(1)] {
let modifier_key = self.resolve_modifier(part);
self.down(&modifier_key).await?;
}
let need_shift = is_uppercase_letter(actual_key);
if need_shift {
self.down("Shift").await?;
}
self.down(actual_key).await?;
if let Some(d) = delay {
tokio::time::sleep(d).await;
}
self.up(actual_key).await?;
if need_shift {
self.up("Shift").await?;
}
for part in parts[..parts.len().saturating_sub(1)].iter().rev() {
let modifier_key = self.resolve_modifier(part);
self.up(&modifier_key).await?;
}
Ok(())
}
fn resolve_modifier(&self, key: &str) -> String {
match key {
"ControlOrMeta" => {
if cfg!(target_os = "macos") {
"Meta".to_string()
} else {
"Control".to_string()
}
}
_ => key.to_string(),
}
}
#[instrument(level = "debug", skip(self), fields(key = %key))]
pub async fn down(&self, key: &str) -> Result<(), LocatorError> {
let def = get_key_definition(key)
.ok_or_else(|| LocatorError::EvaluationError(format!("Unknown key: {key}")))?;
let is_repeat = {
let mut state = self.state.lock().await;
state.key_down(key)
};
let state = self.state.lock().await;
let current_modifiers = state.modifiers;
drop(state);
debug!(code = def.code, key = def.key, is_repeat, "Key down");
let params = DispatchKeyEventParams {
event_type: KeyEventType::KeyDown,
modifiers: Some(current_modifiers),
timestamp: None,
text: def.text.map(String::from),
unmodified_text: def.text.map(String::from),
key_identifier: None,
code: Some(def.code.to_string()),
key: Some(def.key.to_string()),
windows_virtual_key_code: Some(def.key_code),
native_virtual_key_code: Some(def.key_code),
auto_repeat: Some(is_repeat),
is_keypad: Some(def.is_keypad),
is_system_key: None,
commands: None,
};
self.dispatch_key_event(params).await?;
if !is_modifier_key(key) {
if let Some(text) = def.text {
let char_params = DispatchKeyEventParams {
event_type: KeyEventType::Char,
modifiers: Some(current_modifiers),
timestamp: None,
text: Some(text.to_string()),
unmodified_text: Some(text.to_string()),
key_identifier: None,
code: Some(def.code.to_string()),
key: Some(def.key.to_string()),
windows_virtual_key_code: Some(def.key_code),
native_virtual_key_code: Some(def.key_code),
auto_repeat: None,
is_keypad: Some(def.is_keypad),
is_system_key: None,
commands: None,
};
self.dispatch_key_event(char_params).await?;
}
}
Ok(())
}
#[instrument(level = "debug", skip(self), fields(key = %key))]
pub async fn up(&self, key: &str) -> Result<(), LocatorError> {
let def = get_key_definition(key)
.ok_or_else(|| LocatorError::EvaluationError(format!("Unknown key: {key}")))?;
{
let mut state = self.state.lock().await;
state.key_up(key);
}
let state = self.state.lock().await;
let current_modifiers = state.modifiers;
drop(state);
debug!(code = def.code, key = def.key, "Key up");
let params = DispatchKeyEventParams {
event_type: KeyEventType::KeyUp,
modifiers: Some(current_modifiers),
timestamp: None,
text: None,
unmodified_text: None,
key_identifier: None,
code: Some(def.code.to_string()),
key: Some(def.key.to_string()),
windows_virtual_key_code: Some(def.key_code),
native_virtual_key_code: Some(def.key_code),
auto_repeat: None,
is_keypad: Some(def.is_keypad),
is_system_key: None,
commands: None,
};
self.dispatch_key_event(params).await
}
#[instrument(level = "debug", skip(self), fields(text_len = text.len()))]
pub async fn type_text(&self, text: &str) -> Result<(), LocatorError> {
self.type_text_with_delay(text, None).await
}
#[instrument(level = "debug", skip(self), fields(text_len = text.len()))]
pub async fn type_text_with_delay(
&self,
text: &str,
delay: Option<Duration>,
) -> Result<(), LocatorError> {
for ch in text.chars() {
let char_str = ch.to_string();
if get_key_definition(&char_str).is_some() {
let need_shift = ch.is_ascii_uppercase();
if need_shift {
self.down("Shift").await?;
}
self.down(&char_str).await?;
self.up(&char_str).await?;
if need_shift {
self.up("Shift").await?;
}
} else {
let params = DispatchKeyEventParams {
event_type: KeyEventType::Char,
modifiers: None,
timestamp: None,
text: Some(char_str.clone()),
unmodified_text: Some(char_str),
key_identifier: None,
code: None,
key: None,
windows_virtual_key_code: None,
native_virtual_key_code: None,
auto_repeat: None,
is_keypad: None,
is_system_key: None,
commands: None,
};
self.dispatch_key_event(params).await?;
}
if let Some(d) = delay {
tokio::time::sleep(d).await;
}
}
Ok(())
}
#[instrument(level = "debug", skip(self), fields(text_len = text.len()))]
pub async fn insert_text(&self, text: &str) -> Result<(), LocatorError> {
debug!("Inserting text directly");
self.connection
.send_command::<_, serde_json::Value>(
"Input.insertText",
Some(InsertTextParams {
text: text.to_string(),
}),
Some(&self.session_id),
)
.await?;
Ok(())
}
async fn dispatch_key_event(&self, params: DispatchKeyEventParams) -> Result<(), LocatorError> {
self.connection
.send_command::<_, serde_json::Value>(
"Input.dispatchKeyEvent",
Some(params),
Some(&self.session_id),
)
.await?;
Ok(())
}
}