macOS TCC Permission Checks for Input Monitoring and Accessibility in OpenLogi
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 and error handling in 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. 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 via the input_monitoring() function.
// 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.
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. 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.
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) to the agent (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 constructs the appropriate x-apple.systempreferences URL.
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::Accessibilitymaps to"Privacy_Accessibility"Permission::InputMonitoringmaps to"Privacy_ListenEvent"
Practical Implementation Example
Here is a complete pattern for verifying both permissions before initializing OpenLogi components:
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
IOHIDCheckAccessincrates/openlogi-hid/src/permissions.rsand is required for allIOHIDDeviceOpenoperations. - Accessibility is validated by attempting
CGEventTapCreateincrates/openlogi-hook/src/macos.rs, which returnsHookError::AccessibilityDenied(line 989) if the grant is missing. - Input Monitoring requests block the calling thread and must run inside
spawn_blockingcontexts 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 incrates/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, 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. Input Monitoring status is queried on-demand through the PermissionStatus enum exposed in crates/openlogi-permissions/src/macos.rs via the input_monitoring() function.
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 →