How to Define Custom Actions and Bindings in OpenLogi: A Complete Guide

You define custom actions and bindings in OpenLogi by selecting payload-carrying variants from the Action enum (such as CustomShortcut or Workflow) and mapping them to physical buttons via the TOML configuration file located at ~/.config/openlogi/config.toml, where the Config::effective_bindings method automatically merges global defaults with per-application overrides.

OpenLogi is an open-source input remapping framework that translates physical button presses into executable OS-level commands. To customize your input devices, you work with two core components: the extensible Action enum defined in crates/openlogi-core/src/binding/action.rs and the TOML-based configuration system managed by crates/openlogi-core/src/config.rs. This architecture allows you to create everything from simple keyboard shortcuts to complex multi-step workflows without modifying the Rust source code.

Understanding the Action Enum Structure

The foundation of every user-triggered operation resides in the Action enum located in crates/openlogi-core/src/binding/action.rs. This enum is deliberately designed to be extensible, separating operations into two distinct categories.

Payload-Free Actions

Payload-free actions are simple variants that execute fixed commands without additional parameters. Common examples include:

  • Copy
  • PlayPause
  • ScrollUp

These variants are processed by the for_each_unit_action! macro, which generates the label, category, and catalog helper methods. These helpers populate the UI picker with user-friendly descriptions.

Payload-Carrying Actions

Payload-carrying actions accept parameters that customize their behavior, enabling complex automation:

  • CustomShortcut(KeyCombo) – Dispatches arbitrary keyboard chords.
  • Workflow(Vec<WorkflowStep>) – Executes scripted sequences of text input, delays, and key presses.
  • OpenApplication(ApplicationTarget) – Launches specific programs.
  • HoldShortcut(KeyCombo) – Implements "hold-to-talk" chords that activate while a button remains pressed.

These variants allow you to define custom actions entirely through configuration without extending the enum definition.

Configuring Bindings in config.toml

Bindings—the mapping from physical buttons to Action variants—are stored in a TOML configuration file (config.toml) located in the platform-specific config directory (e.g., ~/.config/openlogi/config.toml on Linux/macOS). The Config struct in crates/openlogi-core/src/config.rs parses this file and exposes the effective_bindings(device_id, app_bundle) method to resolve final mappings.

Global Bindings

Global bindings apply to your device regardless of the active application. Define them under the [bindings] table using ButtonId variants (defined in openlogi_core::binding::ButtonId) as keys:

[bindings]
ButtonId.Back = "Copy"                 # Simple payload-free action

ButtonId.Forward = { CustomShortcut = "Ctrl+Shift+T" }  # Payload-carrying action

ButtonId.Middle = { Workflow = [
    { TypeText = "Hello, world!" },
    { Delay = { millis = 500 } },
    { PressKey = "Enter" }
] }

Values can be bare strings for payload-free actions or TOML tables specifying the variant name and its payload for complex operations.

Per-Application Overrides

You can override global bindings for specific applications by nesting configurations under the [per_app] table, using the application bundle ID as the key:

[per_app."com.microsoft.VSCode"]
ButtonId.Back = "Undo"
ButtonId.Forward = { CustomShortcut = "Ctrl+Shift+P" }

When resolving bindings, Config::effective_bindings merges these definitions, with per-app overrides taking precedence over global defaults.

Creating Complex Workflows

The Workflow action type accepts a vector of WorkflowStep enums, allowing you to script multi-step automations directly in TOML. Each step executes sequentially:


# ~/.config/openlogi/config.toml

[bindings]
ButtonId.Middle = { Workflow = [
    { TypeText = "git status" },
    { Delay = { millis = 200 } },
    { PressKey = "Enter" }
] }

Valid workflow steps include TypeText for string input, Delay for timed pauses, and PressKey for individual keystrokes. The injector (openlogi-inject) synthesizes these into OS-level commands at runtime.

Runtime Binding Resolution

When the GUI or CLI needs active mappings, it invokes Config::effective_bindings(device_id, app_bundle) from crates/openlogi-core/src/config.rs. This method implements a hierarchical merge strategy:

  1. Device-global bindings serve as the base configuration.
  2. Per-app overrides are layered on top when a specific application window is foreground.

This resolution guarantees that the most specific definition wins. The resulting map drives the Actions Ring (the on-screen radial menu), with slot-to-action mappings transmitted over IPC using the ActionRingInvocation struct defined in crates/openlogi-ipc/src/ipc.rs.

You can also manipulate bindings programmatically using the Rust API:

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

// Build a custom shortcut programmatically
let combo = KeyCombo::parse("Ctrl+Alt+L").unwrap();
let custom = Action::CustomShortcut(combo);

// Bind it to a button in memory
let mut cfg = Config::load_default().unwrap();
cfg.set_binding(ButtonId::Back, custom.clone());

// Resolve the final map for a device (no per-app overrides)
let bindings = cfg.effective_bindings("2b042", None);
assert_eq!(bindings.get(&ButtonId::Back), Some(&custom));

Summary

  • OpenLogi stores all possible operations in the Action enum at crates/openlogi-core/src/binding/action.rs, which supports both simple commands and complex parameterized actions.
  • You map buttons to actions via config.toml in your platform-specific config directory, using ButtonId keys and TOML values that specify either bare action names or detailed payload tables.
  • Config::effective_bindings in crates/openlogi-core/src/config.rs resolves final bindings by merging global defaults with per-application overrides.
  • Workflow actions accept sequences of WorkflowStep enums to create scripted automations without external tools.
  • The ActionRingInvocation struct in crates/openlogi-ipc/src/ipc.rs carries binding data to the UI layer for the on-screen radial menu.

Frequently Asked Questions

Do I need to modify the Rust source code to add custom actions?

No. The Action enum in crates/openlogi-core/src/binding/action.rs already includes payload-carrying variants like CustomShortcut and Workflow that accept user-defined parameters. You create custom behavior by configuring these variants in config.toml rather than extending the enum.

What configuration file format does OpenLogi use?

OpenLogi uses TOML for its configuration files. The main configuration resides in config.toml within the platform-specific config directory (e.g., ~/.config/openlogi/config.toml on Linux/macOS), parsed by the Config struct in crates/openlogi-core/src/config.rs.

How does OpenLogi resolve conflicting bindings for the same button?

The Config::effective_bindings method implements a specificity-based merge: per-application bindings defined under [per_app."bundle_id"] override global bindings defined under [bindings]. When no application-specific override exists, the global binding applies.

Can I create different actions for the same button depending on which application is active?

Yes. Define global bindings under [bindings] for default behavior, then create application-specific tables under [per_app."com.example.AppName"] to override those bindings when that application is foregrounded. The resolution system automatically selects the appropriate mapping based on the current active window.

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 →