# How the OpenLogi Action Catalog Maps Buttons to Short, Long, and Hold Actions

> Discover how the OpenLogi Action Catalog maps buttons to short long and hold actions using the Action enum and Binding struct Learn to centralize device commands effectively.

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

---

**The OpenLogi Action Catalog centralizes every possible device command in the `Action` enum and exposes them through the `Binding` struct, which stores separate `Option<Action>` fields for short presses, long presses, and hold actions.**

The AprilNEA/OpenLogi repository implements a robust input handling system for Logitech devices. At its core, the **Action Catalog** provides a unified registry of all available commands, allowing users to map distinct behaviors to different button press durations. This architecture ensures that any new capability added to the `Action` enum automatically becomes available across the short, long, and hold binding slots.

## The Action Enum and Catalog Structure

The canonical definition of the Action Catalog resides in [`crates/openlogi-core/src/binding/action.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/binding/action.rs). This file defines the `Action` enum, where each variant represents a distinct user-visible command such as `CaptureRegion`, `SwitchApp`, or `ToggleSmartShift`.

Every action variant exposes three critical metadata methods that drive the UI and runtime logic:

- **`label()`** – Returns the human-readable name displayed in configuration interfaces.
- **`category()`** – Provides logical grouping (e.g., *Navigation*, *Media*, *System*) used to organize actions into UI sections.
- **`catalog()`** – Returns the complete ordered list of all actions, which the application iterates when presenting binding options.

### Action Metadata Implementation

According to the source code in [`action.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/action.rs), the `catalog()` method constructs a static ordered slice of every variant. This guarantees that the UI presents a consistent, exhaustive list of capabilities regardless of which binding slot (short, long, or hold) is being configured.

## The Binding Structure for Input Handling

Button mappings are stored in the `Binding` struct defined in [`crates/openlogi-core/src/binding/binding.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/binding/binding.rs). This struct explicitly separates input behaviors into three distinct fields:

```rust
pub struct Binding {
    pub short: Option<Action>,   // Triggered by a quick press and release
    pub long:  Option<Action>,   // Triggered by press-and-hold exceeding the timeout threshold
    pub hold:  Option<Action>,   // Continuously fired while the button remains depressed
}

```

Each field is an `Option<Action>`, allowing any combination of mapped or unmapped behaviors. The `short` field handles immediate clicks, `long` captures deliberate press-and-hold gestures, and `hold` enables repeating actions like scrolling or volume adjustment.

### Creating a Button Binding

The following example demonstrates how to instantiate a `Binding` with distinct actions for each press type:

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

let binding = Binding {
    short: Some(Action::CaptureRegion),
    long:  Some(Action::SwitchApp),
    hold:  Some(Action::ScrollUp),
};

```

## Runtime Dispatch Flow

During device operation, the agent receives raw HID++ events from the hardware. The event decoder classifies each physical button interaction into a `PressType` variant (`Short`, `Long`, or `Hold`). The runtime then queries the active `Binding` to resolve which `Action` to execute.

The dispatch logic matches the press type to the corresponding struct field:

```rust
fn resolve_press(binding: &Binding, press_type: PressType) -> Option<Action> {
    match press_type {
        PressType::Short => binding.short,
        PressType::Long  => binding.long,
        PressType::Hold  => binding.hold,
    }
}

```

Once resolved, the selected `Action` is dispatched to the appropriate subsystem—such as the **inject** crate for synthetic keystrokes or the **device** crate for DPI adjustments—based on the action's variant type.

## UI Integration with the Catalog

The desktop interface leverages `Action::catalog()` to populate three separate dropdown menus—one for each binding slot—ensuring that every available command can be assigned to any press type. The UI code in [`crates/openlogi-desktop/src/features/action_ring/editor.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/features/action_ring/editor.rs) iterates over the catalog to build these selectors.

Localization is handled by the i18n service in [`crates/openlogi-desktop/src/services/i18n.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/services/i18n.rs), which consumes the string returned by `Action::label()` and translates it according to the user's locale before display.

### Enumerating the Full Catalog

This pattern demonstrates how the UI retrieves the complete action list for rendering configuration options:

```rust
use openlogi_core::binding::Action;

// Retrieve the ordered list of all actions.
let all_actions = Action::catalog();

for action in &all_actions {
    println!("{} – {}", action.label(), action.category());
}

```

## Summary

- The `Action` enum in [`crates/openlogi-core/src/binding/action.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/binding/action.rs) serves as the central catalog, exposing `label()`, `category()`, and `catalog()` methods for UI consumption.
- The `Binding` struct in [`crates/openlogi-core/src/binding/binding.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/binding/binding.rs) maps physical button behaviors to three distinct fields: `short`, `long`, and `hold`.
- Runtime dispatch matches decoded HID++ events against the appropriate `Binding` field to determine which command to execute.
- The UI automatically reflects new capabilities because it derives its option lists directly from `Action::catalog()`, ensuring synchronization between the core library and the desktop interface.

## Frequently Asked Questions

### What determines the threshold between a short and long press?

The timing threshold is handled by the HID++ event decoder in the device communication layer, which classifies raw input into `PressType::Short` or `PressType::Long` before querying the `Binding` struct. The `Binding` itself stores only the resulting action mapping, not the timing logic.

### Can the same action be assigned to multiple press types on one button?

Yes. Since `short`, `long`, and `hold` are independent `Option<Action>` fields, you can assign `Some(Action::CaptureRegion)` to all three. However, note that the `hold` action executes repeatedly while the button remains pressed, whereas `short` and `long` fire once per gesture.

### Where are new actions defined in the codebase?

New actions are added as variants to the `Action` enum in [`crates/openlogi-core/src/binding/action.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/binding/action.rs). After implementing the required metadata methods (`label()` and `category()`), the new variant automatically appears in `Action::catalog()` and becomes selectable in the UI for all three binding types.

### How does the UI handle localization for action labels?

The desktop application references `Action::label()` to obtain the base identifier string, then passes this through the i18n service located in [`crates/openlogi-desktop/src/services/i18n.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/services/i18n.rs). This ensures that catalog entries display in the user's configured language without requiring changes to the core enum definition.