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

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, 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 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 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 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:

[[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 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, 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.
  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 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:

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, bypassing the UI while maintaining TOML compatibility.

CLI Profile Management

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

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 and related command modules, providing programmatic access to the same configuration structures used by the GUI.

Summary

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 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 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 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 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 detects a change in the active application identifier.

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 →