How to Implement Hold-to-Release Chords for Push-to-Talk with HoldShortcut in OpenLogi
OpenLogi's HoldShortcut action automatically maintains a keyboard shortcut for the exact duration of a physical button press, creating a hold-to-release chord that is ideal for push-to-talk workflows.
OpenLogi is an open-source input remapping framework that treats hold-to-release chords as first-class citizens through its specialized HoldShortcut action. Unlike simple keystroke injection, this mechanism binds the lifecycle of a key combo directly to a physical button, activating on press and releasing on lift—perfect for push-to-talk (PTT) scenarios where microphone control must synchronize precisely with input device interaction.
Understanding the HoldShortcut Data Model
At the heart of this functionality lies the Action enum defined in crates/openlogi-core/src/binding/action.rs. The HoldShortcut variant stores a KeyCombo that the runtime will maintain as a held chord.
pub enum Action {
// …
/// Hold a keyboard combo until the bound button is released.
HoldShortcut(KeyCombo),
// …
}
When the runtime resolves a binding to Action::HoldShortcut(combo), it delegates to the injection layer to create a HeldChord object. This object tracks the specific key combination and manages the transition between pressed and released states across platforms.
Runtime Lifecycle and Injection Architecture
The agent core handles the button-to-chord mapping in crates/openlogi-agent-core/src/runtime/hook.rs (around lines 490–505). The implementation follows a strict down/up/cancel lifecycle:
- Button Down: The agent receives a
Button::Downevent and callspress_hold(combo) - Chord Activation: The injector sends platform-specific key-down events via
hold_keys(..., KeyPhase::Down) - Sustained Hold: The HeldChord remains active, keeping the shortcut pressed at the OS level
- Button Up: The agent triggers
hold_keys(..., KeyPhase::Up)to release the keys
The public API for this process resides in crates/openlogi-inject/src/inject.rs:
pub fn press_hold(combo: &KeyCombo) -> HeldChord {
// Build a `HeldChord` that remembers the combo and the platform‑specific
// “hold_keys” implementation.
hold_transition(None, Some(&held.combo));
}
Platform-specific implementations in openlogi-inject/src/inject/<platform>.rs (covering macOS, Windows, and Linux) handle the actual OS-level key event generation. Each platform exposes a hold_keys function that maintains the key state until explicitly released.
Configuration Syntax for Push-to-Talk
OpenLogi uses a TOML-based configuration format. To create a hold-to-release chord for PTT, define a binding with action = "HoldShortcut" and specify the target shortcut in the combo field.
# Example: bind the thumb‑wheel button to “Ctrl+Space” for push‑to‑talk
[[bindings]]
# The physical input that triggers the action (device‑specific identifier)
input = "thumbwheel"
# The type of action we want to perform
action = "HoldShortcut"
# The shortcut that will be held while the button is pressed
combo = "Ctrl+Space"
- The
inputfield accepts device-specific identifiers (mouse buttons, keyboard keys, orHID codes) - The
combostring parses into aKeyCombovia the parser inopenlogi-core/src/binding/key_combo.rs - Alternatively, use
buttonfor mouse-specific bindings instead ofinput
Practical Implementation Examples
Basic Mouse Button PTT Configuration
Bind a side mouse button to your voice application's push-to-talk shortcut:
[[bindings]]
input = "mouse_button_4" # Side button on Logitech MX Master
action = "HoldShortcut"
combo = "Ctrl+Space" # Discord/TeamSpeak default PTT
Programmatic Usage in Rust
For custom workflows or automation, trigger holds directly using the injector API:
use openlogi_core::binding::KeyCombo;
use openlogi_inject::inject::press_hold;
// Parse a shortcut string
let combo: KeyCombo = "Ctrl+Space".parse().expect("valid shortcut");
// Start the hold (normally done automatically by the agent)
let held = press_hold(&combo);
// … microphone remains active while held exists …
// Automatic cleanup: when `held` drops, the destructor sends KeyPhase::Up
drop(held);
The press_hold function dispatches to the correct hold_keys implementation for the current platform, ensuring consistent behavior across operating systems.
Summary
- HoldShortcut creates a HeldChord that bridges physical button presses to sustained keyboard shortcuts
- The action lifecycle is managed in
crates/openlogi-agent-core/src/runtime/hook.rs, while platform-specific injection lives incrates/openlogi-inject/src/inject/<platform>.rs - Configuration requires only three fields:
input,action = "HoldShortcut", andcombo - The chord automatically releases when the button is lifted or if the hold is cancelled, preventing stuck keys
- Works identically across macOS, Windows, and Linux through the abstracted
hold_keysinterface
Frequently Asked Questions
What is the difference between HoldShortcut and regular keystroke injection?
Regular keystroke injection sends discrete key-down and key-up events immediately, while HoldShortcut maintains the key-down state for an arbitrary duration tied to a physical button. According to the OpenLogi source code, Action::HoldShortcut specifically creates a HeldChord object that tracks the combo lifecycle, whereas simple actions trigger one-shot events through separate injection paths.
Can HoldShortcut be triggered by keyboard keys instead of mouse buttons?
Yes. The input field in the TOML configuration accepts any valid device identifier, including keyboard scan codes. You can bind a keyboard key (such as CapsLock remapped to another HID code) to hold a different shortcut combo like "LeftCtrl" for temporary crouch or PTT functionality.
How does the system prevent stuck keys if the application crashes during a hold?
The HeldChord struct implements the Drop trait to ensure cleanup. When the HeldChord object goes out of scope—whether through normal button release or an unexpected drop due to process termination—it automatically invokes the platform-specific release logic with KeyPhase::Up. This RAII pattern guarantees that OS-level key states are reset even if the agent crashes.
Are there performance implications to using HoldShortcut for gaming?
No. The hold_keys implementation in crates/openlogi-inject/src/inject.rs uses efficient platform APIs that maintain key state without continuous event polling. Once the initial KeyPhase::Down is sent, the OS maintains the virtual key state until the corresponding KeyPhase::Up, consuming negligible CPU resources during the hold duration.
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 →