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

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

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

// 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):

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:

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

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

Conditional profile usage:

// 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 + platform equivalents Platform-specific foreground observation
crates/openlogi-ipc/src/ipc.rs ForegroundApps structure and AgentSnapshot IPC definition
crates/openlogi-desktop/src/state/agent.rs AgentSession foreground storage
crates/openlogi-desktop/src/state.rs set_foreground, recent_apps, profile_app, active_profile_name
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 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.

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 →