# How Per-Application Profile Overlays Auto-Switch on App Focus in OpenLogi

> Discover how OpenLogi's per-application profile overlays auto-switch on app focus across Linux, macOS, and Windows. Streamline your workflow with automatic configuration changes.

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

---

**OpenLogi implements per-application profile overlays by reading the identifier of the frontmost application and overlaying stored per-app bindings onto the generic device configuration, using a background watcher that polls at approximately 1 Hz across Linux (X11/Wayland), macOS, and Windows.**

OpenLogi is an open-source input device manager that enables context-aware control schemes through automatic profile switching. This article examines the source code implementation of how per-application profile overlays automatically switch when app focus changes, covering the cross-platform detection mechanisms and configuration merging logic found in the `AprilNEA/OpenLogi` repository.

## Platform-Specific Front-Most Detection

OpenLogi abstracts platform differences through the `FrontmostSource` trait, which provides an opaque identifier string for the currently focused window. Each platform implements this trait to extract the appropriate application identifier.

### Linux: X11 and Wayland Backends

In [`crates/openlogi-hook/src/linux.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/linux.rs), the `FrontmostSource` trait defines the interface for retrieving the active window identifier. The implementation distinguishes between X11 and Wayland sessions using `detect_session_kind()` (lines 66‑81) and selects the appropriate backend via `detect_frontmost_source()` (lines 102‑130).

- **X11**: The `X11Source` backend reads the `WM_CLASS` property, returning the class component (lines 21‑32).
- **Wayland**: Two backends provide support for modern compositors:
  - `WlrForeignToplevelSource` in [`crates/openlogi-hook/src/linux/wlr_foreign_toplevel.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/linux/wlr_foreign_toplevel.rs) implements the `wlr_foreign_toplevel` protocol for wlroots-based compositors.
  - `GnomeShellSource` in [`crates/openlogi-hook/src/linux/gnome_shell.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/linux/gnome_shell.rs) uses D-Bus to query the frontmost application on GNOME Shell.

### macOS Bundle Identification

On macOS, [`crates/openlogi-hook/src/macos.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/macos.rs) implements `frontmost_app()` using `NSWorkspace.sharedWorkspace().frontmostApplication()?.bundleIdentifier()` to retrieve the application's bundle ID (e.g., `com.apple.Terminal`).

### Windows Executable Detection

The Windows implementation in [`crates/openlogi-hook/src/windows.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/windows.rs) provides `frontmost_app()`, which returns the active window's executable name as the identifier.

## The Background Watcher Thread

The `ForegroundAppWatcher` 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) runs on a dedicated background thread to monitor focus changes without blocking the main application logic.

The watcher registers a channel that monitors `openlogi_hook::frontmost_bundle_id`, polling the front-most source at approximately 1 Hz. When the identifier differs from the previously cached value, the watcher emits a change event through a broadcast channel, triggering the configuration system to recompute the effective bindings.

## Configuration Overlay Resolution

When a new foreground-app identifier arrives, the core configuration resolves the effective binding map by merging per-application overlays into the base device profile.

In [`crates/openlogi-core/src/config.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/config.rs), the `effective_bindings()` method (lines 410‑428) calls `app_overlay()` (lines 800‑819) to retrieve platform-specific overrides. The overlay lookup checks `overlays.get(app)` for an exact match, or falls back to an `exe:<lowercase-executable>` key for Windows-style identifiers.

