Documentation
¶
Overview ¶
Package multitouch reads the raw contacts on a Mac's trackpad.
It exists because the gestures macOS publishes are the ones macOS has already decided about. A three-finger swipe means "switch space" only if that is switched on, it is delivered as a private event the Dock consumes first, and an application that wants the gesture for something else has to fight for it -- or ask a person to change a system setting so that a program can work, which is backwards.
The contacts underneath are not spoken for. Reading them needs NO permission: not Accessibility, not Screen Recording, not an event tap. Measured on an M4 Max: 2918 frames in sixty seconds, one to four contacts distinguished.
The consumer it was written for is a virtual desktop worn on the face, where three fingers sliding sideways should turn the ribbon of screens -- something no system gesture is going to offer, on a machine where the same swipe is bound to nothing.
A private framework ¶
MultitouchSupport publishes no header. The contact layout here is the one every open-source reader of it uses, and it is CHECKED rather than trusted: normalised coordinates are in [0,1] by definition, so a wrong layout shows up as coordinates outside it rather than as plausible nonsense. See TestTheLayoutIsRight, which asserts that on real contacts.
What this package will not do ¶
It reads where fingers are, because that is what a gesture is made of. It never logs one. A test here prints counts and ranges, never a position, and anything built on it should keep that discipline: a trackpad trace is a record of somebody's hands.
Index ¶
Constants ¶
const ( // DefaultFingers is three: two is a scroll everywhere on this platform, and // four is the system's own. DefaultFingers = 3 // DefaultDistance is a seventh of the trackpad. Short enough to be one // comfortable motion, long enough that resting three fingers and shifting // slightly is not a gesture. DefaultDistance = 0.15 // DefaultWithin bounds how long the fingers may take. A slow drift across // the surface is somebody resting their hand, not a swipe. DefaultWithin = 700 * time.Millisecond )
Defaults for a Swiper. They are exported because a caller tuning one wants to say what it changed FROM.
Variables ¶
var ErrUnsupported = errors.New("multitouch: only macOS has this")
ErrUnsupported is returned on platforms with no multitouch device to read.
Functions ¶
This section is empty.
Types ¶
type Contact ¶
type Contact struct {
// ID follows one finger for as long as it stays down, so a caller can tell
// three fingers moving together from three that landed and lifted.
ID int
// X and Y are 0..1 across the surface.
X, Y float32
// VX and VY are the same units per second.
VX, VY float32
// Size is how much of the surface the contact covers, in the framework's
// own arbitrary units: useful for telling a finger from a resting palm,
// meaningless as an absolute number.
Size float32
}
Contact is one finger on the surface.
X and Y are NORMALISED: 0 to 1 across the trackpad, origin at the bottom left, which is the framework's own convention and the reason a caller can compare a gesture on a laptop's trackpad with the same gesture on a Magic Trackpad twice the size.
type Frame ¶
type Frame struct {
// At is the device's own timestamp, which is monotonic and NOT wall clock:
// use it for durations between frames, never for what time something
// happened.
At time.Duration
// Device tells frames from two trackpads apart. A Mac with a Magic
// Trackpad beside its built-in one has two, and a caller that mixes them
// sees six fingers where there are three.
Device int
Contacts []Contact
}
Frame is every contact on one device at one instant.
type Swipe ¶
A Swipe is fingers that went somewhere together.
Dx and Dy are the movement in the same normalised units as Contact, so a swipe on a small trackpad and the same one on a large Magic Trackpad give the same numbers. Positive Dx is rightward, positive Dy is upward -- the framework's own axes, not a screen's.
func (Swipe) Horizontal ¶
Horizontal reports whether this went sideways rather than up or down.
type Swiper ¶
type Swiper struct {
// Fingers is how many contacts must move together; 0 means
// [DefaultFingers].
Fingers int
// Distance is how far they must go, normalised; 0 means [DefaultDistance].
Distance float32
// Within bounds the gesture's duration; 0 means [DefaultWithin].
Within time.Duration
// contains filtered or unexported fields
}
Swiper turns frames into swipes.
It is deliberately separate from the reading: this half is arithmetic over values, so it is tested against frames made up in a test file rather than against somebody's hand. A recogniser that can only be tried by swiping is a recogniser nobody changes.
The zero value works and uses the defaults above.
func (*Swiper) Feed ¶
Feed offers one frame and reports a swipe when the fingers have completed one.
It reports at most once per gesture: after a swipe, the fingers must LEAVE the surface before another can begin. That is not a nicety -- without it a single motion crosses the threshold on one frame and stays across it for every frame after, and a desk that changes screen per report would fly past six of them.
type Watcher ¶
type Watcher struct {
// contains filtered or unexported fields
}
Watcher is a subscription to the trackpads' contacts.
func Watch ¶
Watch calls fn for every frame from EVERY multitouch device, and keeps calling it until the watcher is closed.
Every device, not the first: a Mac with a Magic Trackpad beside its built-in one has two, and listening to the wrong one hears nothing at all while somebody uses the other. The Frame says which device it came from.
fn runs on the framework's own thread, once per frame -- about fifty to seventy times a second per device. It must not block: what it should do is hand the frame on and return.