How OpenLogi Requests and Manages macOS Input Monitoring Permissions

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 (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 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 (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 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 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 (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, 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:

// 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"),
}
// 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();
});
// 3. Open the System Settings pane from the UI
use openlogi_permissions::open_pane;

open_pane(Permission::InputMonitoring);

Summary

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, 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.

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 →