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 overForegroundApps::recentto list applications by identifier and display nameprofile_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:
- Agent detects focus change via platform observer
- New
AgentSnapshottransmitted over IPC set_foregroundupdates cache (only if changed)active_profile_namere-evaluates override status- 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-hookcapture foreground changes and publish them via IPC - State management:
AppState::set_foregrounddeduplicates updates to prevent unnecessary re-renders (lines 105-112) - Profile resolution:
active_profile_nameperforms 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →