How to Implement Long-Press and Short-Press Action Pairs in OpenLogi

OpenLogi treats short-press and long-press as independent actions configured via TOML or the Rust API, using HID++ control IDs 0xFB and 0xCF to persist distinct behaviors to Logitech input devices.

The OpenLogi project provides an open-source framework for remapping Logitech mice and keyboards at the firmware level. Understanding how to implement long-press and short-press action pairs in OpenLogi button bindings allows you to double the functionality of every physical button through the HID++ reprogrammable-control protocol.

Core Architecture Overview

OpenLogi separates button binding logic into three distinct layers: the core API defining actions and buttons, the configuration layer parsing user-defined TOML, and the device layer translating these mappings into HID++ payloads.

Action and Button Definitions

All possible behaviors are enumerated in the Action type, while physical buttons are identified via ButtonId. These primitives reside in:

Configuration Parsing

The Config::bindings structure stores a map from ButtonId to an action pair. According to the source in crates/openlogi-core/src/config.rs, the parser extracts two optional keys per button: short and long. During runtime, Config::effective_bindings builds a BTreeMap<ButtonId, (Action, Option<Action>)> where the tuple contains the required short-press action and an optional long-press action.

HID++ Device Communication

The device layer uses feature ID-specific control identifiers to program the hardware:

  • 0xFB — Short-press control ID
  • 0xCF — Long-press control ID

These constants are defined in crates/openlogi-hidpp/src/feature/reprog_controls/control_ids.rs. The ReprogControls::write method in crates/openlogi-hidpp/src/feature/reprog_controls.rs transmits payloads via write_long_register to commit the configuration to the device firmware.

Configuring Button Bindings via TOML

You define action pairs directly in your config.toml (or per-application profile). Each button entry accepts a short key for tap actions and an optional long key for hold actions.


# Global bindings example

[bindings.back]
short = "Undo"          # Single tap triggers Undo

long  = "Copy"          # Hold triggers Copy

[bindings.forward]
short = "Redo"

# Omitting `long` causes long-press to fall back to the short-press action

The TOML parser validates these strings against the Action enum variants. When the configuration loads, OpenLogi builds the internal (Action, Option<Action>) tuple for each ButtonId.

Implementing Bindings Programmatically

For developers extending OpenLogi or building custom configuration tools, the Config struct provides explicit setters for both press types.

use openlogi_core::binding::{ButtonId, Action, Config};

fn configure_back_button(config: &mut Config) {
    // Assign short-press action
    config.set_binding(ButtonId::Back, Action::Undo);
    
    // Assign long-press action (optional)
    config.set_long_binding(ButtonId::Back, Action::Copy);
}

The effective_bindings method merges global and per-app profiles into the final BTreeMap<ButtonId, (Action, Option<Action>)> used by the agent at runtime.

HID++ Protocol Implementation Details

Under the hood, OpenLogi communicates with Logitech devices using the HID++ reprogrammable-controls feature. The implementation writes distinct control IDs for each press duration.

// From crates/openlogi-hidpp/src/feature/reprog_controls.rs
let short_payload = ControlId::LaserButtonShortPress.to_payload(&action_short);
let long_payload  = ControlId::LaserButtonLongPress.to_payload(&action_long);

endpoint.call_long(0, short_payload).await?;
endpoint.call_long(0, long_payload).await?;

These calls utilize write_long_register (and its counterpart read_long_register) to modify the device's programmable-control table. The agent distinguishes press length via HID++ press-duration events and looks up the correct action from the effective_bindings map.

Summary

  • OpenLogi Architecture separates action definitions (action.rs), configuration parsing (config.rs), and HID++ device communication (reprog_controls.rs).
  • TOML Configuration uses short and long keys within button sections to define dual-action bindings.
  • HID++ Control IDs 0xFB (short) and 0xCF (long) are written to the device firmware via write_long_register calls.
  • Runtime Resolution produces a BTreeMap<ButtonId, (Action, Option<Action>)> where the UI displays a single icon while the agent handles press-duration discrimination.

Frequently Asked Questions

What HID++ feature IDs does OpenLogi use for press types?

OpenLogi uses control ID 0xFB for short-press actions and 0xCF for long-press actions. These values are hardcoded in crates/openlogi-hidpp/src/feature/reprog_controls/control_ids.rs and are written to the device using the ReprogControls feature implementation.

Can I assign only a long-press action without a short-press?

No, the current implementation requires a short-press action as the primary binding. The long-press action is optional and specified as Option<Action> in the effective_bindings map. If you omit the long key in TOML or skip set_long_binding in code, long-presses fall back to executing the short-press action.

Where does OpenLogi store the binding configuration?

Bindings are stored in TOML configuration files parsed by crates/openlogi-core/src/config.rs. The Config::bindings field holds the deserialized data, which is then converted into the runtime BTreeMap structure via effective_bindings. These configurations can be global or scoped to specific applications.

How does the UI handle buttons with dual actions?

The UI (Actions Ring) displays a single icon representing the button, regardless of whether dual actions are configured. The press-duration discrimination happens at the agent level by monitoring HID++ press-duration events. The agent consults the (Action, Option<Action>) tuple from effective_bindings to determine which action to execute based on how long the user held the button.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →