# How OpenLogi Independent Short/Long Press Actions Work With the 500ms Threshold

> Discover how OpenLogi's 500ms threshold enables independent short and long press actions for buttons. Learn to implement flexible input handling in your projects.

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

---

**The OpenLogi input system uses a 500ms `LONG_PRESS_THRESHOLD` to determine whether a button press fires a short action (release before deadline) or a long action (deadline reached while held), with both actions remaining mutually independent.**

In the OpenLogi universal mouse and keyboard customization system, buttons can be configured with dual-action behavior. When a user presses and holds a button, the runtime must decide between two possible outcomes. This article explains how the 500ms threshold governs that decision without coupling the two actions.

## Binding Definition: LongPressBinding Structure

Independent short and long actions are stored together in the `LongPressBinding` struct defined in [`crates/openlogi-core/src/binding/value.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/binding/value.rs):

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

```

This structure appears at **lines 12-25** of [[`value.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/value.rs)](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-core/src/binding/value.rs#L12-L25). The `short` and `long` fields are completely independent—neither depends on the other, and either can be triggered in isolation based on timing.

The threshold itself is defined as a global constant:

```rust
pub const LONG_PRESS_THRESHOLD: Duration = Duration::from_millis(500);

```

## Press Lifecycle: Creating the Pending State

When a button press occurs, the runtime in [`crates/openlogi-agent-core/src/runtime/button.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent-core/src/runtime/button.rs) creates a `PressBehavior::LongPressPending` variant. At **lines 90-100** of [[`button.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/button.rs)](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-agent-core/src/runtime/button.rs#L90-L100), the `PressBehavior::new` method calculates the deadline:

```rust
PressBehavior::LongPressPending {
    short: binding.short().clone(),
    long: binding.long().clone(),
    deadline: pressed_at + LONG_PRESS_THRESHOLD,
}

```

The **deadline** equals press time plus 500ms. Until this moment, both actions remain possible. After it passes, only the long action remains valid.

## Threshold Evaluation: Two Trigger Paths

OpenLogi evaluates the 500ms threshold through two distinct mechanisms:

### Deadline-Driven Long Action Firing

The button runtime worker periodically checks for expired deadlines. At **lines 31-33** of [[`button.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/button.rs)](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-agent-core/src/runtime/button.rs#L31-L33), `emit_due_long_presses` handles this:

```rust
let due_presses = state.due_long_presses(now);
emit_selected_long_presses(state, &due_presses, now, emit);

```

When `now >= deadline`, the **long action fires immediately** and state transitions to `LongPressFired`. The short action becomes permanently suppressed for this press.

### Release-Driven Short Action Firing

If the button releases **before** the deadline, the short action fires instead. The runtime compares `released_at` against `pressed_at + LONG_PRESS_THRESHOLD` to make this determination.

## Unit Test Verification: Concrete Threshold Behavior

The test suite in [`crates/openlogi-agent-core/src/runtime/button/tests.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent-core/src/runtime/button/tests.rs) validates the exact threshold semantics.

### Short-Only Case: Release at 499ms

At **lines 94-115**, [`release_before_long_press_threshold_fires_only_the_short_action`](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-agent-core/src/runtime/button/tests.rs#L94-L115) demonstrates:

```rust
released_at: pressed_at + LONG_PRESS_THRESHOLD.saturating_sub(Duration::from_millis(1)),

```

With release at **499ms**, only the short action emits. The test asserts no long action fires.

### Long-Only Case: Threshold at 500ms

At **lines 33-78**, [`threshold_fires_long_once_and_suppresses_short_on_release`](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-agent-core/src/runtime/button/tests.rs#L33-L78) shows:

```rust
emit_due_long_presses(..., pressed_at + LONG_PRESS_THRESHOLD);
// later release after the threshold

```

Advancing the worker to exactly **500ms** triggers the long action. Subsequent release ends the press without emitting the short action.

## Cancellation Safety: Preventing Ghost Actions

Press cancellation—such as session invalidation during hold—must not leave orphaned actions. At **lines 81-100**, [`cancellation_never_fires_a_pending_short_or_long_action`](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-agent-core/src/runtime/button/tests.rs#L81-L100) verifies that cleanup routines clear both pending actions regardless of how close to the deadline.

## Configuration Example: TOML Profile Definition

Users define independent short/long press actions in device configuration files:

```toml
[profile.my_mouse]
button_back = { short = "Copy", long = "Paste" }

```

The TOML parser maps the `{ short = "...", long = "..." }` table to `Binding::LongPress` wrapping a `LongPressBinding`. See the `Binding` enum at **lines 71-80** of [[`value.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/value.rs)](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-core/src/binding/value.rs#L71-L80).

## Runtime Behavior Summary

| Timing Scenario | Emitted Action | State After Event |
|-----------------|----------------|-------------------|
| Release before 500ms | **short** only | Press ended |
| 500ms reached while held | **long** only | `LongPressFired` |
| Release after long fired | none (already emitted) | Press ended |
| Cancel at any point | **none** | Pending cleared |

## Key Source Files

- **[`crates/openlogi-core/src/binding/value.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/binding/value.rs)** — `LONG_PRESS_THRESHOLD` constant, `LongPressBinding` struct, `Binding` enum
- **[`crates/openlogi-agent-core/src/runtime/button.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent-core/src/runtime/button.rs)** — `PressBehavior` creation, deadline evaluation, `emit_due_long_presses`
- **[`crates/openlogi-agent-core/src/runtime/button/tests.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent-core/src/runtime/button/tests.rs)** — Verification of threshold boundary conditions
- **[`crates/openlogi-core/src/binding.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/binding.rs)** — Re-export of `LONG_PRESS_THRESHOLD` for cross-crate usage

## Summary

- **Independent actions**: `LongPressBinding` stores short and long actions as separate fields without interdependence
- **500ms threshold**: `LONG_PRESS_THRESHOLD` creates a precise boundary between short and long press detection
- **Deadline-based triggering**: Long actions fire automatically when the threshold elapses, not on release
- **Short action suppression**: Once the deadline passes, the short action becomes unavailable for that press cycle
- **Cancellation safety**: Pending actions clear on any cancellation, preventing unintended emission

## Frequently Asked Questions

### Can the 500ms threshold be customized per device or button?

No. `LONG_PRESS_THRESHOLD` is a **compile-time constant** defined in [`crates/openlogi-core/src/binding/value.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/binding/value.rs). The value is hardcoded to 500ms across all bindings in the OpenLogi system. Per-button or per-device threshold customization would require modifying the source and rebuilding.

### What happens if both short and long actions are configured identically?

The actions fire independently based on timing. A quick tap emits one instance; a 500ms hold emits another. The runtime does not deduplicate identical actions. This can produce double-trigger behavior if the user accidentally holds slightly past the threshold.

### Does the long action fire repeatedly if the button stays held?

No. The `LongPressPending` state transitions to `LongPressFired` after the first deadline evaluation. Subsequent worker ticks find no pending presses for that button. The long action fires **exactly once** per press cycle, regardless of hold duration beyond 500ms.

### How does OpenLogi handle rapid successive presses near the threshold?

Each press creates an independent `PressBehavior` with its own deadline. The runtime tracks presses by button identity, so overlapping or closely-timed presses on the same button replace the previous pending state. This prevents action stacking but means a very quick release-and-repress sequence resets the 500ms window entirely.