Feeling every button on macOS: haptic feedback built on the accessibility tree

작성자

카테고리:

← 피드로
DEV Community · Mason Chen · 2026-07-13 개발(SW)

Mason Chen

Most macOS hover states are a small color change. If your eyes are tired, or your vision is low, or the button is a subtle ghost control, you can easily miss it. I wanted to feel when my cursor reached something clickable, the way a real switch has a detent. So I built Tactile, a menu bar app that taps the trackpad’s haptic motor whenever the pointer is over a clickable element, anywhere on the system.

The pipeline

The whole thing is event-driven:

  1. A CGEventTap watches cursor movement. When the mouse is still, nothing runs, so it costs 0% CPU.
  2. On movement, a throttled hit-test asks the macOS Accessibility tree what element is under the cursor. This is the same tree VoiceOver uses.
  3. A classifier decides whether that element is clickable (button, link, checkbox, menu item, tab, Dock item, and so on). Finder is a fun edge case: files and sidebar items expose an Open action rather than Press.
  4. If the cursor just entered a clickable element, the trackpad ticks once. Results are cached per element so continuous movement stays under a few percent of one core.

Why the accessibility tree

Reading the AX tree means Tactile learns only the kind of element under the pointer, never its content and never your keystrokes. It also means coverage is broad by default: native apps, Finder, browsers, and Electron all expose accessibility roles. The privacy story falls out of the design rather than being bolted on.

The haptics

The default engine is the public NSHapticFeedbackManager. There is also an optional engine that drives the trackpad actuator directly through a private framework, loaded at runtime with dlopen and a safe fallback. That unlocks real strength differences and continuous vibration by pacing pulses on a dedicated thread, fast enough to feel like a buzz rather than a series of taps.

The browser gap

Some web controls are divs with a click handler and cursor: pointer and no ARIA role, so they never reach the accessibility tree. For those, an optional Chrome extension reports element rectangles over a local Unix socket, and a shared fire-region memory dedupes the two paths so nothing double-fires.

Accessibility beyond haptics

Because it started as a low-vision aid, Tactile gives three independent signals for the same event: feel (the tick), color (a green circle over clickable, red over destructive), and shape (an outline around the control, plus a crosshair and a small labeled pill telling you the element type and where it came from).

Honest limitations

It needs a Force Touch trackpad, since that is the hardware with the haptic motor, and macOS 14.6 or later. It is not notarized yet, so there is a one-time quarantine step, and the full source is available to inspect or build. MIT licensed, and the only network call it makes is the Sparkle update check to its own repo.

Repo: https://github.com/Mason363/Tactile

원문에서 계속 ↗

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다