The [`crates/openlogi-core/src/bindings.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/bindings.rs) file (lines 15‑86) then applies this overlay during the binding lookup, ensuring the per-app configuration takes precedence over generic device settings. The merged map is subsequently sent to the input-hook or overlay UI, causing the per-application profile to appear automatically.

## Complete Auto-Switching Workflow

The end-to-end process follows these steps:

1. **Session Detection**: `detect_session_kind()` determines whether the environment is X11, Wayland, or Unknown (lines 66‑81).
2. **Backend Selection**: `detect_frontmost_source()` builds a candidate list ( prioritizing Wayland backends with X11 fallback) and returns the first successful connection (lines 102‑130).
3. **Identifier Reading**: The selected backend's `frontmost_bundle_id()` returns an opaque string (WM_CLASS on X11, `app_id` on Wayland, bundle ID on macOS, or executable name on Windows).
4. **Change Detection**: `ForegroundAppWatcher` compares the new identifier against its cache and posts updates to the broadcast channel only when the value changes.
5. **Binding Resolution**: The agent calls `Config::effective_bindings(device_key, app_bundle)`, which merges the per-app overlay into the base profile using `app_overlay()`.
6. **Profile Application**: The resulting binding map is applied to the device or displayed in the overlay UI, completing the automatic switch.

## Implementation Examples

The following code demonstrates the core API usage for implementing auto-switching in your own integration:

```rust
// 1. Get the front-most identifier (platform-agnostic)
use openlogi_hook::frontmost_bundle_id;

if let Some(app_id) = frontmost_bundle_id() {
    println!("Frontmost app: {}", app_id);
}

```

```rust
// 2. Resolve the effective binding map for a device
use openlogi_core::{Config, DeviceKey};

let cfg = Config::load()?;                     // reads ~/.config/openlogi-dev/config.toml
let device_key = DeviceKey::Mouse;              // example device
let bindings = cfg.effective_bindings(device_key, &app_id);

// `bindings` now contains the base profile overlaid with any per-app entries

```

```rust
// 3. Background watcher (simplified)
use openlogi_agent_core::watchers::foreground_app::ForegroundAppWatcher;

let (tx, rx) = std::sync::mpsc::channel();
let watcher = ForegroundAppWatcher::new(tx);
watcher.start();                               // spawns a thread that polls every second

// In the main loop:
while let Ok(app_id) = rx.recv() {
    // react to a change in foreground app
    println!("Switching profile for {}", app_id);
}

```

## Summary

- **Cross-platform identifiers**: OpenLogi uses `WM_CLASS` on X11, `app_id` on Wayland, bundle IDs on macOS, and executable names on Windows to identify applications.
- **Polling architecture**: A background thread in `ForegroundAppWatcher` checks the front-most application at ~1 Hz to detect focus changes without blocking the main thread.
- **Configuration merging**: The `effective_bindings()` method in [`crates/openlogi-core/src/config.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/config.rs) overlays per-app profiles onto base device configurations using the `app_overlay()` logic.
- **Namespace isolation**: X11 and Wayland identifiers exist in separate namespaces, requiring distinct overlay entries for the same application under different display servers.

## Frequently Asked Questions

### How does OpenLogi handle the same application running on different platforms?

Because the identifier is taken exactly as reported by the platform-specific backend (WM_CLASS on X11 versus bundle ID on macOS), per-application profiles are platform-specific. You must define separate overlay entries for each platform identifier (e.g., `firefox` for X11 and `org.mozilla.firefox` for macOS) as the code does not attempt to reconcile these namespaces.

### What is the polling frequency for focus changes, and can it be adjusted?

The `ForegroundAppWatcher` polls the front-most source at approximately 1 Hz (once per second). This frequency is hardcoded in the background thread implementation to balance responsiveness with CPU usage. The source code 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) does not expose a configuration parameter for this interval in the current implementation.

### How are binding conflicts resolved between base and overlay profiles?

According to [`crates/openlogi-core/src/bindings.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/bindings.rs) (lines 15‑86), the overlay system performs a merge where per-application bindings take precedence over base device mappings. When `effective_bindings()` constructs the final map, values from `app_overlay()` overwrite any conflicting entries in the base profile, ensuring that app-specific configurations override generic settings.

### What happens when the front-most detection fails or returns None?

If `frontmost_bundle_id()` returns `None` (indicating no focused window or detection failure), the configuration system falls back to the base device profile without applying any overlay. The `app_overlay()` method in [`crates/openlogi-core/src/config.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/config.rs) handles missing identifiers by returning an empty overlay, leaving the base bindings unchanged until a valid identifier is detected.