# How to Configure Per-Application Profile Bindings for Automatic App Switching in OpenLogi

> Learn how to configure per-application profile bindings for automatic app switching in OpenLogi. Dynamically overlay device-specific settings when target apps gain focus.

- Repository: [Xuan Zhang/OpenLogi](https://github.com/AprilNEA/OpenLogi)
- Tags: how-to-guide
- Published: 2026-09-06

---

**OpenLogi configures per-application profile bindings by monitoring the active foreground window and dynamically overlaying device-specific settings—keyed by application bundle identifier—onto the global profile whenever the target application gains focus.**

OpenLogi enables sophisticated device management through **per-application profiles** that apply custom button mappings, DPI adjustments, and lighting configurations only when specific applications are active. To configure per-application profile bindings for automatic app switching in OpenLogi, you define device-specific overrides in a TOML configuration file or through the desktop interface, allowing the agent core to dynamically swap bindings based on the current foreground window's identity.

## Understanding Per-Application Profile Storage

OpenLogi persists configuration data in a TOML file located at `~/.config/openlogi/config.toml`. According to the data model defined in [`crates/openlogi-core/src/config.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/config.rs), each device entry contains both a **global profile** and an optional map of **per-app profiles** keyed by the application's bundle identifier.

Within the configuration structure, the `per_app` section uses the bundle identifier as the lookup key. Each per-application profile contains the same sections as the global profile—buttons, DPI, lighting, and other device settings—but only defined entries override the global defaults. Unspecified properties inherit from the global profile, creating a layered configuration system.

## How OpenLogi Detects and Matches Foreground Applications

The automatic switching mechanism relies on three core components working in concert:

- **Foreground detection** – Platform-specific hooks in [`crates/openlogi-hook/src/lib.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/lib.rs) expose the current foreground application through `ForegroundApp::id`, which returns the identifier used for profile lookups.
- **Profile resolution** – The IPC layer defined in [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs) transmits the active application identifier from the background agent to the GUI process.
- **Per-app overlay** – When a new foreground application is detected, the orchestrator in [`crates/openlogi-agent-core/src/orchestrator.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent-core/src/orchestrator.rs) recomputes the effective binding map by merging the per-app profile on top of the global profile.

This architecture ensures that profile switches occur immediately when the user changes applications, with zero manual intervention required.

## Setting Up Per-Application Profile Bindings

### Editing the TOML Configuration Directly

To manually configure per-application profile bindings, edit the TOML configuration file and add a `per_app` section under the target device. The following example demonstrates profiles for Safari and VS Code:

```toml
[[devices]]
name = "Logitech G502"

[devices.profile]

# Global defaults apply when no per-app profile matches

[devices.per_app."com.apple.Safari"]
[devices.per_app."com.apple.Safari".profile]
button_1 = { action = "copy" }
dpi = 800

[devices.per_app."com.microsoft.VSCode"]
[devices.per_app."com.microsoft.VSCode".profile]
button_2 = { action = "run_debug" }
button_3 = { action = "toggle_terminal" }

```

The configuration parser in [`crates/openlogi-core/src/config.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/config.rs) validates these entries and loads them into the device state manager. Keys inside `devices.per_app."<bundle_id>"` mirror the structure of the global `profile` section, allowing granular control over individual settings.

### Using the Desktop Interface

Alternatively, use the desktop UI to create per-application profiles without manually editing TOML:

1. Open the **Profiles** pane (ring icon) in the OpenLogi desktop application.
2. Click **Add Profile** → **Per-App** and select the target application from the installed apps list.
3. Adjust bindings in the panel; changes persist automatically to the configuration file.

The UI logic for this workflow resides in [`crates/openlogi-desktop/src/state/bindings.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/state/bindings.rs), which handles the creation, editing, and deletion of per-app binding entries.

### Verifying Automatic Switching

To confirm that automatic app switching functions correctly:

1. Launch the target application (e.g., Safari with bundle ID `com.apple.Safari`).
2. Observe the OpenLogi GUI header, which displays the active profile name via `state.active_profile_name()` in [`crates/openlogi-desktop/src/state.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/state.rs).
3. Test the configured buttons; the agent routes inputs according to the per-app overrides rather than the global profile.

The system applies changes immediately upon detecting the foreground window change, requiring no restart of the OpenLogi agent.

## Handling Edge Cases and Configuration Pitfalls

When working with per-application profiles, consider the following scenarios:

- **No matching per-app profile** – The system falls back to the global profile. Ensure you define a global profile to guarantee consistent default behavior.
- **Applications without bundle identifiers** – On some Linux window managers, applications may lack standard identifiers, causing per-app lookup to fail. In these cases, the system remains on the global profile; you may manually specify fallback identifiers in the configuration if needed.
- **Multiple devices with overlapping profiles** – Each device resolves its per-app overlay independently. Device configurations remain isolated, so a profile activated for your mouse does not affect your keyboard settings.
- **Empty profile pruning** – If you remove all bindings from a per-app profile, the UI logic in [`crates/openlogi-desktop/src/state/bindings.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/state/bindings.rs) prunes the empty entry on save, reverting to global defaults. Use the delete function in the UI rather than manual file editing to remove profiles cleanly.

## Programmatic Configuration Examples

### Creating Bindings in Rust

For advanced integrations, manipulate per-application profiles programmatically using the `openlogi_core` crate:

```rust
use openlogi_core::config::{Config, DeviceKey, Profile};
use std::path::PathBuf;

// Load existing configuration from ~/.config/openlogi/config.toml
let mut cfg = Config::load().expect("cannot read config");

// Identify the target device and application
let dev_key = DeviceKey::new("Logitech G502");
let app_id = "com.apple.Safari".to_string();

// Create or modify the per-app profile entry
let per_app = cfg
    .devices
    .entry(dev_key.clone())
    .or_default()
    .per_app
    .entry(app_id.clone())
    .or_insert_with(Profile::default);

// Assign a custom button binding
per_app.profile.button_1 = Some(openlogi_core::bindings::Binding::Single(
    openlogi_core::actions::Action::Custom("copy".into()),
));

// Write changes to disk
cfg.save().expect("cannot write config");

```

This approach directly interfaces with the configuration API defined in [`crates/openlogi-core/src/config.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/config.rs), bypassing the UI while maintaining TOML compatibility.

### CLI Profile Management

For automation or shell scripting, use the OpenLogi CLI to set per-application bindings:

```bash
openlogi-cli profile set --device "Logitech G502" \
  --app "com.apple.Safari" --binding "button_1=copy"

```

The CLI implementation is located in [`crates/openlogi-cli/src/cmd/light.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-cli/src/cmd/light.rs) and related command modules, providing programmatic access to the same configuration structures used by the GUI.

## Summary

- OpenLogi stores per-application profiles in `~/.config/openlogi/config.toml` as overlays on the global profile, parsed by [`crates/openlogi-core/src/config.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/config.rs).
- The system uses `ForegroundApp::id` from [`crates/openlogi-hook/src/lib.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/lib.rs) to detect the active application and trigger automatic profile switching.
- Configure bindings manually via TOML editing or through the desktop UI in [`crates/openlogi-desktop/src/state/bindings.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/state/bindings.rs).
- The orchestrator in [`crates/openlogi-agent-core/src/orchestrator.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent-core/src/orchestrator.rs) merges per-app settings with global defaults at runtime.
- Use the Rust API or CLI tools for automated profile management and deployment.

## Frequently Asked Questions

### What happens if an application does not have a bundle identifier?

When the foreground application lacks a bundle identifier—common with some Linux window managers or Wine applications—the lookup mechanism in [`crates/openlogi-hook/src/lib.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/lib.rs) fails to return a valid key. In this scenario, OpenLogi retains the current global profile and does not attempt to apply per-app overrides. You can work around this limitation by manually specifying an identifier in the configuration file if the application exposes any consistent metadata.

### Can different devices have conflicting per-app profiles for the same application?

Yes, and these conflicts do not cause errors. Each device entry in the TOML configuration maintains its own `per_app` map, and the orchestrator in [`crates/openlogi-agent-core/src/orchestrator.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent-core/src/orchestrator.rs) processes each device independently. This means your mouse can activate a gaming profile for an application while your keyboard maintains a productivity layout for the same app, with neither configuration interfering with the other.

### How do I temporarily disable automatic app switching for a specific device?

To disable automatic switching, remove all per-app entries for that device from the configuration file, or delete the per-app profile through the desktop UI. When no per-app profiles exist for a device, the system consistently uses the global profile defined in `devices.profile`. The cleanup logic in [`crates/openlogi-desktop/src/state/bindings.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/state/bindings.rs) automatically removes empty per-app entries when you save changes through the interface.

### Why does the GUI not show my newly created per-app profile immediately?

The GUI relies on the IPC layer in [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs) to receive foreground application updates from the agent. If the profile does not appear, ensure the OpenLogi agent process is running and that the application you configured is actually the foreground window. The display updates when `state.active_profile_name()` in [`crates/openlogi-desktop/src/state.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/state.rs) detects a change in the active application identifier.