How FluentRead Manages Hotkey Conflicts and Custom Hotkey Assignments
FluentRead prevents hotkey conflicts by validating user-defined shortcuts against a hard-coded list of common OS shortcuts before saving, while supporting custom hotkey assignments through a parsing and normalization layer that ensures reliable runtime detection.
FluentRead is an open-source browser extension that provides instant translation capabilities through customizable keyboard shortcuts. Understanding how FluentRead manages hotkey conflicts and custom hotkey assignments reveals a defensive design that prioritizes system stability while offering users flexibility in defining their own translation triggers.
Architecture and Workflow
FluentRead's hotkey system operates through four coordinated components that handle configuration, validation, and runtime detection.
Configuration Model
The configuration schema is defined in entrypoints/utils/model.ts, where the Config interface declares fields for hotkey, customHotkey, and floatingBallHotkey. Default values are established here, ensuring that every installation starts with safe, non-conflicting presets.
Hotkey Processing Engine
All parsing, validation, and display logic resides in entrypoints/utils/hotkey.ts. This module exports parseHotkey, validateHotkeyConflicts, and generateDisplayName, forming a complete pipeline from raw user input to validated configuration.
Runtime Detection
The content script in entrypoints/content.ts implements the actual keyboard listener. It retrieves the configured hotkey parts via getConfiguredMouseHotkeyParts() and maintains a Set<string> of currently pressed keys, triggering translation only when the exact combination is pressed and subsequently released.
Parsing and Validation Logic
FluentRead implements strict validation to prevent both malformed inputs and dangerous conflicts with system operations.
The parseHotkey Function
The parseHotkey function in entrypoints/utils/hotkey.ts normalizes user input into a structured format. It splits strings like "Ctrl+Alt+T" by the "+" delimiter, lowercases each component, and validates that all elements except the last are modifiers defined in MODIFIER_KEYS (ctrl, alt, shift, meta). The final element must exist in REGULAR_KEYS.
The function enforces two critical safety rules:
- Single-letter keys must include at least one modifier to prevent intercepting normal typing.
- The
meta(Cmd) modifier is explicitly blocked on macOS to avoid capturing system-level shortcuts.
Conflict Detection with validateHotkeyConflicts
Before saving, validateHotkeyConflicts compares the parsed hotkey against a hard-coded commonConflicts array containing approximately 30 common OS shortcuts (copy, paste, new tab, close window, etc.). If the modifiers and key match any entry exactly, the function returns {hasConflict: true, conflictDescription: '与系统快捷键冲突: 复制'}, preventing the user from saving a destructive configuration.
Display Name Generation
The generateDisplayName function creates platform-specific strings (e.g., "Control+Option+T" on macOS versus "Ctrl+Alt+T" on Windows) for UI presentation, ensuring users recognize their configured shortcuts regardless of underlying normalization.
Runtime Hotkey Matching
The content script translates physical key events into the abstract representation used during validation.
Event Normalization
In entrypoints/content.ts, keyboard events are normalized to match the identifiers used by parseHotkey. The script checks e.ctrlKey, e.altKey, and e.shiftKey, pushing corresponding strings ('control', 'alt', 'shift') into a Set<string> named mouseHotkeysPressed. The regular key is lowercased and added if it is a single character.
Configuration Retrieval
The function getConfiguredMouseHotkeyParts() reads config.hotkey and config.customHotkey from the singleton configuration object managed by entrypoints/utils/config.ts. If config.hotkey equals 'custom', it parses config.customHotkey; otherwise, it uses the preset value.
The checkMouseHotkey() function compares the current mouseHotkeysPressed set against the required parts. It verifies that both sets have identical length and that every required key is present. When matched, screen.hotkeyPressed is set to true. Upon keyup, if the set becomes empty and screen.hotkeyPressed was true, handleTranslation() executes the translation workflow.
Implementation Examples
The following examples demonstrate how FluentRead's hotkey system handles parsing, validation, and runtime detection.
Parsing and validating a user-provided hotkey:
import { parseHotkey, validateHotkeyConflicts } from '@/entrypoints/utils/hotkey';
// Example user input
const userInput = 'Ctrl+Alt+T';
// Step 1 – parse
const parsed = parseHotkey(userInput);
if (!parsed.isValid) {
console.error('Invalid format:', parsed.errorMessage);
}
// Step 2 – conflict check
const conflict = validateHotkeyConflicts(parsed);
if (conflict.hasConflict) {
console.warn('Conflict detected:', conflict.conflictDescription);
} else {
console.log('Hotkey is safe to use:', parsed.displayName);
}
Saving a custom hotkey in the options page:
async function saveHotkey(newHotkey: string) {
const parsed = parseHotkey(newHotkey);
const conflict = validateHotkeyConflicts(parsed);
if (!parsed.isValid) throw new Error(parsed.errorMessage);
if (conflict.hasConflict) throw new Error(conflict.conflictDescription);
// store as custom hotkey
await storage.setItem('local:config', JSON.stringify({
...config,
hotkey: 'custom',
customHotkey: newHotkey,
}));
}
Runtime detection in the content script:
// In entrypoints/content.ts
window.addEventListener('keydown', e => {
// normalise pressed keys (same identifiers as parseHotkey)
const parts = [];
if (e.ctrlKey) parts.push('control');
if (e.altKey) parts.push('alt');
if (e.shiftKey) parts.push('shift');
const key = e.key.toLowerCase();
if (key.length === 1) parts.push(key); // regular key
// compare with configured hotkey
const required = getConfiguredMouseHotkeyParts(); // from config
const matches = required.length === parts.length &&
required.every(k => parts.includes(k));
if (matches) {
screen.hotkeyPressed = true;
}
});
Summary
FluentRead implements a multi-layered defense system for managing hotkey conflicts and custom hotkey assignments:
- Structured Configuration: The
Configmodel inentrypoints/utils/model.tsmaintains separate fields for preset and custom hotkeys, ensuring type safety and default fallbacks. - Rigorous Parsing: The
parseHotkeyfunction inentrypoints/utils/hotkey.tsnormalizes input strings, enforces modifier requirements for single-letter keys, and explicitly blocks themeta(Cmd) modifier on macOS. - Conflict Prevention:
validateHotkeyConflictscompares proposed hotkeys against a hard-coded list of approximately 30 common OS shortcuts, preventing users from saving configurations that would interfere with system operations. - Runtime Reliability: The content script in
entrypoints/content.tsuses exact set matching against normalized key events, ensuring translations trigger only when the precise configured combination is pressed and released.
Frequently Asked Questions
How does FluentRead prevent conflicts with system shortcuts?
FluentRead prevents conflicts through the validateHotkeyConflicts function in entrypoints/utils/hotkey.ts, which checks every custom hotkey against a hard-coded commonConflicts array containing approximately 30 common OS shortcuts like copy, paste, and new tab operations. If the parsed modifiers and key match any system shortcut exactly, the function returns a conflict description and prevents the configuration from being saved.
Can users define completely custom hotkey combinations?
Yes, users can define custom hotkey combinations through the customHotkey field in the configuration model defined in entrypoints/utils/model.ts. When config.hotkey is set to 'custom', the system uses the user-defined string stored in config.customHotkey. The parseHotkey function in entrypoints/utils/hotkey.ts validates these custom strings, ensuring they contain supported modifiers and regular keys while blocking potentially problematic combinations like single-letter keys without modifiers.
Why does FluentRead block the Meta (Cmd) key on macOS?
FluentRead explicitly blocks the meta modifier (Cmd key on macOS) in the parseHotkey function within entrypoints/utils/hotkey.ts to avoid capturing system-level shortcuts that are fundamental to macOS operation. This deliberate restriction prevents the extension from interfering with essential macOS commands like Cmd+C (copy), Cmd+V (paste), and Cmd+Tab (application switching), ensuring the browser extension remains unobtrusive and does not degrade the core user experience.
How does the extension detect hotkeys while browsing?
The extension detects hotkeys through the content script in entrypoints/content.ts, which listens for keydown and keyup events. It normalizes each event into a Set<string> of pressed keys using the same identifiers as the parseHotkey function (e.g., 'control', 'alt', 't'). The checkMouseHotkey() function compares this set against the required parts retrieved from getConfiguredMouseHotkeyParts(). When the sets match exactly, screen.hotkeyPressed is set to true, and upon key release, the translation workflow executes via handleTranslation().
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →