macOS Accessibility API reference

accessibility-sys AXUIElementCreateSystemWide, decoded

The docs.rs page for accessibility-sys reports that 0% of the crate is documented. So you land on AXUIElementCreateSystemWide and get one line of FFI with no explanation. This page is the explanation: what the system-wide root returns, the one thing it cannot do, and the trust gate you hit before any of it works.

M
Matthew Diakonov
9 min read

Direct answer · verified 2026-06-16

In accessibility-sys (v0.2.0), AXUIElementCreateSystemWide is a zero-argument FFI binding that returns the macOS system-wide accessibility root. You use it to read the currently focused element and application, and to hit-test a screen coordinate. It does not let you enumerate a whole application's UI tree. For that, create a per-process element with AXUIElementCreateApplication(pid).

pub unsafe extern "C" fn AXUIElementCreateSystemWide() -> AXUIElementRef

Source: docs.rs/accessibility-sys and Apple's AXUIElement reference.

What the crate page actually tells you (and what it leaves out)

accessibility-sys is a binding crate, nothing more. Its job is to hand Rust the raw symbols from Apple's HIServices headers so you can call the C Accessibility API. The published facts are short and worth pinning down before you write a line:

  • Version 0.2.0, authored by eiz, dual licensed MIT or Apache-2.0.
  • One dependency: core-foundation-sys ^0.8. It targets Apple Darwin.
  • docs.rs reports 0% of the crate is documented, which is why every symbol renders as a bare signature.

That last point is the trap. The signatures are correct, but a signature does not tell you that the system-wide element behaves completely differently from an application element, or that it returns a usable ref even when your process has no Accessibility permission and every later call fails. The behavior lives in Apple's C semantics, not in the Rust doc string. Below is the part the rendered docs cannot give you.

The four symbols you almost always need together

Reaching for AXUIElementCreateSystemWide alone is rare. In practice you call it next to three siblings: the per-app constructor, the attribute reader, and the trust check. Here are the real signatures as exported by accessibility-sys.

// The system-wide root: no arguments.
pub unsafe extern "C" fn AXUIElementCreateSystemWide() -> AXUIElementRef;

// A single application's root: takes its process id.
pub unsafe extern "C" fn AXUIElementCreateApplication(pid: pid_t) -> AXUIElementRef;

// Read one attribute (e.g. kAXFocusedUIElementAttribute) off any element.
pub unsafe extern "C" fn AXUIElementCopyAttributeValue(
    element: AXUIElementRef,
    attribute: CFStringRef,
    value: *mut CFTypeRef,
) -> AXError;

// Is this process trusted for Accessibility? Pass kAXTrustedCheckOptionPrompt
// in the options dictionary to surface the System Settings prompt.
pub unsafe extern "C" fn AXIsProcessTrustedWithOptions(
    options: CFDictionaryRef,
) -> bool;

The system-wide constructor takes nothing; the application constructor takes a pid_t. That single difference is the whole mental model.

System-wide element vs application element

The two constructors look similar and behave nothing alike. Pick by the question you are asking, not by which name you remember.

FeatureCreateSystemWide()CreateApplication(pid)
ConstructorAXUIElementCreateSystemWide()AXUIElementCreateApplication(pid: pid_t)
ArgumentsNoneOne process id (pid_t)
What it points atThe system-wide root, not tied to any one appThe root of a single running application
Read the focused elementYes, via kAXFocusedUIElementAttributeOnly the focus inside that app
Hit-test by screen coordinateYes, via AXUIElementCopyElementAtPositionScoped to that app's windows
Walk the whole UI tree (AXChildren)No, the system-wide root does not expose app childrenYes, this is how you enumerate windows and controls
Typical useGlobal focus tracking, pointer hit-testing, system attributesInspecting and driving a specific target application

Both return AXUIElementRef and both follow the Create Rule, so you own and must CFRelease whatever they hand back.

What the system-wide root is genuinely good for

Keep the system-wide element for cross-application questions. The moment you want to read a specific window or click a specific button, switch to a per-pid element.

Valid uses of AXUIElementCreateSystemWide

  • Read the focused UI element across every app via kAXFocusedUIElementAttribute
  • Read the focused application via kAXFocusedApplicationAttribute
  • Hit-test a screen coordinate with AXUIElementCopyElementAtPosition
  • Query a handful of system-level attributes that are not app-scoped
  • Walk a full application's window and control tree
  • Enumerate every open window on the desktop in one call

The order the calls actually happen in

A working session is never just one function. This is the sequence that produces a real element instead of a permission error.

1

Check trust first

Call AXIsProcessTrustedWithOptions with kAXTrustedCheckOptionPrompt set true. If the process is not trusted, macOS prompts the user and every later attribute read returns kAXErrorAPIDisabled.

2

Create the right root

AXUIElementCreateSystemWide() for focus and pointer questions, or AXUIElementCreateApplication(pid) when you want a specific app's tree.

3

Copy the attribute you want

AXUIElementCopyAttributeValue with a CFString like kAXFocusedUIElementAttribute. Check the returned AXError; do not assume success.

4

Recurse or hit-test

From an app element, read kAXWindowsAttribute then kAXChildrenAttribute to walk down. From the system-wide element, hit-test with AXUIElementCopyElementAtPosition instead.

5

Release what you created

Every ref whose constructor name contains Create or Copy is yours to CFRelease. The safe accessibility crate does this through Drop so you never miss one.

A minimal read of the focused element

Here is the smallest honest example with the raw accessibility-sys surface: gate on trust, create the system-wide root, copy the focused element. The unsafe is unavoidable because these are C bindings.

use accessibility_sys::{
    kAXFocusedUIElementAttribute, AXIsProcessTrustedWithOptions,
    AXUIElementCopyAttributeValue, AXUIElementCreateSystemWide,
};
use core_foundation::base::TCFType;
use core_foundation::string::CFString;
use std::ptr;

unsafe {
    // 1. No trust, no elements. Pass an empty options dict here for brevity;
    //    pass kAXTrustedCheckOptionPrompt = true to show the system prompt.
    if !AXIsProcessTrustedWithOptions(ptr::null()) {
        eprintln!("not trusted for Accessibility yet");
        return;
    }

    // 2. The zero-argument root.
    let system_wide = AXUIElementCreateSystemWide();

    // 3. Ask it for whatever currently has focus, anywhere on screen.
    let attr = CFString::new(kAXFocusedUIElementAttribute);
    let mut focused: core_foundation::base::CFTypeRef = ptr::null();
    let err = AXUIElementCopyAttributeValue(
        system_wide,
        attr.as_concrete_TypeRef(),
        &mut focused,
    );

    println!("AXError = {err}, got focused = {}", !focused.is_null());
    // CFRelease(system_wide) and CFRelease(focused) when done.
}

Most code should not look like that. The safe accessibility crate (also by eiz) wraps the same call so you skip the raw pointers and the manual release:

use accessibility::{AXUIElement, AXAttribute};

let system_wide = AXUIElement::system_wide();
let focused = system_wide
    .attribute(&AXAttribute::focused_uielement());
// Drop releases the underlying AXUIElementRef for you.

// And for a specific app, by pid:
let app = AXUIElement::application(pid);
let windows = app.attribute(&AXAttribute::windows());

From four FFI calls to an automation framework

Reading the focused element is the easy 5%. The hard 95% is everything a real automation tool has to wrap around these bindings: gating and re-checking trust, decoding every AX value type, retrying flaky attribute reads, mapping AX roles to something stable, and giving you a way to find an element by name or role instead of walking children by hand. On Windows the same shape repeats with UI Automation instead of the AX API. The primitives differ; the work is identical.

That wrapper is what Terminator is. It is a desktop automation framework built on native accessibility APIs, shaped like Playwright but pointed at the whole operating system rather than the browser. You write a selector, Terminator resolves it against the accessibility tree, and acts on the element. You do not call AXUIElementCopyAttributeValue in a loop, you do not hand-manage CFRelease, and you do not re-implement the trust dance on every project.

If you are reaching for accessibility-sys because you are building agent or computer-use tooling that has to drive real apps, that is the exact problem Terminator exists to take off your plate, including the MCP server that lets an AI assistant call these capabilities as a tool.

Building on the AX API and tired of the FFI?

Tell us what you are automating on the desktop and we will show you where Terminator already solved the tree-walking, trust, and selector problems.

Frequently asked questions

Why is the accessibility-sys docs.rs page almost empty?

Because the crate is a thin FFI binding layer. docs.rs reports 0% of accessibility-sys is documented, so each item renders as a raw signature with no doc comment. AXUIElementCreateSystemWide shows up as `pub unsafe extern "C" fn AXUIElementCreateSystemWide() -> AXUIElementRef` and nothing else. The semantics live in Apple's HIServices headers, not in the Rust crate. The crate is version 0.2.0, authored by eiz, dual licensed MIT or Apache-2.0, and depends on core-foundation-sys ^0.8.

What does AXUIElementCreateSystemWide actually return?

An AXUIElementRef pointing at the system-wide accessibility object. It is the entry point for things that are not tied to one application: the currently focused UI element across all apps (kAXFocusedUIElementAttribute), the focused application (kAXFocusedApplicationAttribute), and hit-testing a screen coordinate with AXUIElementCopyElementAtPosition. Ownership follows Core Foundation's Create Rule, so you are responsible for releasing it (CFRelease) when you are done.

Why can't I get an app's full UI tree from the system-wide element?

The system-wide root is not a parent of every application's accessibility tree. Asking it for kAXChildrenAttribute does not hand you every window on the desktop. To enumerate a specific app you call AXUIElementCreateApplication(pid) with that process's id, then read kAXWindowsAttribute and recurse through kAXChildrenAttribute from there. The system-wide element is for focus and pointer queries, not tree traversal.

Do I need accessibility permission before any of this works?

Yes. Before macOS hands back real elements, your process must be trusted for Accessibility. accessibility-sys exposes AXIsProcessTrustedWithOptions plus the kAXTrustedCheckOptionPrompt constant. Pass that key set to true in a CFDictionary and macOS shows the System Settings prompt. Until the user grants it, attribute reads return kAXErrorAPIDisabled and you get nothing useful, even though AXUIElementCreateSystemWide itself still returns a non-null ref.

What is the difference between accessibility-sys and the accessibility crate?

accessibility-sys is the raw -sys binding: unsafe extern C functions, AXUIElementRef pointers, manual CFRelease. The accessibility crate (also by eiz) is the safe wrapper on top of it. There you write AXUIElement::system_wide() and AXUIElement::application(pid), and you read attributes through AXAttribute and typed getters instead of calling AXUIElementCopyAttributeValue by hand. Most application code wants the wrapper; reach for -sys only when you need a function the wrapper has not surfaced.

Is the macOS AX API portable to Windows?

The concepts are, the bindings are not. macOS exposes the AX API (AXUIElement, AXAttribute, AX roles); Windows exposes UI Automation (UIAutomationCore, IUIAutomationElement, control patterns). Both model the screen as a tree of typed elements with names, roles, and values, which is why a single locator grammar can sit over both. But AXUIElementCreateSystemWide has no literal Windows twin. If you want one API across both, that abstraction is exactly the work a framework like Terminator does for you.

How do I release the element to avoid a leak?

AXUIElementCreateSystemWide follows the Core Foundation Create Rule: the name contains Create, so you own the returned reference. Call CFRelease on the AXUIElementRef when you are finished. The same applies to any AXUIElementRef you get back from AXUIElementCreateApplication or AXUIElementCopyElementAtPosition. The safe accessibility crate handles this for you through Rust's Drop, which is the main reason to prefer it over calling -sys directly.

terminatorDesktop automation SDK
© 2026 terminator. All rights reserved.

How did this page land for you?

React to reveal totals

Comments ()

Leave a comment to see what others are saying.

Public and anonymous. No signup.