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

> Discover how OpenLogi manages per-application profiles and bindings. Learn to map identifiers, merge configurations, and apply app-specific overrides dynamically.

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

---

**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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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:

```rust
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:

```rust
// 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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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.