Skip to content

mrkai77/Subsurface

Repository files navigation

Subsurface

Raw multitouch, gestures, and haptics for macOS.

Subsurface gives Swift apps direct access to the Mac's multitouch devices. It wraps Apple's private MultitouchSupport.framework in a friendlier API for reading raw contact frames, tracking connected trackpads, recognizing pan/pinch/rotation gestures, and triggering Force Touch haptics.

Installation

To add Subsurface to your Xcode project, you can use Swift Package Manager (SPM). Follow these steps:

  1. Open your project in Xcode.
  2. Go to File > Add Package Dependencies....
  3. Enter this URL: https://github.com/mrkai77/Subsurface
  4. Add the Subsurface library to your target.

Subsurface currently targets macOS 13 and later.

Usage

The examples below show the main layers of the package. You can work with a single device directly, listen globally through a monitor, feed contact frames into a gesture recognizer, or use the trackpad actuator for haptic feedback.

Reading Contacts From One Device

import Subsurface

guard let device = SubsurfaceDevice.defaultDevice else {
    return
}

device.start()

for await contacts in device.contactFrames() {
    for contact in contacts {
        let position = contact.normalizedVector.position
        print("finger \(contact.id): \(position.x), \(position.y)")
    }
}

MTContact exposes the contact state, finger and hand classification, normalized position, velocity, pressure, angle, and touch ellipse size. The values come directly from the underlying multitouch stream, so you can decide how much filtering or interpretation you want.

Monitoring Every Trackpad

import Subsurface

let monitor = SubsurfaceMonitor()
monitor.start()

for await (device, contacts) in monitor.contacts() {
    print("\(device.name): \(contacts.count) contacts")
}

The monitor watches IOKit for multitouch devices as they appear and disappear. It is useful if you want to support built-in trackpads and Magic Trackpads without asking the caller to pick a device up front.

Recognizing Gestures

import Subsurface

let monitor = SubsurfaceMonitor()
let recognizer = SubsurfaceGestureRecognizer(fingerCount: 2)

monitor.start()

for await event in recognizer.events(from: monitor) {
    switch event {
    case let .pan(pan):
        print("pan: \(pan.translation), velocity: \(pan.velocity)")

    case let .pinch(pinch):
        print("pinch: \(pinch.distance) from \(pinch.originDistance)")

    case let .rotation(rotation):
        print("rotation: \(rotation.rotation) radians")

    case let .determining(centroid, fingerCount):
        print("waiting on \(fingerCount) fingers at \(centroid)")

    case let .unresolvedEnded(reason):
        print("gesture ended before resolving: \(reason)")
    }
}

The recognizer starts in a determining phase, then locks onto the first gesture that crosses its threshold. Pinch wins first, then rotation, then pan. Once a gesture begins, it keeps tracking while at least two fingers remain on the surface, which matches the sticky feel of macOS system gestures.

You can tune the thresholds directly:

recognizer.minimumPanTranslation = 0.08
recognizer.minimumPinchDistance = 0.1
recognizer.minimumRotation = 0.15
recognizer.inactivityTimeout = .milliseconds(250)

Haptic Feedback

import Subsurface

guard let actuator = SubsurfaceDevice.defaultDevice?.actuator else {
    return
}

actuator.open()
actuator.actuate(pattern: .click, intensity: 0.8)
actuator.close()

Subsurface includes the built-in feedback patterns that have been mapped so far, along with support for custom haptic patterns if you want to build your own waveform dictionary.

Visualizer

The Visualizer folder contains a small SwiftUI app for seeing the raw touch stream. It can show contact ellipses and optionally, velocity vectors, contact metadata such as pressure, palm rejection, connected devices, and haptic feedback patterns. It is the easiest way to sanity-check what your trackpad is actually reporting before building against the library!

gestures.mp4
The visualizer being used to show Loop's trackpad gestures.

License

Subsurface is released under the Apache-2.0 license. See the LICENSE file in the repository for the full license.

About

Raw multitouch, gestures, and haptics for macOS.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Contributors

Languages