1
0
mirror of https://github.com/bitwarden/browser.git synced 2025-12-05 09:14:28 +01:00
This commit is contained in:
neuronull 2025-12-04 18:39:15 -06:00 committed by GitHub
commit 7a17d40a7a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 167 additions and 109 deletions

View File

@ -184,6 +184,7 @@
"html-webpack-injector",
"html-webpack-plugin",
"interprocess",
"itertools",
"json5",
"keytar",
"libc",

View File

@ -344,6 +344,7 @@ name = "autotype"
version = "0.0.0"
dependencies = [
"anyhow",
"itertools",
"mockall",
"serial_test",
"tracing",
@ -1060,6 +1061,12 @@ dependencies = [
"zeroize",
]
[[package]]
name = "either"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "elliptic-curve"
version = "0.13.8"
@ -1657,6 +1664,15 @@ version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "itertools"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.15"

View File

@ -39,6 +39,8 @@ futures = "=0.3.31"
hex = "=0.4.3"
homedir = "=0.3.4"
interprocess = "=2.2.1"
itertools = "=0.14.0"
keytar = "=0.1.6"
libc = "=0.2.177"
linux-keyutils = "=0.2.4"
memsec = "=0.7.0"

View File

@ -9,6 +9,7 @@ publish.workspace = true
anyhow = { workspace = true }
[target.'cfg(windows)'.dependencies]
itertools.workspace = true
mockall = "=0.14.0"
serial_test = "=3.2.0"
tracing.workspace = true

View File

@ -28,6 +28,6 @@ pub fn get_foreground_window_title() -> Result<String> {
/// This function returns an `anyhow::Error` if there is any
/// issue in typing the input. Detailed reasons will
/// vary based on platform implementation.
pub fn type_input(input: Vec<u16>, keyboard_shortcut: Vec<String>) -> Result<()> {
pub fn type_input(input: &[u16], keyboard_shortcut: &[String]) -> Result<()> {
windowing::type_input(input, keyboard_shortcut)
}

View File

@ -2,6 +2,6 @@ pub fn get_foreground_window_title() -> anyhow::Result<String> {
todo!("Bitwarden does not yet support Linux autotype");
}
pub fn type_input(_input: Vec<u16>, _keyboard_shortcut: Vec<String>) -> anyhow::Result<()> {
pub fn type_input(_input: &[u16], _keyboard_shortcut: &[String]) -> anyhow::Result<()> {
todo!("Bitwarden does not yet support Linux autotype");
}

View File

@ -2,6 +2,6 @@ pub fn get_foreground_window_title() -> anyhow::Result<String> {
todo!("Bitwarden does not yet support macOS autotype");
}
pub fn type_input(_input: Vec<u16>, _keyboard_shortcut: Vec<String>) -> anyhow::Result<()> {
pub fn type_input(_input: &[u16], _keyboard_shortcut: &[String]) -> anyhow::Result<()> {
todo!("Bitwarden does not yet support macOS autotype");
}

View File

@ -1,6 +1,10 @@
use anyhow::Result;
use itertools::Itertools;
use tracing::debug;
use windows::Win32::Foundation::{GetLastError, SetLastError, WIN32_ERROR};
use windows::Win32::{
Foundation::{GetLastError, SetLastError, WIN32_ERROR},
UI::Input::KeyboardAndMouse::INPUT,
};
mod type_input;
mod window_title;
@ -12,7 +16,7 @@ const WIN32_SUCCESS: WIN32_ERROR = WIN32_ERROR(0);
/// win32 errors.
#[cfg_attr(test, mockall::automock)]
trait ErrorOperations {
/// https://learn.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-setlasterror
/// <https://learn.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-setlasterror>
fn set_last_error(err: u32) {
debug!(err, "Calling SetLastError");
unsafe {
@ -20,7 +24,7 @@ trait ErrorOperations {
}
}
/// https://learn.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror
/// <https://learn.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror>
fn get_last_error() -> WIN32_ERROR {
let last_err = unsafe { GetLastError() };
debug!("GetLastError(): {}", last_err.to_hresult().message());
@ -36,6 +40,23 @@ pub fn get_foreground_window_title() -> Result<String> {
window_title::get_foreground_window_title()
}
pub fn type_input(input: Vec<u16>, keyboard_shortcut: Vec<String>) -> Result<()> {
type_input::type_input(input, keyboard_shortcut)
/// `KeyboardShortcutInput` is an `INPUT` of one of the valid shortcut keys:
/// - Control
/// - Alt
/// - Super
/// - Shift
/// - \[a-z\]\[A-Z\]
struct KeyboardShortcutInput(INPUT);
pub fn type_input(input: &[u16], keyboard_shortcut: &[String]) -> Result<()> {
debug!(?keyboard_shortcut, "Using keyboard shortcut.");
// convert the raw string input to Windows input and error
// if any key is not a valid keyboard shortcut input
let keyboard_shortcut: Vec<KeyboardShortcutInput> = keyboard_shortcut
.iter()
.map(|s| KeyboardShortcutInput::try_from(s.as_str()))
.try_collect()?;
type_input::type_input(input, &keyboard_shortcut)
}

View File

@ -1,3 +1,5 @@
use std::sync::OnceLock;
use anyhow::{anyhow, Result};
use tracing::{debug, error};
use windows::Win32::UI::Input::KeyboardAndMouse::{
@ -5,7 +7,15 @@ use windows::Win32::UI::Input::KeyboardAndMouse::{
VIRTUAL_KEY,
};
use super::{ErrorOperations, Win32ErrorOperations};
use super::{ErrorOperations, KeyboardShortcutInput, Win32ErrorOperations};
const SHIFT_KEY_STR: &str = "Shift";
const CONTROL_KEY_STR: &str = "Control";
const ALT_KEY_STR: &str = "Alt";
const LEFT_WINDOWS_KEY_STR: &str = "Super";
const IS_VIRTUAL_KEY: bool = true;
const IS_REAL_KEY: bool = false;
/// `InputOperations` provides an interface to Window32 API for
/// working with inputs.
@ -13,16 +23,21 @@ use super::{ErrorOperations, Win32ErrorOperations};
trait InputOperations {
/// Attempts to type the provided input wherever the user's cursor is.
///
/// https://learn.microsoft.com/en-in/windows/win32/api/winuser/nf-winuser-sendinput
/// <https://learn.microsoft.com/en-in/windows/win32/api/winuser/nf-winuser-sendinput>
fn send_input(inputs: &[INPUT]) -> u32;
}
struct Win32InputOperations;
static INPUT_STRUCT_SIZE: OnceLock<i32> = OnceLock::new();
impl InputOperations for Win32InputOperations {
fn send_input(inputs: &[INPUT]) -> u32 {
const INPUT_STRUCT_SIZE: i32 = std::mem::size_of::<INPUT>() as i32;
let insert_count = unsafe { SendInput(inputs, INPUT_STRUCT_SIZE) };
let size = *(INPUT_STRUCT_SIZE.get_or_init(|| {
i32::try_from(std::mem::size_of::<INPUT>()).expect("have space for INPUT size")
}));
let insert_count = unsafe { SendInput(inputs, size) };
debug!(insert_count, "SendInput() called.");
@ -33,40 +48,37 @@ impl InputOperations for Win32InputOperations {
/// Attempts to type the input text wherever the user's cursor is.
///
/// `input` must be a vector of utf-16 encoded characters to insert.
/// `keyboard_shortcut` must be a vector of Strings, where valid shortcut keys: Control, Alt, Super,
/// Shift, letters a - Z
/// `keyboard_shortcut` is a vector of valid shortcut keys.
///
/// https://learn.microsoft.com/en-in/windows/win32/api/winuser/nf-winuser-sendinput
pub(super) fn type_input(input: Vec<u16>, keyboard_shortcut: Vec<String>) -> Result<()> {
/// <https://learn.microsoft.com/en-in/windows/win32/api/winuser/nf-winuser-sendinput>
pub(super) fn type_input(input: &[u16], keyboard_shortcut: &[KeyboardShortcutInput]) -> Result<()> {
// the length of this vec is always shortcut keys to release + (2x length of input chars)
let mut keyboard_inputs: Vec<INPUT> =
Vec::with_capacity(keyboard_shortcut.len() + (input.len() * 2));
debug!(?keyboard_shortcut, "Converting keyboard shortcut to input.");
// Add key "up" inputs for the shortcut
for key in keyboard_shortcut {
keyboard_inputs.push(convert_shortcut_key_to_up_input(key)?);
// insert the keyboard shortcut
for shortcut in keyboard_shortcut {
keyboard_inputs.push(shortcut.0);
}
add_input(&input, &mut keyboard_inputs);
add_input(input, &mut keyboard_inputs);
send_input::<Win32InputOperations, Win32ErrorOperations>(keyboard_inputs)
send_input::<Win32InputOperations, Win32ErrorOperations>(&keyboard_inputs)
}
// Add key "down" and "up" inputs for the input
// (currently in this form: {username}/t{password})
fn add_input(input: &[u16], keyboard_inputs: &mut Vec<INPUT>) {
const TAB_KEY: u8 = 9;
const TAB_KEY: u16 = 9;
for i in input {
let next_down_input = if *i == TAB_KEY.into() {
build_virtual_key_input(InputKeyPress::Down, *i as u8)
let next_down_input = if *i == TAB_KEY {
build_virtual_key_input(InputKeyPress::Down, *i)
} else {
build_unicode_input(InputKeyPress::Down, *i)
};
let next_up_input = if *i == TAB_KEY.into() {
build_virtual_key_input(InputKeyPress::Up, *i as u8)
let next_up_input = if *i == TAB_KEY {
build_virtual_key_input(InputKeyPress::Up, *i)
} else {
build_unicode_input(InputKeyPress::Up, *i)
};
@ -76,26 +88,27 @@ fn add_input(input: &[u16], keyboard_inputs: &mut Vec<INPUT>) {
}
}
/// Converts a valid shortcut key to an "up" keyboard input.
///
/// `input` must be a valid shortcut key: Control, Alt, Super, Shift, letters [a-z][A-Z]
fn convert_shortcut_key_to_up_input(key: String) -> Result<INPUT> {
const SHIFT_KEY: u8 = 0x10;
const SHIFT_KEY_STR: &str = "Shift";
const CONTROL_KEY: u8 = 0x11;
const CONTROL_KEY_STR: &str = "Control";
const ALT_KEY: u8 = 0x12;
const ALT_KEY_STR: &str = "Alt";
const LEFT_WINDOWS_KEY: u8 = 0x5B;
const LEFT_WINDOWS_KEY_STR: &str = "Super";
impl TryFrom<&str> for KeyboardShortcutInput {
type Error = anyhow::Error;
Ok(match key.as_str() {
SHIFT_KEY_STR => build_virtual_key_input(InputKeyPress::Up, SHIFT_KEY),
CONTROL_KEY_STR => build_virtual_key_input(InputKeyPress::Up, CONTROL_KEY),
ALT_KEY_STR => build_virtual_key_input(InputKeyPress::Up, ALT_KEY),
LEFT_WINDOWS_KEY_STR => build_virtual_key_input(InputKeyPress::Up, LEFT_WINDOWS_KEY),
_ => build_unicode_input(InputKeyPress::Up, get_alphabetic_hotkey(key)?),
})
fn try_from(key: &str) -> std::result::Result<Self, Self::Error> {
const SHIFT_KEY: u16 = 0x10;
const CONTROL_KEY: u16 = 0x11;
const ALT_KEY: u16 = 0x12;
const LEFT_WINDOWS_KEY: u16 = 0x5B;
// the modifier keys are using the Up keypress variant because the user has already
// pressed those keys in order to trigger the feature.
let input = match key {
SHIFT_KEY_STR => build_virtual_key_input(InputKeyPress::Up, SHIFT_KEY),
CONTROL_KEY_STR => build_virtual_key_input(InputKeyPress::Up, CONTROL_KEY),
ALT_KEY_STR => build_virtual_key_input(InputKeyPress::Up, ALT_KEY),
LEFT_WINDOWS_KEY_STR => build_virtual_key_input(InputKeyPress::Up, LEFT_WINDOWS_KEY),
_ => build_unicode_input(InputKeyPress::Up, get_alphabetic_hotkey(key)?),
};
Ok(KeyboardShortcutInput(input))
}
}
/// Given a letter that is a String, get the utf16 encoded
@ -105,7 +118,7 @@ fn convert_shortcut_key_to_up_input(key: String) -> Result<INPUT> {
/// Because we only accept [a-z][A-Z], the decimal u16
/// cast of the letter is safe because the unicode code point
/// of these characters fits in a u16.
fn get_alphabetic_hotkey(letter: String) -> Result<u16> {
fn get_alphabetic_hotkey(letter: &str) -> Result<u16> {
if letter.len() != 1 {
error!(
len = letter.len(),
@ -135,23 +148,28 @@ fn get_alphabetic_hotkey(letter: String) -> Result<u16> {
}
/// An input key can be either pressed (down), or released (up).
#[derive(Copy, Clone)]
enum InputKeyPress {
Down,
Up,
}
/// A function for easily building keyboard unicode INPUT structs used in SendInput().
///
/// Before modifying this function, make sure you read the SendInput() documentation:
/// https://learn.microsoft.com/en-in/windows/win32/api/winuser/nf-winuser-sendinput
fn build_unicode_input(key_press: InputKeyPress, character: u16) -> INPUT {
/// Before modifying this function, make sure you read the `SendInput()` documentation:
/// <https://learn.microsoft.com/en-in/windows/win32/api/winuser/nf-winuser-sendinput>
/// <https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes>
fn build_input(key_press: InputKeyPress, character: u16, is_virtual: bool) -> INPUT {
let (w_vk, w_scan) = if is_virtual {
(VIRTUAL_KEY(character), 0)
} else {
(VIRTUAL_KEY::default(), character)
};
match key_press {
InputKeyPress::Down => INPUT {
r#type: INPUT_KEYBOARD,
Anonymous: INPUT_0 {
ki: KEYBDINPUT {
wVk: Default::default(),
wScan: character,
wVk: w_vk,
wScan: w_scan,
dwFlags: KEYEVENTF_UNICODE,
time: 0,
dwExtraInfo: 0,
@ -162,8 +180,8 @@ fn build_unicode_input(key_press: InputKeyPress, character: u16) -> INPUT {
r#type: INPUT_KEYBOARD,
Anonymous: INPUT_0 {
ki: KEYBDINPUT {
wVk: Default::default(),
wScan: character,
wVk: w_vk,
wScan: w_scan,
dwFlags: KEYEVENTF_KEYUP | KEYEVENTF_UNICODE,
time: 0,
dwExtraInfo: 0,
@ -173,53 +191,29 @@ fn build_unicode_input(key_press: InputKeyPress, character: u16) -> INPUT {
}
}
/// A function for easily building keyboard virtual-key INPUT structs used in SendInput().
///
/// Before modifying this function, make sure you read the SendInput() documentation:
/// https://learn.microsoft.com/en-in/windows/win32/api/winuser/nf-winuser-sendinput
/// https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
fn build_virtual_key_input(key_press: InputKeyPress, virtual_key: u8) -> INPUT {
match key_press {
InputKeyPress::Down => INPUT {
r#type: INPUT_KEYBOARD,
Anonymous: INPUT_0 {
ki: KEYBDINPUT {
wVk: VIRTUAL_KEY(virtual_key as u16),
wScan: Default::default(),
dwFlags: Default::default(),
time: 0,
dwExtraInfo: 0,
},
},
},
InputKeyPress::Up => INPUT {
r#type: INPUT_KEYBOARD,
Anonymous: INPUT_0 {
ki: KEYBDINPUT {
wVk: VIRTUAL_KEY(virtual_key as u16),
wScan: Default::default(),
dwFlags: KEYEVENTF_KEYUP,
time: 0,
dwExtraInfo: 0,
},
},
},
}
/// A function for easily building keyboard unicode `INPUT` structs used in `SendInput()`.
fn build_unicode_input(key_press: InputKeyPress, character: u16) -> INPUT {
build_input(key_press, character, IS_REAL_KEY)
}
fn send_input<I, E>(inputs: Vec<INPUT>) -> Result<()>
/// A function for easily building keyboard virtual-key `INPUT` structs used in `SendInput()`.
fn build_virtual_key_input(key_press: InputKeyPress, character: u16) -> INPUT {
build_input(key_press, character, IS_VIRTUAL_KEY)
}
fn send_input<I, E>(inputs: &[INPUT]) -> Result<()>
where
I: InputOperations,
E: ErrorOperations,
{
let insert_count = I::send_input(&inputs);
let insert_count = I::send_input(inputs);
if insert_count == 0 {
let last_err = E::get_last_error().to_hresult().message();
error!(GetLastError = %last_err, "SendInput sent 0 inputs. Input was blocked by another thread.");
return Err(anyhow!("SendInput sent 0 inputs. Input was blocked by another thread. GetLastError: {last_err}"));
} else if insert_count != inputs.len() as u32 {
} else if insert_count != u32::try_from(inputs.len()).expect("to convert inputs len to u32") {
let last_err = E::get_last_error().to_hresult().message();
error!(sent = %insert_count, expected = inputs.len(), GetLastError = %last_err,
"SendInput sent does not match expected."
@ -237,8 +231,9 @@ where
mod tests {
//! For the mocking of the traits that are static methods, we need to use the `serial_test`
//! crate in order to mock those, since the mock expectations set have to be global in
//! absence of a `self`. More info: https://docs.rs/mockall/latest/mockall/#static-methods
//! absence of a `self`. More info: <https://docs.rs/mockall/latest/mockall/#static-methods>
use itertools::Itertools;
use serial_test::serial;
use windows::Win32::Foundation::WIN32_ERROR;
@ -249,7 +244,7 @@ mod tests {
fn get_alphabetic_hot_key_succeeds() {
for c in ('a'..='z').chain('A'..='Z') {
let letter = c.to_string();
let converted = get_alphabetic_hotkey(letter).unwrap();
let converted = get_alphabetic_hotkey(&letter).unwrap();
assert_eq!(converted, c as u16);
}
}
@ -258,14 +253,14 @@ mod tests {
#[should_panic = "Final keyboard shortcut key should be a single character: foo"]
fn get_alphabetic_hot_key_fail_not_single_char() {
let letter = String::from("foo");
get_alphabetic_hotkey(letter).unwrap();
get_alphabetic_hotkey(&letter).unwrap();
}
#[test]
#[should_panic = "Letter is not ASCII Alphabetic ([a-z][A-Z]): '}'"]
fn get_alphabetic_hot_key_fail_not_alphabetic() {
let letter = String::from("}");
get_alphabetic_hotkey(letter).unwrap();
get_alphabetic_hotkey(&letter).unwrap();
}
#[test]
@ -275,7 +270,7 @@ mod tests {
ctxi.checkpoint();
ctxi.expect().returning(|_| 1);
send_input::<MockInputOperations, MockErrorOperations>(vec![build_unicode_input(
send_input::<MockInputOperations, MockErrorOperations>(&[build_unicode_input(
InputKeyPress::Up,
0,
)])
@ -284,6 +279,29 @@ mod tests {
drop(ctxi);
}
#[test]
#[serial]
fn keyboard_shortcut_conversion_succeeds() {
let keyboard_shortcut = [CONTROL_KEY_STR, SHIFT_KEY_STR, "B"];
let _: Vec<KeyboardShortcutInput> = keyboard_shortcut
.iter()
.map(|s| KeyboardShortcutInput::try_from(*s))
.try_collect()
.unwrap();
}
#[test]
#[serial]
#[should_panic = "Letter is not ASCII Alphabetic ([a-z][A-Z]): '1'"]
fn keyboard_shortcut_conversion_fails_invalid_key() {
let keyboard_shortcut = [CONTROL_KEY_STR, SHIFT_KEY_STR, "1"];
let _: Vec<KeyboardShortcutInput> = keyboard_shortcut
.iter()
.map(|s| KeyboardShortcutInput::try_from(*s))
.try_collect()
.unwrap();
}
#[test]
#[serial]
#[should_panic(
@ -298,7 +316,7 @@ mod tests {
ctxge.checkpoint();
ctxge.expect().returning(|| WIN32_ERROR(1));
send_input::<MockInputOperations, MockErrorOperations>(vec![build_unicode_input(
send_input::<MockInputOperations, MockErrorOperations>(&[build_unicode_input(
InputKeyPress::Up,
0,
)])
@ -320,7 +338,7 @@ mod tests {
ctxge.checkpoint();
ctxge.expect().returning(|| WIN32_ERROR(1));
send_input::<MockInputOperations, MockErrorOperations>(vec![build_unicode_input(
send_input::<MockInputOperations, MockErrorOperations>(&[build_unicode_input(
InputKeyPress::Up,
0,
)])

View File

@ -11,10 +11,10 @@ use super::{ErrorOperations, Win32ErrorOperations, WIN32_SUCCESS};
#[cfg_attr(test, mockall::automock)]
trait WindowHandleOperations {
// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowtextlengthw
// <https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowtextlengthw>
fn get_window_text_length_w(&self) -> Result<i32>;
// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowtextw
// <https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowtextw>
fn get_window_text_w(&self, buffer: &mut Vec<u16>) -> Result<i32>;
}
@ -70,7 +70,7 @@ pub(super) fn get_foreground_window_title() -> Result<String> {
/// Retrieves the foreground window handle and validates it.
fn get_foreground_window_handle() -> Result<WindowHandle> {
// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getforegroundwindow
// <https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getforegroundwindow>
let handle = unsafe { GetForegroundWindow() };
debug!("GetForegroundWindow() called.");
@ -87,7 +87,7 @@ fn get_foreground_window_handle() -> Result<WindowHandle> {
///
/// # Errors
///
/// - If the length zero and GetLastError() != 0, return the GetLastError() message.
/// - If the length zero and `GetLastError()` != 0, return the `GetLastError()` message.
fn get_window_title_length<H, E>(window_handle: &H) -> Result<usize>
where
H: WindowHandleOperations,
@ -128,7 +128,7 @@ where
/// # Errors
///
/// - If the actual window title length (what the win32 API declares was written into the buffer),
/// is length zero and GetLastError() != 0 , return the GetLastError() message.
/// is length zero and `GetLastError()` != 0 , return the `GetLastError()` message.
fn get_window_title<H, E>(window_handle: &H, expected_title_length: usize) -> Result<String>
where
H: WindowHandleOperations,
@ -140,7 +140,7 @@ where
// The upstream will make a contains comparison on what we return, so an empty string
// will not result on a match.
warn!("Window title length is zero.");
return Ok(String::from(""));
return Ok(String::new());
}
let mut buffer: Vec<u16> = vec![0; expected_title_length + 1]; // add extra space for the null character
@ -171,7 +171,7 @@ where
mod tests {
//! For the mocking of the traits that are static methods, we need to use the `serial_test`
//! crate in order to mock those, since the mock expectations set have to be global in
//! absence of a `self`. More info: https://docs.rs/mockall/latest/mockall/#static-methods
//! absence of a `self`. More info: <https://docs.rs/mockall/latest/mockall/#static-methods>
use mockall::predicate;
use serial_test::serial;

View File

@ -1200,8 +1200,7 @@ pub mod autotype {
input: Vec<u16>,
keyboard_shortcut: Vec<String>,
) -> napi::Result<(), napi::Status> {
autotype::type_input(input, keyboard_shortcut).map_err(|_| {
napi::Error::from_reason("Autotype Error: failed to type input".to_string())
})
autotype::type_input(&input, &keyboard_shortcut)
.map_err(|e| napi::Error::from_reason(format!("Autotype Error: {e}")))
}
}