How OpenLogi Handles macOS Input Monitoring and Accessibility Permissions (TCC)

OpenLogi manages macOS TCC (Transparency, Consent, and Control) permissions through a dedicated permissions crate that queries IOHID access states, isolates the agent process for distinct TCC identity, monitors permission changes via polling watchers, and implements a lifecycle watchdog to recover from accessibility tap stalls caused by permission revocation.

OpenLogi is an open-source input automation framework that deeply integrates with macOS privacy controls. Handling macOS input monitoring and accessibility permissions (TCC) is central to its runtime architecture, ensuring the agent process can capture and inject input events while respecting user consent. The implementation spans multiple crates, from low-level system queries to user-facing recovery mechanisms.

Querying TCC Permissions Without Prompts

The openlogi-permissions crate provides non-prompting APIs that interface directly with macOS frameworks to determine current authorization states without triggering system dialogs.

Input Monitoring Detection

To check Input Monitoring (listen-event) permission, OpenLogi calls IOHIDCheckAccess with IOHIDRequestType::ListenEvent in openlogi-permissions/src/macos.rs. The function maps the raw IOHIDAccessType to a custom PermissionStatus enum:

// openlogi-permissions/src/macos.rs
pub fn input_monitoring() -> PermissionStatus {
    match IOHIDCheckAccess(IOHIDRequestType::ListenEvent) {
        IOHIDAccessType::Granted => PermissionStatus::Granted,
        IOHIDAccessType::Denied  => PermissionStatus::Denied,
        _ => PermissionStatus::Unknown,
    }
}

Accessibility Grant Verification

Accessibility permission—required for the CGEventTap that injects mouse and keyboard events—is implicitly verified through the same IOHIDCheckAccess infrastructure. Because the event tap lives in the agent process, the binary must hold the Accessibility grant for the tap to function, making this check sufficient for both permission types.

Process Isolation for TCC Identity

OpenLogi achieves TCC isolation by launching the agent binary as its own TCC-responsible process. In openlogi-agent/src/services/ipc/launch.rs, the agent is executed directly rather than through the GUI process, giving it a distinct bundle identifier that macOS associates with Accessibility and Input-Monitoring grants.

// crates/openlogi-agent/src/services/ipc/launch.rs
// …launch the agent binary under its own TCC identity

This architectural decision ensures that permission prompts appear in the context of the agent (the entity requiring access), not the desktop GUI, and that macOS tracks consent separately for each component.

Real-Time Permission State Watching

A lightweight watcher implementation polls the permission status every few hundred milliseconds to detect changes. Located in openlogi-agent-core/src/watchers/input_monitoring.rs, the watcher uses openlogi_hid::permissions::has_access() (which wraps the input_monitoring() function) to stream state changes via an async channel:

// crates/openlogi-agent-core/src/watchers/input_monitoring.rs
pub fn spawn(period: Duration) -> mpsc::UnboundedReceiver<bool> {
    // macOS‑only watcher
}

This polling mechanism allows the system to react immediately when a user grants or revokes permissions through System Settings.

Handling TCC Revocation and Recovery

OpenLogi implements defensive measures against permission revocation, which can cause the CoreGraphics event tap to become "wedged" or unresponsive.

CGEventTap Watchdog Mechanism

In openlogi-hook/src/macos.rs, the CGEventTap runs in a dedicated thread monitored by a lifecycle watchdog. If TCC permission is revoked while the tap is active, CoreGraphics may stall the tap without error. The watchdog detects this state through has_accessibility() checks and force-exits the agent process, allowing the system to clear the stale tap and restore normal input handling:

// crates/openlogi-hook/src/macos.rs
// …watchdog thread exits the process when TCC revocation stalls the tap

Post-Grant Process Relaunch

After the user grants Input Monitoring permission, the agent typically requires a restart to acquire the new capability. The relaunch_after_input_monitoring_grant() function in openlogi-agent/src/binary_watch/relaunch.rs handles this transition:

// crates/openlogi-agent/src/binary_watch/relaunch.rs
pub fn relaunch_after_input_monitoring_grant() { /* … */ }

The lifecycle code invokes this function once the permission watcher detects a change from Denied to Granted.

User-Facing Permission Recovery

When the GUI detects missing Input Monitoring permission, it presents a settings item in openlogi-desktop/src/windows/settings/permissions.rs. Selecting this item calls openlogi_permissions::open_pane(permission), which constructs and opens the appropriate System Settings URL:

// crates/openlogi-permissions/src/macos.rs
pub fn open_pane(permission: Permission) {
    let anchor = match permission {
        Permission::Accessibility => "Privacy_Accessibility",
        Permission::InputMonitoring => "Privacy_ListenEvent",
        // …
    };
    let url = format!("x-apple.systempreferences:com.apple.preference.security?{anchor}");
    opener::open(&url).ok();
}

This deep-links directly to the privacy pane, eliminating user navigation friction.

Summary

  • Permission Queries: The openlogi-permissions crate uses IOHIDCheckAccess to check Input Monitoring and Accessibility status without prompting the user.
  • Process Isolation: The agent binary runs as its own TCC-responsible process in openlogi-agent/src/services/ipc/launch.rs, ensuring proper permission attribution.
  • Continuous Monitoring: A watcher in openlogi-agent-core/src/watchers/input_monitoring.rs polls permission states every few hundred milliseconds.
  • Revocation Safety: The hook implementation includes a watchdog that terminates the process if TCC revocation stalls the CGEventTap.
  • Deep Linking: The GUI can open exact System Settings panes using x-apple.systempreferences URLs to guide users through granting access.

Frequently Asked Questions

How does OpenLogi check if it has Input Monitoring permission without showing a system dialog?

OpenLogi calls the low-level macOS framework function IOHIDCheckAccess(IOHIDRequestType::ListenEvent) through the input_monitoring() function in openlogi-permissions/src/macos.rs. This API returns the current authorization status (Granted, Denied, or Unknown) without triggering a permission prompt, allowing the application to check state silently.

Why does the OpenLogi agent run as a separate process from the GUI?

The agent binary is launched as its own TCC-responsible process in openlogi-agent/src/services/ipc/launch.rs to obtain a distinct bundle identifier and TCC identity. This isolation ensures that macOS associates Accessibility and Input Monitoring grants specifically with the agent process (which performs the sensitive input operations) rather than the GUI, providing clearer permission boundaries and more reliable consent management.

What happens if a user revokes Accessibility permission while OpenLogi is running?

If TCC permission is revoked, the CGEventTap managed by openlogi-hook/src/macos.rs can become wedged inside CoreGraphics. A lifecycle watchdog thread monitors the tap's health via accessibility checks; upon detecting the stall, it force-exits the agent process. This clears the stale tap and allows the system to restore normal input functionality, after which the user must relaunch and re-authorize the application.

How does OpenLogi relaunch after the user grants permission?

Once the permission watcher detects a state change from Denied to Granted, it triggers relaunch_after_input_monitoring_grant() from openlogi-agent/src/binary_watch/relaunch.rs. This function restarts the agent binary, allowing it to acquire the newly granted Input Monitoring capability and initialize the CGEventTap with proper authorization.

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 →