How OpenLogi Auto-Switches Per-Application Profile Overlays

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, 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, 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, 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 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 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 displays the updated Actions Ring, while spawn_click_away_dismissal in 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 stores per-app bindings in a separate section keyed by the app identifier. When resolved, 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:

// 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 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 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 overlays only the specified per-app bindings onto the global map, allowing selective overrides while maintaining global shortcuts for unmapped keys.

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 →