How OpenLogi Implements Auto-Switching Per-Application Profile Overlays
OpenLogi automatically switches per-application profile overlays by detecting the foreground application through platform-specific hooks, transmitting the identifier via IPC to the GUI, and dynamically regenerating the Actions Ring bindings whenever the active window changes.
OpenLogi is an open-source input management framework that enables context-aware control schemes through per-application profile overlays. The auto-switching mechanism relies on a real-time monitoring pipeline that tracks window focus changes across macOS, Linux, and Windows. This article examines the implementation details based on the AprilNEA/OpenLogi source code, covering the Agent-Hook-GUI architecture and the binding overlay system.
Three-Tier Architecture for Profile Detection
OpenLogi implements per-application profile overlays through a distributed architecture consisting of a background agent, a platform-specific hook, and a GUI client. Each tier handles a distinct phase of the foreground application detection and profile switching pipeline.
Agent (Background Server)
The Agent serves as the central coordinator that maintains the system state and publishes foreground application updates to the GUI. In openlogi-core/src/app.rs, the ForegroundApp type defines the structure that carries the application identifier used for profile matching:
pub struct ForegroundApp {
pub id: String,
pub name: String,
}
The agent stores the current foreground application in its internal state and pushes updates through the IPC channel defined in openlogi-ipc/src/ipc.rs. The ForegroundApps DTO (Data Transfer Object) contains both the current application and a history of recent apps:
pub struct ForegroundApps {
pub current: Option<ForegroundApp>,
pub recent: Vec<ForegroundApp>,
}
Hook (Input-Capture Layer)
The Hook operates at the platform level to extract the foreground application identifier using OS-specific APIs. According to openlogi-hook/src/lib.rs, the hook monitors the window server through platform-specific mechanisms such as macOS CGEventTap, Wayland wlroots, or X11 WM_CLASS properties.
The hook constructs a ForegroundApp instance where the id field represents a stable, platform-specific identifier. On macOS, this is the bundle_id; on Linux Wayland, the app_id; and on X11, the WM_CLASS or lower-cased executable path. This identifier serves as the exact string key that the per-application profile lookup compares against.
GUI (Client)
The GUI receives foreground application updates via the Agent::observe RPC and manages the visual presentation of the Actions Ring. The desktop client implementation in openlogi-desktop/src/state.rs provides the active_profile_name() method, which resolves the appropriate per-application profile based on the current ForegroundApp.id:
pub fn active_profile_name(&self) -> Option<&str> {
// Resolves profile key from current foreground app ID
}
When a new application enters focus, the GUI triggers a binding regeneration process that overlays per-application configurations onto the global defaults.
The Auto-Switching Flow
The complete auto-switching cycle occurs in five distinct steps:
-
Hook Detection – Platform-specific code in the hook layer detects window focus changes and constructs a
ForegroundAppcontaining the application identifier and display name. -
Agent Broadcasting – The agent stores the foreground app in
ForegroundApps::currentand pushes an update through theobserveRPC stream to all connected clients. -
Profile Resolution – Upon receiving the
observeevent, the desktop process callsAppState::active_profile_name()to lookup the per-application profile keyed by the currentForegroundApp.id. -
Binding Overlay – The binding builder in
openlogi-core/src/bindings.rsmerges global device bindings with the per-application overlay. Theeffective_bindings()function applies overrides while falling back to global settings when no per-app entry exists. -
Ring Update – The overlay process (
openlogi-overlay/src/ring.rs) renders the Actions Ring with the updated icon layout. If the user clicks outside the ring,spawn_click_away_dismissalinopenlogi-overlay/src/session.rshandles safe dismissal of the previous session.
Profile Configuration and Key Matching
Per-application overlays are stored in the same TOML configuration format as global bindings but reside in a separate section keyed by the application identifier. The configuration schema in openlogi-core/src/config.rs supports nested profiles that override specific actions while inheriting base bindings.
The profile key uses platform-specific stable identifiers to prevent collisions:
- macOS: Application
bundle_id(e.g.,com.apple.Safari) - Wayland:
app_idfrom the Wayland protocol - X11:
WM_CLASSstring or lower-cased executable path
This guarantees that a profile created for a specific application matches only that application, avoiding accidental cross-platform or cross-instance collisions.
Code Implementation Example
The following Rust implementation demonstrates the complete flow from IPC observation to effective binding generation:
// 1. Receive foreground application update via IPC
let apps: ForegroundApps = agent.observe().await?;
let current_app_id = apps.current.as_ref().map(|a| &a.id);
// 2. Resolve the per-application profile name from configuration
let profile_name = state.active_profile_name(); // Returns Option<&str>
// 3. Build effective bindings by overlaying per-app config onto globals
let effective_bindings = openlogi_core::bindings::effective_bindings(
&global_bindings,
per_app_overlays.get(profile_name),
);
// 4. Update the Actions Ring with the new layout
let ring_view = RingView::new(effective_bindings);
cx.update(|cx| cx.add_window(ring_view, window_geometry));
The effective_bindings function in openlogi-core/src/bindings.rs handles the merge logic, ensuring that per-application actions take precedence while unspecified actions retain their global defaults. The UI-level binding logic in openlogi-desktop/src/bindings.rs then processes these effective bindings to generate the final input map.
Summary
- Three-tier architecture: The Hook captures OS-level focus changes, the Agent distributes state via IPC, and the GUI applies visual overlays.
- Stable identifiers: Profile matching relies on platform-specific IDs (
bundle_id,app_id,WM_CLASS) to ensure accurate per-application targeting. - Dynamic binding overlay: The system merges global and per-application bindings through
effective_bindings(), with per-app configurations taking precedence. - Real-time updates: The
Agent::observeRPC stream enables instantaneous profile switching without manual intervention when the foreground window changes.
Frequently Asked Questions
How does OpenLogi detect which application is currently active?
OpenLogi uses platform-specific hooks that interface directly with the window server. On macOS, it utilizes CGEventTap; on Linux Wayland, it monitors wlroots or reads the app_id; and on X11, it examines the WM_CLASS property or executable path. These hooks run in the background and publish the foreground application identifier to the Agent via the ForegroundApp struct.
What happens if no per-application profile exists for the current app?
When active_profile_name() returns None or the application ID is not found in the per-application configuration, the system falls back to the global bindings. The effective_bindings() function in openlogi-core/src/bindings.rs handles this merge transparently, ensuring that users retain their base configuration while specific overrides apply only to registered applications.
Can multiple devices have different per-application profiles simultaneously?
Yes. The binding overlay system operates on a per-device basis within the global configuration. Each device maintains its own binding map, and the profile switching logic in openlogi-desktop/src/state.rs tracks the active profile independently for each connected device. When the foreground application changes, the GUI regenerates the binding map specific to the currently active device while respecting its unique per-application overrides.
How does the Actions Ring handle rapid application switching?
The overlay process implements session management through spawn_click_away_dismissal in openlogi-overlay/src/session.rs. When rapid switching occurs, the system safely dismisses the existing ring instance before rendering the new profile's Actions Ring. The IPC stream via Agent::observe ensures that the GUI receives updates in real-time, allowing the ring to reflect the new application's binding configuration immediately upon focus change.
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 →