# How OpenLogi Requests and Manages macOS Input Monitoring Permissions

> Discover how OpenLogi expertly requests and manages macOS input monitoring permissions. Learn about its layered architecture for seamless access and user experience.

- Repository: [Xuan Zhang/OpenLogi](https://github.com/AprilNEA/OpenLogi)
- Tags: how-to-guide
- Published: 2026-09-03

---

**OpenLogi uses a layered permission architecture that queries status via IOKit’s `IOHIDCheckAccess`, requests access through a blocking `IOHIDRequestAccess` call in the agent process, monitors changes through periodic polling, and automatically relaunches to bind the new TCC identity when granted.**

OpenLogi is an open-source input device management framework written in Rust. Understanding how OpenLogi requests and manages macOS Input Monitoring permissions requires examining its multi-layered approach to **Transparency, Consent, and Control (TCC)**, where the agent process—not the GUI—handles permission requests to ensure proper code-signing identity binding.

## Querying Input Monitoring Status Without Prompting

To read the current grant state without triggering a system dialog, OpenLogi leverages IOKit’s `IOHIDCheckAccess` function. In [`crates/openlogi-permissions/src/macos.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-permissions/src/macos.rs) (lines 22-30), the `input_monitoring()` function calls `IOHIDCheckAccess(IOHIDRequestType::ListenEvent)` and maps the result to the `PermissionStatus` enum with variants `Granted`, `Denied`, or `Unknown`.

This query mechanism is used by both the GUI and the CLI—for example, [`crates/openlogi-cli/src/cmd/list.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-cli/src/cmd/list.rs) checks the agent’s permission status before attempting to enumerate devices.

## Requesting Permission Through the Agent Process

Unlike the GUI, the **agent** process performs the actual permission request because macOS TCC binds the grant to the code-signing identity of the process that opens HID devices. The request blocks the calling thread until the user responds to the system consent dialog.

### The Blocking Nature of macOS Input Monitoring Requests

In [`crates/openlogi-hid/src/permissions.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hid/src/permissions.rs) (lines 26-33), the `request_access()` function executes `IOHIDRequestAccess(IOHIDRequestType::ListenEvent)`. Because this call blocks indefinitely, the agent wraps it in `tokio::task::spawn_blocking` to prevent freezing the async runtime.

### Agent Startup and the Wanted::arm() State

The agent lifecycle in [`crates/openlogi-agent/src/lifecycle.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent/src/lifecycle.rs) implements a state machine that gates permission acquisition during startup. When the machine reaches `Wanted::arm()` (around lines 250-264), it invokes `request_input_monitoring()`, which checks `openlogi-hid::has_access()` and calls the blocking `request_access()` if the permission is missing.

## Monitoring Permission Changes at Runtime

OpenLogi does not require manual restarts when users toggle permissions in System Settings. The `input_monitoring` watcher in [`crates/openlogi-agent-core/src/watchers/input_monitoring.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent-core/src/watchers/input_monitoring.rs) periodically polls `openlogi-permissions::input_monitoring()`. When the status changes, it publishes an `ObservableState::InputMonitoringChanged` event, causing the agent to restart the HID inventory or pause device access if the permission was revoked.

## Opening System Settings from the UI

While the GUI never requests permission directly, it provides an "Open Settings" button that calls `openlogi-permissions::open_pane(Permission::InputMonitoring)`. Located in [`crates/openlogi-permissions/src/macos.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-permissions/src/macos.rs) (lines 62-79), this constructs an `x-apple.systempreferences:` URL targeting the `Privacy_ListenEvent` pane and launches it via the `opener` crate.

## Handling TCC Identity Binding Through Relaunch

When a new grant is obtained, the agent must relaunch to apply the new TCC identity to its running process. The relaunch logic resides in [`crates/openlogi-agent/src/binary_watch/relaunch.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent/src/binary_watch/relaunch.rs), ensuring the process re-executes under the newly granted permission context.

## Complete Implementation Example

The following examples demonstrate the three core permission operations in OpenLogi:

```rust
// 1. Query the current status (used by the GUI)
use openlogi_permissions::macos::{input_monitoring, Permission, PermissionStatus};

let status = input_monitoring();
match status {
    PermissionStatus::Granted => println!("✅ Input Monitoring granted"),
    PermissionStatus::Denied => println!("❌ Input Monitoring denied"),
    PermissionStatus::Unknown => println!("⚠️ Unknown – may need a prompt"),
}

```

```rust
// 2. Prompt the user (called from the agent at startup)
use openlogi_hid::request_access;

tokio::task::spawn_blocking(|| {
    // This blocks the thread until the consent dialog is answered
    request_access();
});

```

```rust
// 3. Open the System Settings pane from the UI
use openlogi_permissions::open_pane;

open_pane(Permission::InputMonitoring);

```

## Summary

- **Permission queries** use `IOHIDCheckAccess` via [`crates/openlogi-permissions/src/macos.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-permissions/src/macos.rs) to check status without triggering dialogs.
- **Permission requests** are executed by the agent (not the GUI) through [`crates/openlogi-hid/src/permissions.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hid/src/permissions.rs), blocking the thread until the user responds.
- **Startup gating** occurs in [`crates/openlogi-agent/src/lifecycle.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent/src/lifecycle.rs) during the `Wanted::arm()` state transition.
- **Runtime monitoring** is handled by [`crates/openlogi-agent-core/src/watchers/input_monitoring.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent-core/src/watchers/input_monitoring.rs), which polls for changes and emits state update events.
- **TCC identity binding** requires a process relaunch via [`crates/openlogi-agent/src/binary_watch/relaunch.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent/src/binary_watch/relaunch.rs) after the user grants permission.
- **UI integration** allows opening System Settings directly via [`crates/openlogi-permissions/src/macos.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-permissions/src/macos.rs) using Apple-specific URL schemes.

## Frequently Asked Questions

### Why does the agent request permission instead of the GUI?

The agent requests permission because macOS TCC binds the Input Monitoring grant to the code-signing identity of the process that actually opens HID devices. Since the agent is the process that calls `IOHIDRequestAccess(IOHIDRequestType::ListenEvent)`, requesting from the GUI would result in a permission grant that does not apply to the agent's device operations.

### What happens if the user denies Input Monitoring permission?

If the user denies the permission, `IOHIDRequestAccess` returns without granting access, and the agent remains in a state where HID device access is blocked. The permission watcher continues polling, so if the user later enables the permission in System Settings, the agent detects the change via `ObservableState::InputMonitoringChanged` and automatically resumes device access without requiring a manual restart.

### How does OpenLogi detect permission changes without restarting?

OpenLogi detects changes through the `input_monitoring` watcher in [`crates/openlogi-agent-core/src/watchers/input_monitoring.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent-core/src/watchers/input_monitoring.rs), which periodically polls `openlogi-permissions::input_monitoring()`. When the status changes from `Denied` to `Granted` (or vice versa), the watcher publishes an event that triggers the agent to restart its HID inventory or pause operations accordingly.

### Why does the agent relaunch after receiving permission?

The agent relaunches because macOS applies TCC grants to the specific code-signing identity of the requesting process. After the user grants permission, the running agent process must restart to have the new TCC identity applied to its execution context. This relaunch is handled by [`crates/openlogi-agent/src/binary_watch/relaunch.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent/src/binary_watch/relaunch.rs).