# How OpenLogi Dynamically Resolves and Applies Per-Application Profile Bindings on Focus Change

> Discover how OpenLogi dynamically resolves per-application profile bindings on focus change. Learn about its foreground observer and IPC communication for seamless application profile application.

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

---

**OpenLogi uses a foreground observer in the agent process to detect active window changes, propagates this via IPC to the GUI, and re-evaluates per-application profile bindings by checking for device-specific overrides against the cached foreground application.**

OpenLogi's dynamic per-application profile system automatically switches device configurations when you change focus between applications. This behavior relies on a split architecture: a lightweight *agent* process monitors system state, while the *GUI* resolves which profile bindings to apply. Both communicate through a local IPC channel using tarpc.

## The Agent-Side Foreground Detection

The agent maintains a live view of the front-most application through platform-specific observers in **`openlogi-hook`**. On Windows, this implementation resides in [`crates/openlogi-hook/src/windows/foreground.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/windows/foreground.rs), with equivalent modules for macOS and Linux.

When the active window changes, the observer packages the new state into an `AgentSnapshot`. This structure contains a `ForegroundApps` field (defined in the IPC crate) that tracks both the current foreground application and recently focused apps. The snapshot travels over the local tarpc channel to the GUI process.

## The GUI State Machine

The desktop application stores the latest snapshot in its global `AppState`. Within `AgentSession`, the `foreground` field holds this cached data:

```rust
// Located in crates/openlogi-desktop/src/state/agent.rs, lines 4-12
pub struct AgentSession {
    pub foreground: Option<ForegroundApps>,
    // ... other fields
}

```

When the GUI receives a new snapshot, it calls `AppState::set_foreground`. This method performs a change check before updating:

```rust
// Located in crates/openlogi-desktop/src/state.rs, lines 105-112
pub fn set_foreground(&mut self, fg: Option<ForegroundApps>) -> bool {
    if self.agent.foreground.as_ref() != fg.as_ref() {
        self.agent.foreground = fg;
        true // indicates a change occurred
    } else {
        false
    }
}

```

The boolean return value signals whether downstream components need to recompute their state.

## Resolving the Active Profile

From the cached `ForegroundApps`, `AppState` exposes several helper methods:

- **`recent_apps`** (lines 11-18): Iterates over `ForegroundApps::recent` to list applications by identifier and display name
- **`profile_app`** (lines 41-43): Returns the first entry in the recent list—the application that was frontmost *before* OpenLogi's own window gained focus

The core resolution logic lives in **`active_profile_name`** (lines 450-458):

```rust
pub fn active_profile_name(&self) -> Option<String> {
    // Get the persistent config key for the selected device
    let device_key = self.selected_device()?.persistent_config_key();
    
    // Identify the frontmost (non-OpenLogi) application
    let app = self.profile_app()?;
    
    // Check for a per-application override in the configuration
    if self.config.has_app_override(&device_key, &app.identifier) {
        Some(app.display_name)
    } else {
        None // Fall back to global profile
    }
}

```

This method implements a three-step lookup: **device key → foreground app → override check**.

## Per-Application Override Resolution

The `has_app_override` call determines whether the device's configuration contains bindings specific to that application. If found, `active_profile_name` returns the application's display name, triggering the UI to render per-application bindings. If no override exists, the `None` return signals fallback to the device's global profile.

This resolution happens automatically without user intervention. The complete flow:

1. Agent detects focus change via platform observer
2. New `AgentSnapshot` transmitted over IPC
3. `set_foreground` updates cache (only if changed)
4. `active_profile_name` re-evaluates override status
5. UI renders appropriate bindings

## Code Examples

**Receiving and handling foreground updates in the GUI poll loop:**

```rust
// Receive snapshot from agent process
let snapshot = ipc::receive_snapshot()?;

// Update cached foreground data
if app_state.set_foreground(snapshot.foreground) {
    // Foreground changed → recompute active profile
    let profile = app_state.active_profile_name();
    
    // Update UI with correct bindings
    ui.update_profile_display(profile);
}

```

**Populating a recent apps picker:**

```rust
// Query recent apps for UI selection
for (id, name) in app_state.recent_apps() {
    combo.add_item(name, id);
}

```

**Conditional profile usage:**

```rust
// Resolve active profile for selected device
match app_state.active_profile_name() {
    Some(name) => println!("Using per-app profile: {}", name),
    None => println!("Using device global profile"),
}

```

## Key Implementation Files

| File Path | Responsibility |
|-----------|--------------|
| [`crates/openlogi-hook/src/windows/foreground.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/windows/foreground.rs) + platform equivalents | Platform-specific foreground observation |
| [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs) | `ForegroundApps` structure and `AgentSnapshot` IPC definition |
| [`crates/openlogi-desktop/src/state/agent.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/state/agent.rs) | `AgentSession` foreground storage |
| [`crates/openlogi-desktop/src/state.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/state.rs) | `set_foreground`, `recent_apps`, `profile_app`, `active_profile_name` |
| [`crates/openlogi-desktop/src/features/profiles.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/features/profiles.rs) | UI consumption of `active_profile_name` |

## Summary

- **Agent-side detection**: Platform-specific observers in `openlogi-hook` capture foreground changes and publish them via IPC
- **State management**: `AppState::set_foreground` deduplicates updates to prevent unnecessary re-renders (lines 105-112)
- **Profile resolution**: `active_profile_name` performs a hierarchical lookup—device key → foreground app → override existence check (lines 450-458)
- **Automatic application**: The entire pipeline triggers on every focus change without manual user action

## Frequently Asked Questions

### How does OpenLogi avoid updating the UI when the same window keeps focus?

`AppState::set_foreground` compares the incoming `ForegroundApps` against the cached value before updating. Only when `as_ref()` comparisons differ does it replace the stored data and return `true`, signaling downstream components to recompute.

### What happens if no per-application profile exists for the foreground app?

`active_profile_name` returns `None`, and the UI falls back to rendering the device's global profile bindings. This behavior is controlled by the `has_app_override` check against the persistent configuration.

### Why does `profile_app` skip the most recent entry when OpenLogi itself has focus?

Lines 41-43 in [`state.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/state.rs) return the first entry from `ForegroundApps::recent` that represents the actual frontmost application *before* OpenLogi's window gained focus. This prevents the GUI from showing a profile for its own process when the user interacts with settings.

### How does the agent-GUI communication work across platforms?

Both processes use tarpc over a local IPC channel. The `AgentSnapshot` structure (defined in `openlogi-ipc`) serializes platform-agnostic representations of foreground state, allowing the same GUI code to run regardless of whether the agent monitors Windows, macOS, or Linux.