Track UI Events and Network Activity in macOS Using Rust + SwiftUI
Stephen Collins

Stephen Collins @stephenc222

About: Senior Software Engineer. Sharing everything I’ve learned in tech.

Location:
Austin, Texas
Joined:
Sep 6, 2018

Track UI Events and Network Activity in macOS Using Rust + SwiftUI

Publish Date: Apr 13
0 0

Blog Post Cover Image

Most macOS apps are black boxes. You click a button, something happens—maybe a network request, maybe some system interaction—but it's hard to know what unless you own the source code.

So I built a tool to watch.

This post walks through a hybrid SwiftUI + Rust project that logs every button click and tracks network traffic tied to the clicked app. It combines:

  • A SwiftUI frontend to simulate normal GUI interaction
  • A Rust backend using CGEventTap and the macOS Accessibility API
  • Per-process network inspection using the undocumented but powerful nettop

No kernel extensions. No root. Just clever usage of system APIs and a bit of FFI.

The full source code is available here.


Why build this?

Because reverse-engineering app behavior shouldn't require a debugger or Wireshark session. I wanted to:

  • Trace which UI elements trigger which network activity
  • Understand what apps do in response to input
  • Have a working reference for macOS Accessibility APIs in Rust

The Windows version of this tool relied on Win32 hooks and low-level TCP inspection via GetExtendedTcpTable.
You can read that walkthrough here →

This macOS build takes the same spirit to Apple's ecosystem—same goals, different APIs.


Demo: From Click to Packet

When a user clicks Button A, here's what gets logged:

📡 example-mac-app.47727 ↑ 6092 B ↓ 0 B (Δ ↑ 6092 ↓ 0)
[INFO] Button Clicked: App='example-mac-app', PID=47727, ID='ButtonA', Label='Button A'
Enter fullscreen mode Exit fullscreen mode

You get:

  • The app name
  • The PID
  • The element's accessibility label and identifier
  • A delta of bytes sent/received from that process (captured via nettop)

Architecture Overview

+----------------------+       +-----------------------+
|   SwiftUI Mac App    | --->  |   Rust macos-watcher  |
| (3 Buttons + Network)|       | (Event Tap + nettop)  |
+----------------------+       +-----------------------+
              ⬑ Accessibility API (AXUIElement)
                        ⬐ CGEventTap mouse/keyboard
Enter fullscreen mode Exit fullscreen mode

Frontend: SwiftUI Playground

The Xcode app has a simple interface with three buttons (ButtonA, ButtonB, ButtonC). When clicked, each makes a network request using URLSession.

Button("Button A") {
    fetchProduct(id: 1)
}
.accessibilityIdentifier("ButtonA")
Enter fullscreen mode Exit fullscreen mode

The important part is setting .accessibilityIdentifier() so we can extract metadata from the Rust process using the Accessibility API.


Backend: Rust Input + Network Logger

This is where the real work happens.

🔹 Event Hooking

We use CGEventTap to hook into global input events like left-clicks and key presses.

let event_mask = (1 << K_CG_EVENT_LEFT_MOUSE_DOWN) | (1 << K_CG_EVENT_KEY_DOWN);
let event_tap = CGEventTapCreate(..., event_mask, event_callback, ...);
Enter fullscreen mode Exit fullscreen mode

🔹 Accessibility API

On every click, we use AXUIElementCopyElementAtPosition to resolve which UI element is under the cursor. We extract:

  • PID of the owning app
  • Accessibility identifier (if set)
  • Role (e.g. AXButton)
  • Optional description or label
let result = ax_ui_element_copy_element_at_position(
    system_wide_element, x, y, &mut element_ref);
Enter fullscreen mode Exit fullscreen mode

Then:

let pid = ax_ui_element_get_pid(element_ref);
let role = ax_ui_element_copy_attribute_value(element_ref, K_AX_ROLE_ATTRIBUTE);
Enter fullscreen mode Exit fullscreen mode

🔹 Network Activity via nettop

After identifying the UI element and its app PID, we shell out to:

nettop -P -J bytes_in,bytes_out -x -l 1
Enter fullscreen mode Exit fullscreen mode

We parse the output for lines matching the PID (e.g. example-mac-app.47727), and log any change in byte counts. This gives us a primitive—but real—view into per-process network activity.


Log Output Example

📡 example-mac-app.47727 ↑ 6092 B ↓ 0 B (Δ ↑ 6092 ↓ 0)
[INFO] Button Clicked: App='example-mac-app', PID=47727, ID='ButtonA', Label='Button A'
Enter fullscreen mode Exit fullscreen mode

Each input event is logged with as much context as we can scrape. You can see which button triggered the traffic, and how much was sent/received.


Installation & Permissions

To run the watcher, build the Rust CLI and grant it accessibility permissions:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
cd macos-watcher
cargo build --release
./target/release/macos-watcher
Enter fullscreen mode Exit fullscreen mode

macOS will prompt you to allow the binary under:

System Settings → Privacy & Security → Accessibility

Once granted, you'll see logs at:

~/macos_watcher.log
Enter fullscreen mode Exit fullscreen mode

You can also run it persistently in the background using the included .plist and install_daemon.sh.


Dev Notes

  • Built using core-foundation, objc, and simplelog
  • All UI inspection is done using C-level bindings to AXUIElement* APIs
  • There's no NSAccessibility or AppKit dependency in the Rust half
  • SwiftUI code is sandboxed and declarative. No extra work needed to simulate real-world interaction

Use Cases

  • Security - See if random apps are sending packets when buttons are clicked
  • Testing - Ensure your app only hits the network when expected
  • Debugging - Tie UI behavior to system-level effects
  • Education - Learn how accessibility and input work on macOS

Limitations

  • Requires GUI permission to run (can't be run headless)
  • nettop output is undocumented and may change
  • Packet contents aren't inspected—this is about attribution, not introspection
  • Only tracks visible, clickable elements (not gestures, background jobs, etc.)

Cross-Platform Parity

We now have both Windows and macOS versions of this tool, each using the platform's native APIs:

Feature Windows macOS
Input Hooking Win32 Low-Level Hooks CGEventTap
UI Element Metadata UIAutomation / IAccessible AXUIElement
Network Tracking GetExtendedTcpTable nettop
Language Rust Rust + SwiftUI

Future Work

  • Add packet inspection (via libpcap) for HTTP tracing
  • Tag UI actions with timestamps to correlate more precisely with network
  • Export events in JSON or send to external observability tools

Closing Thoughts

This project taught me a lot about how macOS input and accessibility work under the hood. The fact that you can hook into system-wide mouse events, pull out UI metadata, and match it to network traffic—all with a user-space Rust binary—is kind of wild.

If you want to analyze how apps behave without reverse-engineering them from scratch, this tool gives you a solid start.

Code is available here.

Comments 0 total

    Add comment