1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-16 00:03:56 +00:00

[PM-22758] Configurable Keyboard Shortcut for Autotype (#16613)

* [PM-22785] Initial push with configuration and ipc changes for the configurable autotype keyboard shortcut

* [PM-22785] Add messy code with working configurable hotkey

* [PM-22785] Add more messy rust code

* [PM-22785] Add temp changes with configurable hotkey ui

* Add shortcut display to settings

* [PM-22785] Logic updates. Ran npm run prettier and lint:fix.

* [PM-22785] Add back disableAutotype with refactors.

* [PM-22785] Clean up Rust code

* [PM-22785] Clean up Rust code v2

* [PM-22785] Add unicode bounds in Rust code

* [PM-22785] Update rust code comments

* [PM-22785] Add unicode_value byte length check post-encoding

* [PM-22785] Extract encoding to a separate function

* Various fixes for the autotype setting label

* Misc component fixes

* Disallow nunmbers and allow Win key

* Themify edit shortcut

* Change display of Super to Win

* Create autotype format method

* Autotpe modal cleanup

* [PM-22785] Some cleanup

* Add unit tests and adjust error handling

* [PM-22785] Fix build issues on Mac and Linux

* [PM-22785] Linting fix

* Remove unused message

* [PM-22785] Linting fix

* [PM-22785] More linting fix

* [PM-22785] Address initial PR comments

* [PM-22785] Comment change

* [PM-22785] If statement change

* [PM-22785] Update with fixes from PR comments

* [PM-22785] Update with fixes from PR comments version ?

* add unit tests for get_alphabetic_hot_key()

* Fix tests

* Add missing mock to tests

* [PM-22785] Update with small fixes via PR comments

---------

Co-authored-by: Robyn MacCallum <robyntmaccallum@gmail.com>
Co-authored-by: neuronull <9162534+neuronull@users.noreply.github.com>
This commit is contained in:
Colton Hurst
2025-09-29 10:20:15 -04:00
committed by GitHub
parent 018b4d5eb4
commit fc53eae4c5
18 changed files with 802 additions and 50 deletions

View File

@@ -17,6 +17,6 @@ pub fn get_foreground_window_title() -> std::result::Result<String, ()> {
///
/// TODO: The error handling will be improved in a future PR: PM-23615
#[allow(clippy::result_unit_err)]
pub fn type_input(input: Vec<u16>) -> std::result::Result<(), ()> {
windowing::type_input(input)
pub fn type_input(input: Vec<u16>, keyboard_shortcut: Vec<String>) -> std::result::Result<(), ()> {
windowing::type_input(input, keyboard_shortcut)
}

View File

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

View File

@@ -1,7 +1,10 @@
pub fn get_foreground_window_title() -> std::result::Result<String, ()> {
todo!("Bitwarden does not yet support Mac OS autotype");
todo!("Bitwarden does not yet support macOS autotype");
}
pub fn type_input(_input: Vec<u16>) -> std::result::Result<(), ()> {
todo!("Bitwarden does not yet support Mac OS autotype");
pub fn type_input(
_input: Vec<u16>,
_keyboard_shortcut: Vec<String>,
) -> std::result::Result<(), ()> {
todo!("Bitwarden does not yet support macOS autotype");
}

View File

@@ -25,25 +25,29 @@ pub fn get_foreground_window_title() -> std::result::Result<String, ()> {
/// Attempts to type the input text wherever the user's cursor is.
///
/// `input` must be an array of utf-16 encoded characters to insert.
/// `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
///
/// https://learn.microsoft.com/en-in/windows/win32/api/winuser/nf-winuser-sendinput
pub fn type_input(input: Vec<u16>) -> Result<(), ()> {
const TAB_KEY: u16 = 9;
pub fn type_input(input: Vec<u16>, keyboard_shortcut: Vec<String>) -> Result<(), ()> {
const TAB_KEY: u8 = 9;
let mut keyboard_inputs: Vec<INPUT> = Vec::new();
// Release hotkeys
keyboard_inputs.push(build_virtual_key_input(InputKeyPress::Up, 0x11)); // ctrl
keyboard_inputs.push(build_virtual_key_input(InputKeyPress::Up, 0x10)); // shift
keyboard_inputs.push(build_unicode_input(InputKeyPress::Up, 42)); // b
// Add key "up" inputs for the shortcut
for key in keyboard_shortcut {
keyboard_inputs.push(convert_shortcut_key_to_up_input(key)?);
}
// Add key "down" and "up" inputs for the input
// (currently in this form: {username}/t{password})
for i in input {
let next_down_input = if i == TAB_KEY {
let next_down_input = if i == TAB_KEY.into() {
build_virtual_key_input(InputKeyPress::Down, i as u8)
} else {
build_unicode_input(InputKeyPress::Down, i)
};
let next_up_input = if i == TAB_KEY {
let next_up_input = if i == TAB_KEY.into() {
build_virtual_key_input(InputKeyPress::Up, i as u8)
} else {
build_unicode_input(InputKeyPress::Up, i)
@@ -56,6 +60,51 @@ pub fn type_input(input: Vec<u16>) -> Result<(), ()> {
send_input(keyboard_inputs)
}
/// 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";
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)?),
})
}
/// Given a letter that is a String, get the utf16 encoded
/// decimal version of the letter as long as it meets the
/// [a-z][A-Z] restriction.
///
/// 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, ()> {
if letter.len() != 1 {
return Err(());
}
let c = letter.chars().next().expect("letter is size 1");
// is_ascii_alphabetic() checks for:
// U+0041 `A` ..= U+005A `Z`, or U+0061 `a` ..= U+007A `z`
if !c.is_ascii_alphabetic() {
return Err(());
}
Ok(c as u16)
}
/// Gets the foreground window handle.
///
/// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getforegroundwindow
@@ -198,3 +247,32 @@ fn send_input(inputs: Vec<INPUT>) -> Result<(), ()> {
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn get_alphabetic_hot_key_happy() {
for c in ('a'..='z').chain('A'..='Z') {
let letter = c.to_string();
println!("{}", letter);
let converted = get_alphabetic_hotkey(letter).unwrap();
assert_eq!(converted, c as u16);
}
}
#[test]
#[should_panic = ""]
fn get_alphabetic_hot_key_fail_not_single_char() {
let letter = String::from("foo");
get_alphabetic_hotkey(letter).unwrap();
}
#[test]
#[should_panic = ""]
fn get_alphabetic_hot_key_fail_not_alphabetic() {
let letter = String::from("🚀");
get_alphabetic_hotkey(letter).unwrap();
}
}

View File

@@ -234,5 +234,5 @@ export declare namespace chromium_importer {
}
export declare namespace autotype {
export function getForegroundWindowTitle(): string
export function typeInput(input: Array<number>): void
export function typeInput(input: Array<number>, keyboardShortcut: Array<string>): void
}

View File

@@ -1044,8 +1044,11 @@ pub mod autotype {
}
#[napi]
pub fn type_input(input: Vec<u16>) -> napi::Result<(), napi::Status> {
autotype::type_input(input).map_err(|_| {
pub fn type_input(
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())
})
}