Short-Press, Long-Press, and Hold-Until-Release Actions in OpenLogi Bindings: A Complete Guide
OpenLogi distinguishes between short-press (under 500ms), long-press (over 500ms), and hold-until-release actions through time-based event measurement in the agent core, allowing a single physical button to trigger three distinct behaviors.
OpenLogi, an open-source Logi Options+ alternative for Linux and macOS, provides sophisticated button remapping through its TOML configuration system. The project implements three distinct interaction modes—short-press, long-press, and hold-until-release—that enable complex macros and multi-function bindings on Logitech peripherals. Understanding these distinctions is essential for crafting efficient input workflows in docs/config.example.toml and the underlying Rust agent core.
The Three Action Types
OpenLogi bindings support three temporal interaction modes defined in crates/openlogi-agent-core/src/bindings.rs. Each mode corresponds to a specific field in the Binding struct: short, long, and hold.
Short-Press Actions
A short-press triggers when a button is pressed and released within the default 500ms threshold. According to the configuration schema in docs/config.example.toml, this maps to quick toggle operations.
When the agent core processes a ButtonEvent::Up in crates/openlogi-agent-core/src/button.rs, it calculates the elapsed duration since the matching ButtonEvent::Down. If the duration falls below the threshold and a short action is defined, the system dispatches that action exactly once upon release.
# Example: Quick desktop access
DpiToggle = { short = "ShowDesktop", long = "MissionControl" }
Long-Press Actions
A long-press activates when a button remains depressed for 500ms or longer. Unlike the short-press, this action fires immediately upon crossing the temporal threshold, regardless of when the user eventually releases the button.
The runtime logic in crates/openlogi-agent-core/src/button.rs compares the press duration against THRESHOLD (default 500ms). When duration >= THRESHOLD and a long binding exists, the agent emits the configured action once. This mode suits secondary functions that require intentional activation.
# Example: Mission Control requires a deliberate hold
DpiToggle = { short = "ShowDesktop", long = "MissionControl" }
Hold-Until-Release Actions
Hold-until-release maintains an active state for the entire duration of the physical press. The action initiates immediately on ButtonEvent::Down and terminates precisely on ButtonEvent::Up, creating a momentary or modifier-style behavior.
This mode requires the hold field in the binding configuration. The agent core dispatches the action on button-down and automatically cancels or stops the action on button-up, making it ideal for scrolling, temporary DPI adjustments, or drag-and-drop operations.
# Example: Continuous scrolling while held
WheelUp = { hold = "ScrollUp" }
Runtime Implementation
The distinction between these three modes relies on precise event timing within the agent core.
Event Processing Logic
The crates/openlogi-agent-core/src/button.rs module implements the decision tree that classifies raw HID++ events into high-level actions. The system tracks press timestamps using state.start_press(id, Instant::now()) on down events and evaluates state.elapsed(id) on up events.
// Simplified logic from crates/openlogi-agent-core/src/button.rs
match event {
ButtonEvent::Down(id) => {
state.start_press(id, Instant::now());
if let Some(action) = binding.hold {
send_action(action); // hold-until-release begins immediately
}
}
ButtonEvent::Up(id) => {
let duration = state.elapsed(id);
if duration < THRESHOLD && binding.short.is_some() {
send_action(binding.short.unwrap());
} else if duration >= THRESHOLD && binding.long.is_some() {
send_action(binding.long.unwrap());
}
// hold-until-release actions terminate automatically here
}
}
Configuration Structure
The data structures in crates/openlogi-agent-core/src/bindings.rs define the Binding struct with optional fields for each action type:
short:Option<Action>for sub-threshold releaseslong:Option<Action>for supra-threshold holdshold:Option<Action>for continuous activation
Each button can simultaneously define all three fields, allowing the same physical input to serve multiple contextual purposes based on user timing.
Configuration Examples
To implement these behaviors, declare bindings in your OpenLogi configuration file using inline table syntax:
# Multi-mode button with distinct short and long behaviors
DpiToggle = { short = "ShowDesktop", long = "MissionControl" }
# Dedicated hold-based scrolling
WheelUp = { hold = "ScrollUp" }
WheelDown = { hold = "ScrollDown" }
# Combined binding using all three modes
ForwardButton = { short = "Copy", long = "Paste", hold = "MiddleClick" }
The default 500ms threshold separating short from long presses is compiled into the agent core, though the architecture in crates/openlogi-agent-core/src/button.rs supports future configurability of this value.
Summary
- Short-press actions fire on button release when the press duration remains under 500ms, mapping to quick toggles defined in the
shortconfiguration field. - Long-press actions trigger once after holding a button for 500ms or longer, enabling secondary functions through the
longfield. - Hold-until-release actions activate continuously from button-down to button-up events, controlled by the
holdfield incrates/openlogi-agent-core/src/bindings.rs. - The agent core in
crates/openlogi-agent-core/src/button.rsmeasures durations usingInstanttimestamps and dispatches actions based on threshold comparisons against theTHRESHOLDconstant.
Frequently Asked Questions
How does OpenLogi prevent short-press and long-press actions from firing simultaneously?
OpenLogi implements an exclusive dispatch model in crates/openlogi-agent-core/src/button.rs. When processing a ButtonEvent::Up, the system calculates the total press duration and evaluates it against the 500ms threshold. If the duration qualifies as a long-press, only the long action emits; if it qualifies as a short-press, only the short action fires. The logic prevents both actions from triggering on a single physical press by using mutually exclusive conditional branches.
Can I use hold-until-release alongside short-press on the same button?
Yes. The Binding struct supports simultaneous definition of short, long, and hold fields. When a button with all three fields is pressed, the hold action fires immediately on ButtonEvent::Down. If the user releases the button before 500ms elapses, the short action also dispatches on release. If the user holds beyond 500ms, the long action fires at the threshold crossing, while the hold action continues until physical release.
Where is the 500ms threshold defined in the OpenLogi source code?
The default 500ms threshold is defined as a constant in crates/openlogi-agent-core/src/button.rs, referenced as THRESHOLD in the duration comparison logic. This value separates short-press from long-press classifications during the ButtonEvent::Up handling routine. Currently, this threshold requires recompilation to modify, as it is hardcoded in the agent core rather than exposed in the TOML configuration.
What happens if I only define a long-press without a short-press for a button?
If a binding specifies only the long field without a short field, pressing and releasing the button quickly (under 500ms) results in no action. The system evaluates the duration on release, determines it falls below the threshold, checks for a short action, finds None, and consequently dispatches nothing. To avoid dead inputs, OpenLogi recommends defining both temporal modes or using hold for always-active behaviors.
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 →