From 7d2cd726ec82d20f18ff3ef2781307bf7a77c7d3 Mon Sep 17 00:00:00 2001 From: zhanba Date: Wed, 25 Mar 2026 23:05:20 +0800 Subject: [PATCH] fix: route keyboard type through text input (#1014) --- cli/src/native/actions.rs | 27 +++++++++++++++++++++ cli/src/native/interaction.rs | 45 +++++++++++++++++++++-------------- 2 files changed, 54 insertions(+), 18 deletions(-) diff --git a/cli/src/native/actions.rs b/cli/src/native/actions.rs index 2365689..4dc0efa 100644 --- a/cli/src/native/actions.rs +++ b/cli/src/native/actions.rs @@ -3115,6 +3115,33 @@ async fn handle_keyboard(cmd: &Value, state: &DaemonState) -> Result { + let text = cmd + .get("text") + .and_then(|v| v.as_str()) + .ok_or("Missing 'text' parameter")?; + interaction::type_text_into_active_context(&mgr.client, &session_id, text, None) + .await?; + return Ok(json!({ "typed": text })); + } + Some("insertText") => { + let text = cmd + .get("text") + .and_then(|v| v.as_str()) + .ok_or("Missing 'text' parameter")?; + mgr.client + .send_command( + "Input.insertText", + Some(json!({ "text": text })), + Some(&session_id), + ) + .await?; + return Ok(json!({ "inserted": true })); + } + _ => {} + } + let event_type = cmd .get("eventType") .and_then(|v| v.as_str()) diff --git a/cli/src/native/interaction.rs b/cli/src/native/interaction.rs index c18d226..0c51ec7 100644 --- a/cli/src/native/interaction.rs +++ b/cli/src/native/interaction.rs @@ -202,25 +202,21 @@ pub async fn type_text( .await?; } + type_text_into_active_context(client, session_id, text, delay_ms).await +} + +pub async fn type_text_into_active_context( + client: &CdpClient, + session_id: &str, + text: &str, + delay_ms: Option, +) -> Result<(), String> { let delay = delay_ms.unwrap_or(0); for ch in text.chars() { - let text_str = ch.to_string(); - let (key, code, key_code) = char_to_key_info(ch); - - // Characters that have no US-keyboard mapping (key_code == 0 and empty - // code) are inserted via `Input.insertText`, matching Playwright's - // keyboard.type() fallback behaviour. This handles emoji, CJK, and - // other characters that don't correspond to a physical key. - if key_code == 0 && code.is_empty() { - client - .send_command_typed::<_, Value>( - "Input.insertText", - &InsertTextParams { text: text_str }, - Some(session_id), - ) - .await?; - } else { + if matches!(ch, '\n' | '\r' | '\t') { + let (key, code, key_code) = char_to_key_info(ch); + let text_str = key_text(&key); client .send_command_typed::<_, Value>( "Input.dispatchKeyEvent", @@ -228,8 +224,8 @@ pub async fn type_text( event_type: "keyDown".to_string(), key: Some(key.clone()), code: Some(code.clone()), - text: Some(text_str.clone()), - unmodified_text: Some(text_str.clone()), + text: text_str.clone(), + unmodified_text: text_str, windows_virtual_key_code: Some(key_code), native_virtual_key_code: Some(key_code), modifiers: None, @@ -254,6 +250,19 @@ pub async fn type_text( Some(session_id), ) .await?; + } else { + // VS Code/Electron webviews reject repeated dispatchKeyEvent calls + // carrying printable `text`. Insert printable characters directly + // and reserve key events for controls like Enter and Tab. + client + .send_command_typed::<_, Value>( + "Input.insertText", + &InsertTextParams { + text: ch.to_string(), + }, + Some(session_id), + ) + .await?; } if delay > 0 {