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:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
2
apps/desktop/desktop_native/napi/index.d.ts
vendored
2
apps/desktop/desktop_native/napi/index.d.ts
vendored
@@ -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
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user