-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Desktop autotype remove SHIFT from valid modifier keys #17347
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: autofill/pm-26227/autotype-strict-type-keyboard-input
Are you sure you want to change the base?
Changes from all commits
2e1dfea
082bd43
7f04588
5701b04
f0fd4da
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| use std::{collections::HashMap, sync::OnceLock}; | ||
|
|
||
| // Electron modifier keys | ||
| // <https://www.electronjs.org/docs/latest/tutorial/keyboard-shortcuts#cross-platform-modifiers> | ||
| pub(crate) const CONTROL_KEY_STR: &str = "Control"; | ||
| pub(crate) const ALT_KEY_STR: &str = "Alt"; | ||
| pub(crate) const SUPER_KEY_STR: &str = "Super"; | ||
|
|
||
| // numeric values for modifier keys | ||
| pub(crate) const CONTROL_KEY: u16 = 0x11; | ||
| pub(crate) const ALT_KEY: u16 = 0x12; | ||
| pub(crate) const SUPER_KEY: u16 = 0x5B; | ||
|
|
||
| /// A mapping of <Electron modifier key string> to <numeric representation> | ||
| static MODIFIER_KEYS: OnceLock<HashMap<&str, u16>> = OnceLock::new(); | ||
|
|
||
| /// Provides a mapping of the valid modifier keys' electron | ||
| /// string representation to the numeric representation. | ||
| pub(crate) fn get_modifier_keys() -> &'static HashMap<&'static str, u16> { | ||
| MODIFIER_KEYS.get_or_init(|| { | ||
| HashMap::from([ | ||
| (CONTROL_KEY_STR, CONTROL_KEY), | ||
| (ALT_KEY_STR, ALT_KEY), | ||
| (SUPER_KEY_STR, SUPER_KEY), | ||
| ]) | ||
| }) | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod test { | ||
| use super::get_modifier_keys; | ||
|
|
||
| #[test] | ||
| fn valid_modifier_keys() { | ||
| assert_eq!(get_modifier_keys().get("Control").unwrap(), &0x11); | ||
| assert_eq!(get_modifier_keys().get("Alt").unwrap(), &0x12); | ||
| assert_eq!(get_modifier_keys().get("Super").unwrap(), &0x5B); | ||
| } | ||
|
|
||
| #[test] | ||
| fn does_not_contain_invalid_modifier_keys() { | ||
| assert!(!get_modifier_keys().contains_key("Shift")); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,7 +5,7 @@ | |
| </div> | ||
| <div bitDialogContent> | ||
| <p> | ||
| {{ "editAutotypeShortcutDescription" | i18n }} | ||
| {{ "editAutotypeKeyboardModifiersDescription" | i18n }} | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Otherwise, non-English users may see missing translations.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this happens as a follow up in some shape or form |
||
| </p> | ||
| <bit-form-field> | ||
| <bit-label>{{ "typeShortcut" | i18n }}</bit-label> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -77,25 +77,31 @@ export class AutotypeShortcutComponent { | |
| } | ||
| } | ||
|
|
||
| // <https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent> | ||
| private buildShortcutFromEvent(event: KeyboardEvent): string | null { | ||
| const hasCtrl = event.ctrlKey; | ||
| const hasAlt = event.altKey; | ||
| const hasShift = event.shiftKey; | ||
| const hasMeta = event.metaKey; // Windows key on Windows, Command on macOS | ||
| const hasSuper = event.metaKey; // Windows key on Windows, Command on macOS | ||
neuronull marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // Require at least one modifier (Control, Alt, Shift, or Super) | ||
| if (!hasCtrl && !hasAlt && !hasShift && !hasMeta) { | ||
| // Require at least one valid modifier (Control, Alt, Super) | ||
| if (!hasCtrl && !hasAlt && !hasSuper) { | ||
| return null; | ||
| } | ||
|
|
||
| const key = event.key; | ||
|
|
||
| // Ignore pure modifier keys themselves | ||
| if (key === "Control" || key === "Alt" || key === "Shift" || key === "Meta") { | ||
| // disallow pure modifier keys themselves | ||
| if (key === "Control" || key === "Alt" || key === "Meta") { | ||
| return null; | ||
| } | ||
|
|
||
| // Accept a single alphabetical letter as the base key | ||
| // disallow shift modifier | ||
| if (hasShift) { | ||
| return null; | ||
| } | ||
|
|
||
| // require a single alphabetical letter as the base key | ||
| const isAlphabetical = typeof key === "string" && /^[a-z]$/i.test(key); | ||
| if (!isAlphabetical) { | ||
| return null; | ||
|
|
@@ -108,10 +114,7 @@ export class AutotypeShortcutComponent { | |
| if (hasAlt) { | ||
| parts.push("Alt"); | ||
| } | ||
| if (hasShift) { | ||
| parts.push("Shift"); | ||
| } | ||
| if (hasMeta) { | ||
| if (hasSuper) { | ||
| parts.push("Super"); | ||
| } | ||
| parts.push(key.toUpperCase()); | ||
|
|
@@ -129,10 +132,9 @@ export class AutotypeShortcutComponent { | |
| } | ||
|
|
||
| // Must include exactly 1-2 modifiers and end with a single letter | ||
| // Valid examples: Ctrl+A, Shift+Z, Ctrl+Shift+X, Alt+Shift+Q | ||
| // Valid examples: Ctrl+A, Alt+B, Ctrl+Alt+X, Alt+Control+Q, Win+B, Ctrl+Win+A | ||
| // Allow modifiers in any order, but only 1-2 modifiers total | ||
| const pattern = | ||
| /^(?=.*\b(Control|Alt|Shift|Win)\b)(?:Control\+|Alt\+|Shift\+|Win\+){1,2}[A-Z]$/i; | ||
| const pattern = /^(?=.*\b(Control|Alt|Win)\b)(?:Control\+|Alt\+|Win\+){1,2}[A-Z]$/i; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested improvementConsider adding validation to reject duplicate modifiers: const pattern = /^(?:(?:Control|Alt|Win)\+){1,2}[A-Z]$/i;
if (!pattern.test(value)) {
return { invalidShortcut: { message: this.i18nService.t("invalidShortcut") } };
}
// Validate no duplicate modifiers
const parts = value.split('+');
const modifiers = parts.slice(0, -1);
const uniqueModifiers = new Set(modifiers.map(m => m.toLowerCase()));
return uniqueModifiers.size === modifiers.length
? null
: { invalidShortcut: { message: this.i18nService.t("invalidShortcut") } };
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I didn't introduce this, it was existing, but it seems legit. If so, I'd vote to open a separate bug
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I mean, it shouldn't be physically possible to do a double modifier key, because the first is already pressed down. but it doesn't hurt to guard against or at least comment on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. โ Critical: Regex allows duplicate modifiers While you noted this was pre-existing, the current regex pattern accepts invalid shortcuts like Why it happens: Recommended fix: const pattern = /^(?=.*\b(Control|Alt|Win)\b)(?:Control\+|Alt\+|Win\+){1,2}[A-Z]$/i;
if (!pattern.test(value)) {
return { invalidShortcut: { message: this.i18nService.t("invalidShortcut") } };
}
// Additional check: ensure no duplicate modifiers
const parts = value.split('+');
const modifiers = parts.slice(0, -1).map(m => m.toLowerCase());
const uniqueModifiers = new Set(modifiers);
if (uniqueModifiers.size !== modifiers.length) {
return { invalidShortcut: { message: this.i18nService.t("invalidShortcut") } };
}
return null;While physically triggering duplicate modifiers may be impossible, validating against it prevents edge cases and maintains data integrity. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. While you noted this is pre-existing and physically impossible to trigger, the regex accepts const pattern = /^(?=.*\b(Control|Alt|Win)\b)(?:Control\+|Alt\+|Win\+){1,2}[A-Z]$/i;
if (!pattern.test(value)) {
return { invalidShortcut: { message: this.i18nService.t("invalidShortcut") } };
}
// Validate no duplicate modifiers
const parts = value.split('+');
const modifiers = parts.slice(0, -1).map(m => m.toLowerCase());
const uniqueModifiers = new Set(modifiers);
if (uniqueModifiers.size !== modifiers.length) {
return { invalidShortcut: { message: this.i18nService.t("invalidShortcut") } };
}
return null; |
||
| return pattern.test(value) | ||
| ? null | ||
| : { invalidShortcut: { message: this.i18nService.t("invalidShortcut") } }; | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.