mirror of
https://github.com/bitwarden/browser
synced 2026-02-05 11:13:44 +00:00
[PM-22785] Initial push with configuration and ipc changes for the configurable autotype keyboard shortcut
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>, keyboardShortcut: Vec<String>) -> std::result::Result<(), ()> {
|
||||
windowing::type_input(input, keyboardShortcut)
|
||||
}
|
||||
|
||||
@@ -28,7 +28,9 @@ pub fn get_foreground_window_title() -> std::result::Result<String, ()> {
|
||||
/// `input` must be an array of utf-16 encoded characters to insert.
|
||||
///
|
||||
/// https://learn.microsoft.com/en-in/windows/win32/api/winuser/nf-winuser-sendinput
|
||||
pub fn type_input(input: Vec<u16>) -> Result<(), ()> {
|
||||
pub fn type_input(input: Vec<u16>, keyboard_input: Vec<String>) -> Result<(), ()> {
|
||||
println!("type_input() hit, keyboardInput is: {:?}", keyboard_input);
|
||||
|
||||
const TAB_KEY: u16 = 9;
|
||||
let mut keyboard_inputs: Vec<INPUT> = Vec::new();
|
||||
|
||||
|
||||
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,8 @@ 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())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -7,18 +7,26 @@ import { WindowMain } from "../../main/window.main";
|
||||
import { stringIsNotUndefinedNullAndEmpty } from "../../utils";
|
||||
|
||||
export class MainDesktopAutotypeService {
|
||||
keySequence: string = "CommandOrControl+Shift+B";
|
||||
autotypeKeyboardShortcut: AutotypeKeyboardShortcut;
|
||||
|
||||
constructor(
|
||||
private logService: LogService,
|
||||
private windowMain: WindowMain,
|
||||
) {}
|
||||
) {
|
||||
this.autotypeKeyboardShortcut = new AutotypeKeyboardShortcut();
|
||||
}
|
||||
|
||||
init() {
|
||||
ipcMain.on("autofill.configureAutotype", (event, data) => {
|
||||
if (data.enabled === true && !globalShortcut.isRegistered(this.keySequence)) {
|
||||
const { response } = data;
|
||||
|
||||
let setCorrectly = this.autotypeKeyboardShortcut.set(response.keyboardShortcut);
|
||||
console.log("Was autotypeKeyboardShortcut set correctly from within the main process? " + setCorrectly);
|
||||
// TODO: What do we do if it wasn't? The value won't change but we need to send a failure message back
|
||||
|
||||
if (response.enabled === true && !globalShortcut.isRegistered(this.autotypeKeyboardShortcut.getElectronFormat())) {
|
||||
this.enableAutotype();
|
||||
} else if (data.enabled === false && globalShortcut.isRegistered(this.keySequence)) {
|
||||
} else if (response.enabled === false && globalShortcut.isRegistered(this.autotypeKeyboardShortcut.getElectronFormat())) {
|
||||
this.disableAutotype();
|
||||
}
|
||||
});
|
||||
@@ -30,21 +38,21 @@ export class MainDesktopAutotypeService {
|
||||
stringIsNotUndefinedNullAndEmpty(response.username) &&
|
||||
stringIsNotUndefinedNullAndEmpty(response.password)
|
||||
) {
|
||||
this.doAutotype(response.username, response.password);
|
||||
this.doAutotype(response.username, response.password, this.autotypeKeyboardShortcut.getArrayFormat());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
disableAutotype() {
|
||||
if (globalShortcut.isRegistered(this.keySequence)) {
|
||||
globalShortcut.unregister(this.keySequence);
|
||||
if (globalShortcut.isRegistered(this.autotypeKeyboardShortcut.getElectronFormat())) {
|
||||
globalShortcut.unregister(this.autotypeKeyboardShortcut.getElectronFormat());
|
||||
}
|
||||
|
||||
this.logService.info("Autotype disabled.");
|
||||
}
|
||||
|
||||
private enableAutotype() {
|
||||
const result = globalShortcut.register(this.keySequence, () => {
|
||||
const result = globalShortcut.register(this.autotypeKeyboardShortcut.getElectronFormat(), () => {
|
||||
const windowTitle = autotype.getForegroundWindowTitle();
|
||||
|
||||
this.windowMain.win.webContents.send("autofill.listenAutotypeRequest", {
|
||||
@@ -57,7 +65,7 @@ export class MainDesktopAutotypeService {
|
||||
: this.logService.info("Enabling autotype failed.");
|
||||
}
|
||||
|
||||
private doAutotype(username: string, password: string) {
|
||||
private doAutotype(username: string, password: string, keyboardShortcut: string[]) {
|
||||
const inputPattern = username + "\t" + password;
|
||||
const inputArray = new Array<number>(inputPattern.length);
|
||||
|
||||
@@ -65,6 +73,6 @@ export class MainDesktopAutotypeService {
|
||||
inputArray[i] = inputPattern.charCodeAt(i);
|
||||
}
|
||||
|
||||
autotype.typeInput(inputArray);
|
||||
autotype.typeInput(inputArray, keyboardShortcut);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
This class provides the following:
|
||||
- A way to get and set an AutotypeKeyboardShortcut value within the main process
|
||||
- A way to set an AutotypeKeyboardShortcut with validation
|
||||
- A way to "get" the value in string array format or a single string format for electron
|
||||
- Default shortcut support
|
||||
|
||||
This is currently only supported for Windows operating systems.
|
||||
*/
|
||||
class AutotypeKeyboardShortcut {
|
||||
private readonly defaultWindowsAutotypeKeyboardShorcut: string[] = ["Control", "Shift", "B"];
|
||||
private autotypeKeyboardShortcut: string[];
|
||||
|
||||
constructor() {
|
||||
this.autotypeKeyboardShortcut = this.defaultWindowsAutotypeKeyboardShorcut;
|
||||
}
|
||||
|
||||
/*
|
||||
Returns a boolean value indicating if the autotypeKeyboardShortcut
|
||||
was valid and set or not.
|
||||
*/
|
||||
set(newAutotypeKeyboardShortcut: string[]) {
|
||||
if (!this.#keyboardShortcutIsValid(newAutotypeKeyboardShortcut)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.autotypeKeyboardShortcut = newAutotypeKeyboardShortcut;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
Returns the autotype keyboard shortcut as a string array.
|
||||
*/
|
||||
getArrayFormat() {
|
||||
return this.autotypeKeyboardShortcut;
|
||||
}
|
||||
|
||||
/*
|
||||
Returns the autotype keyboard shortcut as a single string, as
|
||||
Electron expects. Please note this does not reorder the keys.
|
||||
|
||||
See Electron keyboard shorcut docs for more info:
|
||||
https://www.electronjs.org/docs/latest/tutorial/keyboard-shortcuts
|
||||
*/
|
||||
getElectronFormat() {
|
||||
return this.autotypeKeyboardShortcut.join("+");
|
||||
}
|
||||
|
||||
/*
|
||||
This private function validates the strArray input to make sure the array contains
|
||||
valid, currently accepted shortcut keys for Windows.
|
||||
|
||||
Valid windows shortcut keys: Control, Alt, Super, Shift, letters A - Z
|
||||
Valid macOS shortcut keys: Control, Alt, Command, Shift, letters A - Z (not yet supported)
|
||||
|
||||
See Electron keyboard shorcut docs for more info:
|
||||
https://www.electronjs.org/docs/latest/tutorial/keyboard-shortcuts
|
||||
*/
|
||||
#keyboardShortcutIsValid(strArray: string[]) {
|
||||
const VALID_SHORTCUT_CONTROL_KEYS: string[] = ["Control", "Alt", "Super", "Shift"];
|
||||
const UNICODE_LOWER_BOUND = 65; // 'A' in base 10
|
||||
const UNICODE_UPPER_BOUND = 90; // 'Z' in base 10
|
||||
const MIN_LENGTH: number = 2;
|
||||
const MAX_LENGTH: number = 3;
|
||||
|
||||
// Ensure strArray is a string array of valid length
|
||||
if (strArray === undefined || strArray === null || strArray.length < MIN_LENGTH || strArray.length > MAX_LENGTH) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure strArray is all modifier keys, and that the last key is a modifier key OR a letter key
|
||||
for (let i = 0; i < strArray.length; i++) {
|
||||
if (i < strArray.length - 1) {
|
||||
if (!VALID_SHORTCUT_CONTROL_KEYS.includes(strArray[i])) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!VALID_SHORTCUT_CONTROL_KEYS.includes(strArray[i])) {
|
||||
let unicodeValue: number = strArray[i].charCodeAt(0);
|
||||
|
||||
if (Number.isNaN(unicodeValue) || unicodeValue < UNICODE_LOWER_BOUND || unicodeValue > UNICODE_UPPER_BOUND) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -127,8 +127,8 @@ export default {
|
||||
},
|
||||
);
|
||||
},
|
||||
configureAutotype: (enabled: boolean) => {
|
||||
ipcRenderer.send("autofill.configureAutotype", { enabled });
|
||||
configureAutotype: (enabled: boolean, keyboardShortcut: string[]) => {
|
||||
ipcRenderer.send("autofill.configureAutotype", { enabled, keyboardShortcut });
|
||||
},
|
||||
listenAutotypeRequest: (
|
||||
fn: (
|
||||
|
||||
@@ -17,17 +17,34 @@ import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.servi
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { UserId } from "@bitwarden/user-core";
|
||||
|
||||
// Represents the user's set autotypeEnabled setting
|
||||
export const AUTOTYPE_ENABLED = new KeyDefinition<boolean>(
|
||||
AUTOTYPE_SETTINGS_DISK,
|
||||
"autotypeEnabled",
|
||||
{ deserializer: (b) => b },
|
||||
);
|
||||
|
||||
/*
|
||||
Valid windows shortcut keys: Control, Alt, Super, Shift, letters A - Z
|
||||
Valid macOS shortcut keys: Control, Alt, Command, Shift, letters A - Z
|
||||
|
||||
See Electron keyboard shorcut docs for more info:
|
||||
https://www.electronjs.org/docs/latest/tutorial/keyboard-shortcuts
|
||||
*/
|
||||
export const AUTOTYPE_KEYBOARD_SHORTCUT = new KeyDefinition<string[]>(
|
||||
AUTOTYPE_SETTINGS_DISK,
|
||||
"autotypeKeyboardShortcut",
|
||||
{ deserializer: (b) => b },
|
||||
);
|
||||
|
||||
export class DesktopAutotypeService {
|
||||
private readonly defaultWindowsAutotypeKeyboardShorcut: string[] = ["Control", "Shift", "B"];
|
||||
private readonly autotypeEnabledState = this.globalStateProvider.get(AUTOTYPE_ENABLED);
|
||||
private readonly autotypeKeyboardShortcut = this.globalStateProvider.get(AUTOTYPE_KEYBOARD_SHORTCUT);
|
||||
|
||||
autotypeEnabledUserSetting$: Observable<boolean> = of(false);
|
||||
resolvedAutotypeEnabled$: Observable<boolean> = of(false);
|
||||
autotypeKeyboardShortcut$: Observable<string[]> = of([]);
|
||||
|
||||
constructor(
|
||||
private accountService: AccountService,
|
||||
@@ -51,6 +68,7 @@ export class DesktopAutotypeService {
|
||||
|
||||
async init() {
|
||||
this.autotypeEnabledUserSetting$ = this.autotypeEnabledState.state$;
|
||||
this.autotypeKeyboardShortcut$ = this.autotypeKeyboardShortcut.state$;
|
||||
|
||||
if (this.platformUtilsService.getDevice() === DeviceType.WindowsDesktop) {
|
||||
this.resolvedAutotypeEnabled$ = combineLatest([
|
||||
@@ -76,18 +94,42 @@ export class DesktopAutotypeService {
|
||||
),
|
||||
);
|
||||
|
||||
this.resolvedAutotypeEnabled$.subscribe((enabled) => {
|
||||
ipc.autofill.configureAutotype(enabled);
|
||||
combineLatest([this.resolvedAutotypeEnabled$, this.autotypeKeyboardShortcut$]).subscribe(([resolvedAutotypeEnabled, autotypeKeyboaardShortcut]) => {
|
||||
ipc.autofill.configureAutotype(resolvedAutotypeEnabled, autotypeKeyboaardShortcut);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async setAutotypeEnabledState(enabled: boolean): Promise<void> {
|
||||
|
||||
/////
|
||||
// let's make sure the storage works
|
||||
console.log("----- setAutotypeEnabledState() -----");
|
||||
let currentValue = await firstValueFrom(this.autotypeKeyboardShortcut.state$);
|
||||
console.log("autotypeKeyboardShortcut current value: " + currentValue);
|
||||
|
||||
if (currentValue != undefined && currentValue != null && currentValue.length > 0) {
|
||||
//console.log("clearing the value");
|
||||
//await this.setAutotypeKeyboardShortcutState([]);
|
||||
//let newValue = await firstValueFrom(this.autotypeKeyboardShortcut.state$);
|
||||
//console.log("autotypeKeyboardShortcut new value: " + newValue);
|
||||
} else {
|
||||
console.log("setting the value");
|
||||
await this.setAutotypeKeyboardShortcutState(this.defaultWindowsAutotypeKeyboardShorcut);
|
||||
let newValue = await firstValueFrom(this.autotypeKeyboardShortcut.state$);
|
||||
console.log("autotypeKeyboardShortcut new value: " + newValue);
|
||||
}
|
||||
/////
|
||||
|
||||
await this.autotypeEnabledState.update(() => enabled, {
|
||||
shouldUpdate: (currentlyEnabled) => currentlyEnabled !== enabled,
|
||||
});
|
||||
}
|
||||
|
||||
async setAutotypeKeyboardShortcutState(keyboardShortcut: string[]): Promise<void> {
|
||||
await this.autotypeKeyboardShortcut.update(() => keyboardShortcut);
|
||||
}
|
||||
|
||||
async matchCiphersToWindowTitle(windowTitle: string): Promise<CipherView[]> {
|
||||
const URI_PREFIX = "apptitle://";
|
||||
windowTitle = windowTitle.toLowerCase();
|
||||
|
||||
Reference in New Issue
Block a user