# Rust Switcher - Specification (implementation aligned)
This document describes the current behavior and architecture of the repository implementation.
It is intended as onboarding documentation and as a reference for expected runtime behavior.
## Scope
- Supported OS: Windows (tested on Windows 11)
- Primary UI: native Win32 window + tray icon
- Linux: out of scope for runtime support (some parts may compile, but the app is not a supported Linux product)
## Core user goals
- Convert text typed in the wrong keyboard layout (RU <-> EN) quickly and reliably.
- Work without relying on clipboard paste for the main path.
- Never send `Ctrl+C` to the foreground application (to avoid SIGINT / app-specific semantics).
- Provide global hotkeys and a lightweight UI for configuration.
## Terminology
- Convert: map characters between keyboard layouts (RU <-> EN) using the built in mapping table.
- Selection: currently selected text in the active application.
- Last word: the last token captured by the keyboard hook input journal.
- Autoconvert: automatic conversion triggered by typed delimiter characters (for example Space).
- Autoconvert enabled: runtime flag controlling whether Autoconvert is active.
- Important: this flag is NOT persisted in config.
- Default on app start: disabled.
## High level architecture
- UI boundary (Win32):
- Window procedure, message loop, controls
- Apply and Cancel logic
- Tray icon integration and tray menu actions
- Input boundary:
- Low level keyboard hook (WH_KEYBOARD_LL)
- Input journal and ring buffer for tokenization and last word extraction
- Hotkey sequences matching
- Domain logic:
- Text conversion, replacement of selection, insertion via SendInput
- Platform integration:
- Global hotkeys are implemented by a hybrid model:
- RegisterHotKey for legacy single-chord bindings
- WH_KEYBOARD_LL + sequence matcher for hotkey sequences
- Keyboard layout switching
- Autostart shortcut in Startup folder
- Notifications (tray balloon, MessageBox fallback)
- Persistence:
- Config stored under %APPDATA%\RustSwitcher\config.json via confy
## Configuration
Config fields (see src/config.rs):
- delay_ms: u32
- start_minimized: bool
- theme_dark: bool
Hotkeys (legacy single chord, optional):
- hotkey_convert_last_word
- hotkey_convert_selection
- hotkey_switch_layout
- hotkey_pause
Hotkey sequences (preferred, optional):
- hotkey_convert_last_word_sequence
- hotkey_pause_sequence
- hotkey_convert_selection_sequence
- hotkey_switch_layout_sequence
Notes:
- Autoconvert enabled is runtime only and is not stored in config.
- Hotkey fields are shown in read-only edits, but user interaction updates pending sequence values that are applied on Apply.
- Hotkey sequences are validated on save.
Default bindings (current defaults in code):
- Convert last word: double tap Left Shift within 1000 ms
- Convert last sequence: double tap Left Alt within 1000 ms
- Convert selection: double tap Left Shift within 1000 ms
- Autoconvert toggle: double tap Right Ctrl within 1000 ms
- Switch keyboard layout: CapsLock
## Actions and behavior
### Convert last word
Behavior:
- Converts the last token captured by the input journal.
- Sleeps for autoconvert_delay_ms before replacement.
- Replaces only the final run, preserving trailing suffix text.
### Convert selection
Algorithm (src/domain/text/convert.rs and selection probes):
- Probe selection text from the focused control using UI Automation and/or Win32 (Edit/RichEdit) APIs.
- No synthetic input is sent.
- Clipboard is not touched.
- Sleep for autoconvert_delay_ms before conversion and replacement.
- Convert the probed text via mapping.
- Replace selection by:
- Send Delete to remove the selection
- Inject Unicode text via SendInput
- Attempt to reselect the inserted text within a retry budget
This intentionally avoids paste via Ctrl+V to reduce interference with application specific paste behavior.
### Convert last sequence
Algorithm (src/domain/text/last_word.rs):
- Uses the input journal tokenization to determine the last sequence.
- Sleep for autoconvert_delay_ms before conversion and replacement.
- Applies an input based replacement strategy (backspace and Unicode injection via SendInput).
- Clipboard is not used as the primary mechanism.
### Switch keyboard layout
Switches keyboard layout (Windows) for the current thread using the platform API.
### Autoconvert
- The low level keyboard hook maintains a ring buffer of recent tokens.
- When a trigger delimiter is typed, the hook posts a window message (WM_APP_AUTOCONVERT).
- The UI thread handles WM_APP_AUTOCONVERT and calls autoconvert_last_word only when Autoconvert enabled is true.
- A guard prevents double conversion of the same token.
### Autoconvert toggle
- The toggle hotkey flips runtime Autoconvert enabled.
- Autoconvert enabled default is disabled on app start.
- Toggling shows an informational tray balloon.
- Tray double click triggers the same toggle.
## UI
Native Win32 UI with a single window and two group sections (custom painted frames):
### Settings group
- Autostart (checkbox)
- Delay ms (edit box)
- Theme dark (checkbox)
### Hotkeys group
- Read only displays for:
- Convert last word
- Convert last sequence
- Convert selection (sequence)
- Autoconvert pause
- Switch layout (sequence)
Buttons:
- Apply: persists config and applies theme changes immediately
- Cancel: reloads config from disk and applies it to UI and runtime (including theme)
- Exit: closes the application
Note: there is currently no dedicated GitHub/"Report issue" button in the shipped UI.
Theme behavior:
- Theme can be changed from UI checkbox + Apply.
- Theme can also be changed from tray menu.
- Tray theme switch persists theme_dark into config immediately (without overwriting other pending UI edits).
## Tray icon
- A tray icon is always added via Shell_NotifyIconW.
- Right click shows a context menu:
- Toggle autoconvert
- Show or Hide (toggles window visibility)
- Change theme
- Exit
- Left click is implemented:
- Single click toggles window visibility (debounced with a timer to distinguish it from double click).
- Double click is implemented:
- Toggles autoconvert.
## Autostart
- Implemented by creating a shortcut RustSwitcher.lnk in the user Startup folder.
- The shortcut points to the current executable path.
- Moving or deleting the executable breaks autostart.
Persistence note:
- Autostart is NOT stored in config.json.
- The UI checkbox reflects the current system autostart shortcut presence.
- Toggling the checkbox creates or deletes the shortcut immediately.
## Notifications and errors
- Info notifications use tray balloon where possible.
- Error notifications are queued and drained on the UI thread via a single entry point.
- Fallback for tray failure is MessageBoxW.
- Notifications must not block hotkey critical paths.
## Logging
Current behavior:
- Tracing initialization exists for development only.
- When enabled, logs go to stderr.
- By default, release builds do not install any tracing subscriber.
How it is gated:
- Tracing initialization is guarded by debug assertions.
- The intended way to enable logs during development is to build and run with feature debug-tracing and use RUST_LOG.
## Known issues (current behavior)
- Autostart depends on a shortcut pointing to the current exe path, so relocating the exe breaks autostart.