How OpenLogi Manages Per-Application Profiles and Bindings: Complete Technical Guide

OpenLogi manages per-application profiles by mapping foreground application identifiers to layered configuration overlays, dynamically merging global bindings with app-specific overrides at runtime using the effective_bindings method.

OpenLogi implements a sophisticated per-application profile system that allows device button bindings to change dynamically based on the active foreground window. This architecture, implemented in the AprilNEA/OpenLogi repository, uses stable OS-specific identifiers and a layered configuration approach to deliver context-aware input control across macOS, Linux, and Windows.

Application Identification with ForegroundApp

OpenLogi relies on the ForegroundApp struct defined in crates/openlogi-core/src/app.rs to provide a stable identifier for per-application profile matching. The struct captures the exact string reported by the operating system, ensuring consistent lookup regardless of window manager or desktop environment variations.

The id field serves as the primary matching contract:

  • macOS: Uses the bundle identifier (e.g., com.apple.Safari)
  • Linux (X11): Reads WM_CLASS property
  • Linux (Wayland): Uses the app_id protocol attribute
  • Windows: Normalizes the lower-cased executable path

This identifier representation appears at lines 11-19 in crates/openlogi-core/src/app.rs, providing the foundation for all per-application binding lookups.

Configuration Architecture: Global and Per-Application Layers

The Config struct maintains two distinct binding layers that work in tandem. The global binding map defines default button actions across all applications, while optional per-application overlays store specific overrides for identified foreground apps.

When resolving actions, OpenLogi employs the Config::effective_bindings method to merge these layers. The overlay logic follows a simple replacement strategy: if a per-application entry exists for a specific button, it supersedes the global entry; otherwise, the global binding persists. This ensures that users can override specific buttons without redefining entire device profiles.

Runtime Binding Resolution with bindings_for

The core resolution logic resides in crates/openlogi-core/src/bindings.rs within the bindings_for helper function (lines 24-33). This function accepts three parameters: the global configuration, a device key (config_key), and the current app_bundle identifier.

The resolution process operates in two stages:

  1. Effective Map Generation: The function calls Config::effective_bindings to retrieve the merged global and per-application binding set for the specified device and foreground app.

  2. Action Projection: Each Binding enum variant—whether Single, LongPress, or Gesture—is converted into its executable action via the click_action() method. This yields a ready-to-use BTreeMap<ButtonId, Action> that the GUI, HID++ driver, and OS hooks consume.

This projection ensures that all downstream components receive a simplified, actionable mapping without needing to understand the underlying configuration layering.

Per-Application Gesture Handling and Demotion

Gesture-capable buttons receive special handling through the oshook_gestures_for function in crates/openlogi-core/src/bindings.rs (lines 29-40). This function builds per-direction gesture maps while respecting per-application overrides.

A critical capability involves gesture demotion: when a per-application profile overrides a gesture button with a Single action, the button drops out of the gesture-mode map entirely for that application. This allows users to convert a multi-directional gesture button into a simple click action on a per-app basis, effectively disabling gesture recognition for specific workflows.

Implementing Per-Application Profiles in Code

The following example demonstrates loading a configuration and resolving effective bindings for a specific foreground application:

use openlogi_core::{Config, ForegroundApp};
use openlogi_core::bindings::bindings_for;

// Load a configuration (e.g. from the TOML file)
let cfg = Config::load_from_path("config.toml")?;

// Assume we have a device with key "2b042"
let device_key = Some("2b042");

// The current foreground application (e.g. Safari on macOS)
let app = ForegroundApp {
    id: "com.apple.Safari".to_string(),
    display_name: "Safari".to_string(),
};

// Compute the effective bindings for this device while Safari is foreground
let effective: BTreeMap<ButtonId, Action> =
    bindings_for(&cfg, device_key, Some(&app.id));

// `effective` now contains the per‑app overrides for Safari,
// falling back to the global profile for any button not overridden.

To programmatically create per-application profiles, modify the configuration directly:

// Example of creating a per‑app profile programmatically
let mut cfg = Config::default();

// Global binding: Back button launches Chrome
cfg.set_binding("2b042", ButtonId::Back, Action::AppLaunch("google-chrome".into()));

// Per‑app override for Safari: Back button goes to "Go Back"
cfg.set_per_app_binding(
    "2b042",
    "com.apple.Safari",               // app bundle identifier
    ButtonId::Back,
    Action::Back,                     // override action
);

// The next call to `bindings_for` with `app.id == "com.apple.Safari"`
// will return the Safari‑specific `Back` action, while other apps keep the global binding.

Cross-Platform Agent Integration and Runtime Updates

The per-application system operates dynamically through the agent's foreground application watcher defined in crates/openlogi-agent-core/src/watchers/foreground_app.rs. This component monitors OS-level window focus events and broadcasts ForegroundApp changes via IPC.

When the active application switches, the agent triggers recomputation of effective bindings through the bindings_for pipeline. The crates/openlogi-ipc/src/ipc.rs module handles serialization of ForegroundApp structures between the system agent and the desktop GUI, enabling real-time UI updates that reflect the current per-application profile name.

The UI state management in crates/openlogi-desktop/src/state/bindings.rs completes the loop by persisting user edits back to the Config struct, including automatic pruning of empty per-application profiles to maintain clean configuration files.

Summary

  • OpenLogi identifies applications using the ForegroundApp struct with OS-specific stable identifiers stored in crates/openlogi-core/src/app.rs.
  • The configuration system maintains separate global and per-application binding layers, merged at runtime via Config::effective_bindings.
  • The bindings_for function in crates/openlogi-core/src/bindings.rs projects layered configurations into executable action maps.
  • Gesture buttons can be demoted to single actions within per-application profiles through oshook_gestures_for.
  • The agent's foreground app watcher ensures binding updates occur immediately when switching between applications.

Frequently Asked Questions

How does OpenLogi identify applications across different operating systems?

OpenLogi uses the ForegroundApp struct to capture platform-specific identifiers: macOS bundle identifiers (e.g., com.apple.Safari), X11 WM_CLASS values, Wayland app_id attributes, and lower-cased Windows executable paths. This identifier normalization occurs in crates/openlogi-core/src/app.rs, ensuring consistent per-application profile matching regardless of the underlying operating system.

What happens when a button binding is not defined for a specific application?

When a button lacks a per-application override, the Config::effective_bindings method automatically falls back to the global binding defined in the base configuration. This layered approach ensures that users only need to define overrides for specific buttons they want to customize, while maintaining consistent default behavior across all other applications.

How does OpenLogi handle gesture buttons in per-application profiles?

Gesture buttons respect per-application overrides through the oshook_gestures_for function. If a per-application profile replaces a gesture binding with a Single action type, the button is effectively demoted and removed from gesture recognition for that specific application. This allows users to disable multi-directional gestures on a per-app basis without affecting global behavior.

Can per-application profiles be modified programmatically?

Yes, the Config struct provides the set_per_app_binding method to programmatically define overrides for specific device and application combinations. Changes persist through the standard configuration serialization, and the agent's runtime monitoring ensures that new effective bindings are computed immediately upon the next foreground application detection cycle.

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 →