# macOS TCC Permission Checks for Input Monitoring and Accessibility in OpenLogi

> Discover macOS TCC permission checks for Input Monitoring and Accessibility in OpenLogi. Learn how OpenLogi secures HID devices and mouse hooks with runtime checks.

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

---

**OpenLogi requires macOS Input Monitoring permission to open HID devices via `IOHIDManager` and Accessibility permission to create `CGEventTap` mouse hooks, with runtime checks implemented in [`crates/openlogi-hid/src/permissions.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hid/src/permissions.rs) and error handling in [`crates/openlogi-hook/src/macos.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/macos.rs).**

OpenLogi is an open-source HID++ device manager that interacts with low-level macOS APIs. To function correctly, it must secure two distinct TCC (Transparency, Consent, and Control) entitlements: Input Monitoring for hardware communication and Accessibility for event interception. These macOS TCC permission checks for Input Monitoring and Accessibility in OpenLogi are implemented across multiple crates to separate concerns between the GUI, agent, and hardware abstraction layers.

## Input Monitoring Permission for HID Device Access

### Why Input Monitoring Is Required

The Input Monitoring permission (internally referred to as "Listen Event" access) is mandatory because the HID++ transport layer opens low-level HID devices through `IOHIDManager`. macOS blocks every `IOHIDDeviceOpen` call unless the process holds the Input Monitoring consent.

### Checking Permission Status

According to the OpenLogi source code, the current authorization state is queried in [`crates/openlogi-hid/src/permissions.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hid/src/permissions.rs). The `has_access()` function wraps `IOHIDCheckAccess(IOHIDRequestType::ListenEvent)` and returns a boolean.

The non-prompting status is also exposed as `PermissionStatus` in [`crates/openlogi-permissions/src/macos.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-permissions/src/macos.rs) via the `input_monitoring()` function.

```rust
// crates/openlogi-hid/src/permissions.rs
pub(super) fn has_access() -> bool {
    matches!(
        IOHIDCheckAccess(IOHIDRequestType::ListenEvent),
        IOHIDAccessType::Granted
    )
}

```

### Requesting Access Programmatically

To prompt the user, OpenLogi calls `IOHIDRequestAccess(IOHIDRequestType::ListenEvent)` through the `request_access()` wrapper. This blocks the calling thread until the user responds to the modal dialog, so it must be executed off the async runtime using `tokio::task::spawn_blocking`.

```rust
use openlogi_hid::permissions;

if !permissions::has_access() {
    // Request dialog blocks; run in a dedicated thread
    std::thread::spawn(|| {
        permissions::request_access();
    }).join().unwrap();
}

```

## Accessibility Permission for Event Taps

### Why Accessibility Is Required

The mouse-button remapping hook relies on a `CGEventTap` to receive low-level mouse events. Without the Accessibility grant, `CGEventTapCreate` fails, and the agent cannot start the hook.

### Detecting Denied Access in Code

The hook installation logic resides in [`crates/openlogi-hook/src/macos.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/macos.rs). When `CGEventTapCreate` returns `null`, the implementation produces `HookError::AccessibilityDenied` (see line 989).

The current trust state can also be observed via the watcher in [`crates/openlogi-agent-core/src/watchers/accessibility.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent-core/src/watchers/accessibility.rs).

### Triggering the System Prompt

Unlike Input Monitoring, the Accessibility prompt is triggered via inter-process communication. The desktop GUI sends `Command::RequestAccessibilityPrompt` (defined in [`crates/openlogi-desktop/src/services/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/services/ipc.rs)) to the agent ([`crates/openlogi-agent/src/main.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent/src/main.rs)). The agent then attempts hook installation by calling `openlogi_hook::install()`, which causes macOS to display the system dialog for the **OpenLogi Agent** process.

## Opening System Settings Directly

For both permissions, OpenLogi provides a utility to open the specific System Settings pane without manual navigation. The `open_pane()` function in [`crates/openlogi-permissions/src/macos.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-permissions/src/macos.rs) constructs the appropriate `x-apple.systempreferences` URL.

```rust
use openlogi_permissions::{Permission, open_pane};

// Open Input Monitoring settings
open_pane(Permission::InputMonitoring);

// Open Accessibility settings  
open_pane(Permission::Accessibility);

```

The implementation maps permission variants to their respective anchors:

- `Permission::Accessibility` maps to `"Privacy_Accessibility"`
- `Permission::InputMonitoring` maps to `"Privacy_ListenEvent"`

## Practical Implementation Example

Here is a complete pattern for verifying both permissions before initializing OpenLogi components:

```rust
use openlogi_hid::permissions as hid_perm;
use openlogi_permissions::{Permission, open_pane};

/// Verify Input Monitoring and request if missing
async fn ensure_input_monitoring() {
    if !hid_perm::has_access() {
        // Spawn blocking task for the modal dialog
        tokio::task::spawn_blocking(|| {
            hid_perm::request_access();
        }).await.unwrap();
        
        // Offer direct link to settings
        open_pane(Permission::InputMonitoring);
    }
}

/// Request Accessibility via IPC to the agent process
fn trigger_accessibility_prompt() {
    // Desktop sends Command::RequestAccessibilityPrompt to agent
    // Agent executes openlogi_hook::install() to trigger dialog
    open_pane(Permission::Accessibility);
}

```

## Summary

- **Input Monitoring** is verified via `IOHIDCheckAccess` in [`crates/openlogi-hid/src/permissions.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hid/src/permissions.rs) and is required for all `IOHIDDeviceOpen` operations.
- **Accessibility** is validated by attempting `CGEventTapCreate` in [`crates/openlogi-hook/src/macos.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/macos.rs), which returns `HookError::AccessibilityDenied` (line 989) if the grant is missing.
- Input Monitoring requests block the calling thread and must run inside `spawn_blocking` contexts to avoid freezing the async runtime.
- Accessibility prompts originate from the agent process via IPC commands from the desktop GUI, ensuring the dialog appears for the correct binary.
- Both permission panes can be opened programmatically using `open_pane()` defined in [`crates/openlogi-permissions/src/macos.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-permissions/src/macos.rs).

## Frequently Asked Questions

### What happens if Input Monitoring is denied?

If Input Monitoring is denied, all `IOHIDDeviceOpen` calls will fail, preventing OpenLogi from communicating with HID++ devices. The `has_access()` function returns `false`, allowing the application to detect the missing permission and direct the user to System Settings before attempting hardware initialization.

### Why does Accessibility require an IPC call to the agent?

The Accessibility prompt must originate from the process that creates the event tap. In OpenLogi's architecture, the agent process (`openlogi-agent`) owns the hook logic in [`crates/openlogi-hook/src/macos.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/macos.rs), while the desktop GUI runs as a separate process. The desktop sends `Command::RequestAccessibilityPrompt` via IPC so the agent can attempt `CGEventTapCreate`, which triggers the system dialog for the OpenLogi Agent process specifically.

### Can these permissions be checked without triggering a dialog?

Yes. `IOHIDCheckAccess` for Input Monitoring and the success of `CGEventTapCreate` for Accessibility check the current authorization status silently. Only explicit calls to `request_access()` or attempts to install the hook trigger the modal system dialogs.

### Where are the permission status watchers located?

Real-time Accessibility status is monitored by the watcher in [`crates/openlogi-agent-core/src/watchers/accessibility.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent-core/src/watchers/accessibility.rs). Input Monitoring status is queried on-demand through the `PermissionStatus` enum exposed in [`crates/openlogi-permissions/src/macos.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-permissions/src/macos.rs) via the `input_monitoring()` function.