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
- Permission queries use
IOHIDCheckAccessviacrates/openlogi-permissions/src/macos.rsto check status without triggering dialogs. - Permission requests are executed by the agent (not the GUI) through
crates/openlogi-hid/src/permissions.rs, blocking the thread until the user responds. - Startup gating occurs in
crates/openlogi-agent/src/lifecycle.rsduring theWanted::arm()state transition. - Runtime monitoring is handled by
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.rsafter the user grants permission. - UI integration allows opening System Settings directly via
crates/openlogi-permissions/src/macos.rsusing 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, 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →