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

> Learn how OpenLogi manages macOS TCC permissions for input monitoring. Discover its approach to IOHID access, agent isolation, and permission change monitoring for seamless accessibility.

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

---

**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`](https://github.com/AprilNEA/OpenLogi/blob/main/openlogi-permissions/src/macos.rs). The function maps the raw `IOHIDAccessType` to a custom `PermissionStatus` enum:

```rust
// 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`](https://github.com/AprilNEA/OpenLogi/blob/main/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.

```rust
// 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`](https://github.com/AprilNEA/OpenLogi/blob/main/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:

```rust
// 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`](https://github.com/AprilNEA/OpenLogi/blob/main/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:

```rust
// 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`](https://github.com/AprilNEA/OpenLogi/blob/main/openlogi-agent/src/binary_watch/relaunch.rs) handles this transition:

```rust
// 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`](https://github.com/AprilNEA/OpenLogi/blob/main/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:

```rust
// 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`](https://github.com/AprilNEA/OpenLogi/blob/main/openlogi-agent/src/services/ipc/launch.rs), ensuring proper permission attribution.
- **Continuous Monitoring**: A watcher in [`openlogi-agent-core/src/watchers/input_monitoring.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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.