How Global Keyboard Bindings Work in OpenLogi: Modifier Keys, TOML Syntax, and Runtime Lookup
OpenLogi defines global keyboard bindings in a top-level [keyboard.bindings] TOML table using trigger strings like "shift+command+f5", parses them into KeyTrigger structs with modifier bit masks, and stores them in a thread-safe BTreeMap for real-time lookup by the agent's input hook.
OpenLogi is a Rust-based automation tool that enables system-wide keyboard shortcuts for controlling external devices. Understanding how it handles global keyboard bindings including modifier keys is essential for customizing your workflow. The implementation spans configuration parsing, shared state management, and low-level OS hook integration.
Configuring Global Keyboard Bindings in TOML
OpenLogi uses a plain TOML configuration file that both the GUI and background agent read. Global shortcuts live under the [keyboard.bindings] table at the root of your config file.
Basic Syntax and Modifier Support
Each entry maps a trigger string to an action. The trigger combines a key name with optional modifiers joined by +:
[keyboard.bindings]
f1 = "MissionControl"
"shift+command+f5" = "ShowDesktop"
From docs/config.example.toml (lines 113-115), you can see both a simple key binding and a compound modifier trigger in practice.
Supported Modifiers and Aliases
According to docs/CONFIGURATION.md (lines 84-86), OpenLogi accepts these modifiers:
- Primary names:
shift,control,option,command - Common aliases:
ctrl(for control),alt(for option),cmd(for command)
The parser normalizes aliases to canonical names internally, so "ctrl+alt+delete" and "control+option+delete" are equivalent.
Parsing Trigger Strings into KeyTrigger Structs
The heart of OpenLogi's keyboard binding system is the KeyTrigger struct, implemented in crates/openlogi-core/src/config/key_trigger.rs.
KeyTrigger Structure
When you define "shift+command+f5" in TOML, OpenLogi parses it into:
// Conceptual representation from key_trigger.rs
pub struct KeyTrigger {
pub key: KeyCode, // The physical key (e.g., F5)
pub modifiers: ModifierMask, // Bit mask: SHIFT | COMMAND
}
The ModifierMask bit mask enables fast comparisons using simple bitwise operations rather than string matching.
Parsing Example
use openlogi_core::config::KeyTrigger;
// Parse from config string
let trigger: KeyTrigger = "ctrl+alt+delete".parse().unwrap();
// The parser:
// 1. Splits on '+' → ["ctrl", "alt", "delete"]
// 2. Normalizes aliases → ["control", "option", "delete"]
// 3. Builds modifier bit mask
// 4. Validates key name against known key codes
Storing and Sharing Bindings at Runtime
Parsed bindings are stored in Config.keyboard.bindings as a BTreeMap<KeyTrigger, Action>. The agent promotes this to shared state for cross-thread access.
Shared State Architecture
// From the agent runtime (conceptual, based on hook.rs)
pub type SharedKeyboardBindings = Arc<RwLock<BTreeMap<KeyTrigger, Action>>>;
// On config reload:
let shared = Arc::new(RwLock::new(config.keyboard.bindings.clone()));
This Arc<RwLock<...>> pattern allows:
- Read-heavy access from the hot path (every key event)
- Safe updates when the config file changes
- Minimal contention through read-preferring locks
Runtime Lookup Flow
The global hook in crates/openlogi-agent-core/src/runtime/hook.rs handles every system key event:
fn handle_key(
event: KeyEvent,
bindings: &SharedKeyboardBindings,
dispatcher: &ActionDispatcher
) {
// Convert OS event to our KeyTrigger format
let trigger = KeyTrigger::from(event);
// Fast BTreeMap lookup
if let Some(action) = bindings.read().unwrap().get(&trigger) {
dispatcher.dispatch(action.clone());
}
}
Hot-Reloading and Configuration Changes
OpenLogi's agent watches the TOML file for changes, enabling live updates without restart:
- File watcher detects change → triggers config reload
- Parse new TOML → build fresh
BTreeMap<KeyTrigger, Action> - Acquire write lock → swap shared bindings atomically
- New key events → immediately use updated mappings
This design ensures zero-downtime configuration changes for global shortcuts.
Limitations of Global Bindings
Global keyboard bindings have one key restriction: they are strictly single-action mappings. Unlike device-specific button bindings, they cannot specify short-press/long-press behavior pairs. This limitation is enforced by the Action type's definition—global bindings resolve directly to one action with no duration logic.
Complete Configuration Example
# Global shortcuts active regardless of selected device
[keyboard.bindings]
f2 = "PlayPause"
"shift+command+f5" = "ShowDesktop"
"ctrl+alt+delete" = "EmergencyLock"
"cmd+shift+1" = "SwitchProfileWork"
"cmd+shift+2" = "SwitchProfileGaming"
Summary
- Global keyboard bindings are defined in
[keyboard.bindings]using trigger strings with+-separated modifiers and keys - OpenLogi supports shift, control/ctrl, option/alt, and command/cmd modifiers with alias normalization
- The
KeyTriggerstruct inkey_trigger.rsuses bit-masked modifiers for efficient storage and comparison - Parsed bindings live in a
BTreeMap<KeyTrigger, Action>wrapped inArc<RwLock<_>>for thread-safe access - The agent's input hook performs fast lookups on every key event via
handle_keyinhook.rs - Configuration changes are hot-reloaded without restarting the agent
Frequently Asked Questions
What modifier key aliases does OpenLogi accept?
OpenLogi accepts both canonical names and common aliases. Use control or ctrl, option or alt, command or cmd. The parser internally normalizes all aliases to the canonical form before building the modifier bit mask in KeyTrigger.
Can I use the same key binding for multiple actions?
No. The BTreeMap<KeyTrigger, Action> structure enforces unique keys. If you define the same trigger twice in TOML, the second definition overwrites the first. Each global shortcut maps to exactly one action.
How do global keyboard bindings differ from device button bindings?
Global bindings are system-wide shortcuts defined in [keyboard.bindings] that work regardless of which device is selected. Device button bindings are per-device and support advanced features like short-press/long-press detection. Global bindings are strictly single-action.
Where can I find a complete list of valid key names?
The valid key names are defined in the key parsing implementation within crates/openlogi-core/src/config/key_trigger.rs. Common keys include function keys (f1-f24), alphanumeric keys, space, tab, enter, escape, and arrow keys. Refer to docs/CONFIGURATION.md for documented examples and docs/config.example.toml for concrete usage patterns.
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 →