# How OpenLogi Implements Long-Press and Short-Press Action Bindings

> Learn how OpenLogi implements long-press and short-press action bindings with its deadline-based state machine. Discover distinct callbacks for button release timing.

- Repository: [Xuan Zhang/OpenLogi](https://github.com/AprilNEA/OpenLogi)
- Tags: internals
- Published: 2026-09-05

---

**OpenLogi distinguishes between short-press and long-press actions using a deadline-based state machine that executes distinct callbacks depending on whether a button is released before or after a 500ms threshold.**

The AprilNEA/OpenLogi repository provides a flexible input handling system for Logitech devices, featuring a sophisticated action binding system that supports differentiated short-press and long-press behaviors on the same physical button. This architecture allows users to bind two independent actions—such as Copy on click and Paste on hold—to maximize input efficiency without requiring additional hardware buttons.

## Core Data Structures in openlogi-core

### The Binding Enum

Located in [`crates/openlogi-core/src/binding.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/binding.rs), the **Binding** enum defines all possible button behaviors. The variant relevant to press duration is `LongPress(LongPressBinding)`, which encapsulates dual-action logic for handling time-sensitive inputs.

### LongPressBinding Structure

The actual storage for paired actions resides in [`crates/openlogi-core/src/binding/value.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/binding/value.rs). The **LongPressBinding** struct maintains two independent **Action** fields:

```rust
pub struct LongPressBinding {
    short: Action,
    long:  Action,
}

```

Construction occurs via `LongPressBinding::new(short, long)`, pairing the release action with the hold action. The system defines a **LONG_PRESS_THRESHOLD** constant of 500 milliseconds to differentiate between the two behaviors.

The **`click_action()`** helper method returns the short action for UI display purposes, allowing interfaces to show the default click behavior even when a distinct long-press action exists.

## Runtime State Machine in openlogi-agent-core

### State Tracking

The file [`crates/openlogi-agent-core/src/runtime/button.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent-core/src/runtime/button.rs) implements a state machine that tracks press duration across distinct states:

- **LongPressPending** — Button is currently pressed, storing the short action and a deadline timestamp calculated as `now + LONG_PRESS_THRESHOLD`.
- **LongPressFired** — The deadline elapsed; the long action executed and the short action is permanently suppressed for this press cycle.
- **LifecycleOnly** or **Immediate** — States for other binding types, such as single clicks or gestures, that do not use the deadline-based logic.

### Action Dispatch Flow

When the agent receives a button event, the runtime executes the following deterministic sequence:

1. **Press event** — State transitions to `LongPressPending` with a 500ms deadline.
2. **Release before deadline** — The pending state resolves to `binding.short()`, executing the short action immediately.
3. **Deadline expiration** — The state transitions to `LongPressFired`, dispatches `binding.long()`, and ignores subsequent release events for this specific press.

This guarantees mutual exclusivity: once the 500ms threshold passes, the long action fires and the short action cannot trigger for that press.

## User Configuration and TOML Parsing

### Configuration Syntax

Users define long-press bindings in the TOML configuration file using a table structure that the parser recognizes by the presence of `short` and `long` keys:

```toml
[bindings.<button_id>]
short = "Copy"
long  = "Paste"

```

### Parser Implementation

The parser, located in [`crates/openlogi-core/src/config.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/config.rs) around line 260, identifies the `{ short = "...", long = "..." }` table format. It instantiates `Binding::LongPress(LongPressBinding::new(...))` by mapping the string values to their corresponding **Action** variants, integrating the configuration into the runtime binding system.

## Interaction with Gesture Bindings

Long-press bindings are mutually exclusive with gesture bindings within the OpenLogi system. The **`upgrade_to_gesture`** method, defined in [`crates/openlogi-core/src/binding/value.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/binding/value.rs) at line 124, converts a button to gesture recognition by discarding any existing long-press "long" action.

**Single** bindings, which fire immediately on press, occupy a distinct variant in the **Binding** enum and do not interact with the deadline-based state machine, ensuring clean separation of concerns between immediate, timed, and directional input types.

## Code Examples

The following Rust code demonstrates programmatic creation of a long-press binding:

```rust
use openlogi_core::binding::{Binding, LongPressBinding, Action};

/// Create a long-press binding: click → Copy, hold → Paste
let lp = Binding::LongPress(LongPressBinding::new(Action::Copy, Action::Paste));

assert_eq!(lp.click_action(), Action::Copy); // Returns short action for UI display
// Runtime will dispatch Action::Paste if held ≥ 500ms

```

A corresponding TOML configuration example:

```toml
[[bindings]]
button = "ThumbwheelScrollUp"
short  = "VolumeUp"
long   = "Mute"

```

## Summary

- OpenLogi uses the **`LongPressBinding`** struct in [`crates/openlogi-core/src/binding/value.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/binding/value.rs) to store paired **short** and **long** **Action** values.
- The **LONG_PRESS_THRESHOLD** constant sets a 500ms deadline to differentiate press types.
- The runtime state machine in [`crates/openlogi-agent-core/src/runtime/button.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent-core/src/runtime/button.rs) transitions from `LongPressPending` to either short-action execution or `LongPressFired` (long-action execution).
- Long actions suppress short actions for the same press cycle, ensuring deterministic behavior.
- Configuration occurs via TOML tables parsed in [`crates/openlogi-core/src/config.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/config.rs).
- Gesture bindings via **`upgrade_to_gesture`** are mutually exclusive with long-press bindings.

## Frequently Asked Questions

### What is the long-press threshold in OpenLogi?

OpenLogi defines the long-press threshold as **500 milliseconds** via the `LONG_PRESS_THRESHOLD` constant in [`crates/openlogi-core/src/binding/value.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/binding/value.rs). If a button remains pressed for this duration or longer, the system executes the long action instead of the short action.

### How does OpenLogi prevent both actions from firing simultaneously?

The runtime state machine in [`crates/openlogi-agent-core/src/runtime/button.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent-core/src/runtime/button.rs) enforces mutual exclusivity through the `LongPressFired` state. Once the 500ms deadline passes and the long action dispatches, the state transitions to `LongPressFired`, causing the subsequent button release to ignore the short action entirely for that press cycle.

### Can a button have both a long-press binding and a gesture binding?

No, these binding types are mutually exclusive according to the source code. Converting a button to use gesture recognition via the **`upgrade_to_gesture`** method (found at line 124 in [`crates/openlogi-core/src/binding/value.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/binding/value.rs)) discards the existing long-press configuration, as the system cannot simultaneously interpret directional swipes and press-duration timing on the same input.

### How do I configure a long-press action in OpenLogi?

Add a TOML table under the `[bindings]` section with `short` and `long` keys specifying the desired **Action** names. The parser in [`crates/openlogi-core/src/config.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/config.rs) automatically instantiates a `Binding::LongPress` when it encounters this structure, creating a `LongPressBinding` via `LongPressBinding::new(short, long)`.