# How OpenLogi's Foreground Application Watcher Detects and Switches Profiles on macOS, Windows, and Linux

> Learn how OpenLogi's foreground application watcher automatically detects and switches profiles on macOS, Windows, and Linux using platform-specific observers for seamless app management.

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

---

**OpenLogi uses platform-specific observers running inside the agent process to monitor foreground application changes, publishing updates to an IPC snapshot that the desktop UI consumes to automatically switch per-application profiles across macOS, Windows, and Linux.**

OpenLogi (AprilNEA/OpenLogi) implements a sophisticated foreground application watcher that enables automatic profile switching based on the currently focused window. The system relies on an **agent process** that monitors platform-specific windowing events and communicates state changes to the desktop UI through an IPC mechanism. This architecture ensures consistent cross-platform behavior while respecting each operating system's unique APIs for window management.

## Architecture of the Foreground Application Watcher

The system follows a unified data flow across all platforms: the agent detects changes, updates an IPC snapshot, and the desktop resolves the active profile.

### The Agent Process and IPC Snapshot

The **observer** runs inside the agent process and constructs a `ForegroundApp` struct containing the application identifier and display name. When the foreground changes, the agent updates `AgentSnapshot::foreground` defined in [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs). This snapshot acts as the single source of truth for the desktop UI.

### Profile Resolution in the Desktop UI

The desktop UI reads the `snapshot.foreground` field and invokes `state.set_foreground(foreground)` to update the internal state. It then resolves the active profile by matching `foreground.id` against the keys in the per-app profile map. If no match exists or no foreground app is reported, the system falls back to the default global profile.

## Platform-Specific Detection Mechanisms

Each platform implements a specialized backend that translates native windowing events into the common `ForegroundApp` abstraction.

### macOS: NSWorkspace Notifications

On macOS, the watcher registers a block with `NSWorkspaceDidActivateApplicationNotification` to receive callbacks whenever the user switches applications. The handler, implemented in [`crates/openlogi-hook/src/macos.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/macos.rs), extracts the **bundle identifier** and localized name from the `NSRunningApplication` object.

The key functions are:

- `watch_frontmost_application_activations` – Registers the notification observer
- `foreground_app_from_running_application` – Converts `NSRunningApplication` to `ForegroundApp`

The `ForegroundApp.id` becomes the bundle identifier, which the desktop matches against profile keys.

### Windows: WinEvent Hooks

The Windows implementation in [`crates/openlogi-hook/src/windows/foreground.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/windows/foreground.rs) installs a global **WinEvent hook** for `EVENT_SYSTEM_FOREGROUND`. The `ForegroundApplicationObserver` runs on a dedicated thread and posts a custom `WM_FOREGROUND_CHANGED` message when the callback fires.

Upon processing the message, the system calls `GetForegroundWindow()` to retrieve the foreground window handle and resolves its executable path. The `ForegroundApp.id` is set to the lower-cased full executable path, ensuring consistent matching regardless of filesystem casing.

Key components include:

- `WinEventHook` – Manages the global event hook
- `queue_snapshot` – Handles the thread-safe emission of foreground updates

### Linux: Multi-Backend Support

The Linux implementation in [`crates/openlogi-hook/src/linux.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/linux.rs) provides three backends to accommodate different display servers:

- **X11** – Uses the `WM_CLASS` property's class component via `X11Source::frontmost_app`
- **Wayland (wlroots)** – Listens to the **wlr-foreign-toplevel** protocol
- **GNOME Shell** – Queries the `org.gnome.Shell` DBus API

The `Backend::frontmost_app` method delegates to `FRONTMOST.frontmost_app()`, preferring the Wayland foreign-toplevel when available, falling back to X11 otherwise. The resulting `ForegroundApp.id` uses the class name (X11) or the toplevel's app-id (Wayland).

## Implementation Examples

The following Rust snippets demonstrate how the agent initializes the observer on each platform:

```rust
// macOS – setting up the observer (run inside the agent)
let observer = openlogi_hook::watch_frontmost_application_activations(|app_opt| {
    // `app_opt` is `Option<ForegroundApp>`
    agent_state.set_foreground(app_opt.unwrap_or_default());
});

```

```rust
// Windows – creating the observer (agent side)
let observer = openlogi_hook::windows::foreground::ForegroundApplicationObserver::new(
    move |app_opt| {
        agent_state.set_foreground(app_opt.unwrap_or_default());
    },
);

```

```rust
// Linux – querying the current foreground app (agent side)
if let Some(fg) = openlogi_hook::Backend::frontmost_app() {
    agent_state.set_foreground(fg);
}

```

## Summary

- The **agent process** hosts platform-specific observers that detect foreground application changes using native APIs.
- **macOS** uses `NSWorkspaceDidActivateApplicationNotification` to capture bundle identifiers when apps activate.
- **Windows** installs a global WinEvent hook for `EVENT_SYSTEM_FOREGROUND` and resolves the executable path from the foreground window.
- **Linux** supports X11, Wayland (wlroots), and GNOME Shell backends, selecting the best available at runtime.
- The agent publishes changes to `AgentSnapshot::foreground` in [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs).
- The **desktop UI** consumes the IPC snapshot and matches `foreground.id` against per-app profile keys, falling back to the default profile when necessary.

## Frequently Asked Questions

### How does OpenLogi determine which profile to activate when switching applications?

The desktop UI reads the `foreground` field from the `AgentSnapshot` and compares the `ForegroundApp.id` against the keys in the per-application profile configuration. When a match is found, it activates that profile; otherwise, it uses the default global profile.

### What happens on Linux if multiple desktop environments are detected?

The Linux backend in [`crates/openlogi-hook/src/linux.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/linux.rs) prioritizes the Wayland foreign-toplevel protocol if available. If the compositor does not support it, the system falls back to X11's `WM_CLASS` detection, ensuring compatibility across different desktop environments.

### Why does the Windows implementation use a lower-cased executable path as the identifier?

The Windows observer lower-cases the full executable path to ensure consistent profile matching regardless of filesystem casing variations. This prevents profile lookup failures caused by path capitalization differences between the file system and user configuration.

### Can the foreground detection run without the agent process?

No. According to the source architecture, the platform-specific observers (`watch_frontmost_application_activations`, `ForegroundApplicationObserver`, and `Backend::frontmost_app`) execute inside the agent process and publish updates via IPC. The desktop UI relies entirely on the `AgentSnapshot` provided by the agent to determine the current foreground application.