# How OpenLogi Auto-Switches Per-Application Profile Overlays

> Discover how OpenLogi automatically switches per-application profile overlays by monitoring foreground apps and merging configurations for dynamic adjustments.

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

---

**OpenLogi automatically switches per-application profile overlays by monitoring the foreground application through a platform-specific hook, transmitting the app identifier via IPC to the GUI, and dynamically merging per-app binding overrides with global configurations whenever the active window changes.**

OpenLogi implements intelligent input remapping that adapts to your current workflow by automatically switching per-application profile overlays when you change windows. This open-source Rust application uses a three-tier architecture to detect foreground application changes and instantly update the Actions Ring without manual intervention.

## Three-Tier Detection Architecture

### Agent (Background Server)

The **agent** process runs continuously to maintain system-wide state. In [`openlogi-core/src/app.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/openlogi-core/src/app.rs), the `ForegroundApp` type defines the structure that carries the identifier used for profile matching. The agent stores the current foreground application in its state and publishes updates through the `observe` RPC defined in [`openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/openlogi-ipc/src/ipc.rs), which transmits the `ForegroundApps` DTO containing both the current app and a recent-apps list.

### Hook (Input-Capture Layer)

Platform-specific hooks extract the foreground app identifier across macOS, Linux, and Windows. According to [`openlogi-hook/src/lib.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/openlogi-hook/src/lib.rs), the `ForegroundApp::id` field provides the exact string against which per-app profile keys are compared. On macOS this utilizes `CGEventTap`, on Wayland it monitors `wlroots`, and on X11 it tracks window manager events to capture the stable application identifier.

### GUI (Client)

The desktop GUI receives `ForegroundApps` updates via `Agent::observe` and triggers profile selection. The `active_profile_name()` method in [`openlogi-desktop/src/state.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/openlogi-desktop/src/state.rs) returns the name of the per-app profile currently in effect by looking up the configuration using the current `ForegroundApp.id` as the key.

## The Auto-Switching Execution Flow

When you switch applications, OpenLogi executes a five-step pipeline:

1. **Hook Detection**: Platform-specific code watches the window server and builds a `ForegroundApp` struct containing the id and display name.
2. **Agent Publication**: The agent updates `ForegroundApps::current` and pushes the change through the IPC channel.
3. **Profile Resolution**: The GUI calls `AppState::active_profile_name()` to look up the per-app profile keyed by the current identifier.
4. **Binding Overlay**: [`openlogi-desktop/src/bindings.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/openlogi-desktop/src/bindings.rs) merges global device bindings with per-app overlays, respecting overrides while falling back to globals when no per-app entry exists.
5. **Ring Update**: The overlay process in [`openlogi-overlay/src/ring.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/openlogi-overlay/src/ring.rs) displays the updated Actions Ring, while `spawn_click_away_dismissal` in [`openlogi-overlay/src/session.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/openlogi-overlay/src/session.rs) ensures safe dismissal when clicking outside the ring.

## Profile Keys and Configuration Schema

Per-app overlays use **stable, platform-specific identifiers** as profile keys. These include `bundle_id` on macOS, `WM_CLASS` on X11, Wayland `app_id`, or lower-cased executable paths. This guarantees that profiles match only their intended applications without cross-platform collisions.

The TOML configuration schema in [`openlogi-core/src/config.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/openlogi-core/src/config.rs) stores per-app bindings in a separate section keyed by the app identifier. When resolved, [`openlogi-core/src/bindings.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/openlogi-core/src/bindings.rs) produces the effective binding map by overlaying per-app configurations onto global defaults.

## Implementation Example

The following Rust code demonstrates the client-side flow for handling profile switches:

```rust
// 1. Receive foreground application update via IPC
let apps: ForegroundApps = agent.observe().await?;
let current_app = apps.current.as_ref().map(|a| &a.id);

// 2. Resolve the active profile name from application state
let profile_name = state.active_profile_name(); // returns Option<&str>

// 3. Build effective bindings by merging global and per-app overlays
let effective = openlogi_core::bindings::effective_bindings(
    &global_bindings,
    &per_app_overlays.get(profile_name),
);

// 4. Render the updated Actions Ring
let ring_view = RingView::new(effective);
cx.update(|cx| cx.add_window(ring_view, /* geometry */));

```

## Summary

- **Platform-specific hooks** extract stable application identifiers using native APIs like `CGEventTap` and `wlroots`.
- **IPC communication** via `ForegroundApps` DTOs ensures the GUI receives real-time updates when the foreground window changes.
- **Dynamic binding merging** in [`bindings.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/bindings.rs) overlays per-app profiles onto global configurations while preserving fallback behavior.
- **Automatic ring updates** occur without user intervention, with `spawn_click_away_dismissal` managing proper session lifecycle.

## Frequently Asked Questions

### What identifier does OpenLogi use to match applications to profiles?

OpenLogi uses the `ForegroundApp::id` field, which contains platform-specific stable identifiers such as macOS `bundle_id`, Linux `WM_CLASS` or `app_id`, or the executable path. This ensures profiles bind to specific applications regardless of window title changes.

### How does OpenLogi handle applications without specific profiles?

When no per-app profile exists for the current `ForegroundApp.id`, the system falls back to global bindings defined in the base configuration. The `effective_bindings` function in [`openlogi-core/src/bindings.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/openlogi-core/src/bindings.rs) handles this merge logic automatically.

### Does the profile switch happen instantly when changing windows?

Yes, the switch occurs within milliseconds. The hook detects the window change immediately, the agent publishes the update via `Agent::observe`, and the GUI regenerates the binding map and Actions Ring layout before the user interacts with the new window.

### Can per-app overlays override specific keys while keeping others global?

Absolutely. The binding merge logic in [`openlogi-desktop/src/bindings.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/openlogi-desktop/src/bindings.rs) overlays only the specified per-app bindings onto the global map, allowing selective overrides while maintaining global shortcuts for unmapped keys.