Skip to content

Instantly share code, notes, and snippets.

@rmhsilva
Last active March 23, 2024 20:22
Show Gist options
  • Star 46 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save rmhsilva/61cc45587ed34707da34818a76476e11 to your computer and use it in GitHub Desktop.
Save rmhsilva/61cc45587ed34707da34818a76476e11 to your computer and use it in GitHub Desktop.
Accessing raw multitouch trackpad data (MacOS)

A collection of information about accessing raw MultiTouch events on MacOS.

Useful URLS

Link with g++:

You need the MultitouchSupport framework:

LIBS=-F/System/Library/PrivateFrameworks -framework MultitouchSupport
g++ $(LIBS) ...

Reading the trackpad

Original code from https://web.archive.org/web/20151012175118/http://steike.com/code/multitouch/:

#include <math.h>
#include <unistd.h>
#include <CoreFoundation/CoreFoundation.h>

typedef struct { float x,y; } mtPoint;
typedef struct { mtPoint pos,vel; } mtReadout;

typedef struct {
  int frame;
  double timestamp;
  int identifier, state, foo3, foo4;
  mtReadout normalized;
  float size;
  int zero1;
  float angle, majorAxis, minorAxis; // ellipsoid
  mtReadout mm;
  int zero2[2];
  float unk2;
} Finger;

typedef void *MTDeviceRef;
typedef int (*MTContactCallbackFunction)(int,Finger*,int,double,int);

MTDeviceRef MTDeviceCreateDefault();
void MTRegisterContactFrameCallback(MTDeviceRef, MTContactCallbackFunction);
void MTDeviceStart(MTDeviceRef, int); // thanks comex


int callback(int device, Finger *data, int nFingers, double timestamp, int frame) {
  for (int i=0; i<nFingers; i++) {
    Finger *f = &data[i];
    printf("Frame %7d: Angle %6.2f, ellipse %6.3f x%6.3f; "
    	   "position (%6.3f,%6.3f) vel (%6.3f,%6.3f) "
    	   "ID %d, state %d [%d %d?] size %6.3f, %6.3f?\n",
	   f->frame,
	   f->angle * 90 / atan2(1,0),
	   f->majorAxis,
	   f->minorAxis,
	   f->normalized.pos.x,
	   f->normalized.pos.y,
	   f->normalized.vel.x,
	   f->normalized.vel.y,
	   f->identifier, f->state, f->foo3, f->foo4,
	   f->size, f->unk2);
  }
  printf("\n");
  return 0;
}

int main() {
  MTDeviceRef dev = MTDeviceCreateDefault();
  MTRegisterContactFrameCallback(dev, callback);
  MTDeviceStart(dev, 0);
  printf("Ctrl-C to abort\n");
  sleep(-1);
  return 0;
}

His notes:

identifier
  Persistent identifier for each touch -- each "finger" may move around the Fingers[] array, but this will remain the same.
  
normalized.pos.x
  Current position, from [0..1]
  
size
  Close to zero if you're barely touching the touch pad
  
angle, majorAxis, minorAxis
  Describes the ellipsoid of your finger. Yes, you can track rotation of a single finger!

Racket Code

From https://gist.github.com/dchest/718922

; Ported from http://pb.lericson.se/p/FpbYhX/

(require ffi/unsafe
         ffi/unsafe/atomic)

(define libmulti (ffi-lib "/System/Library/PrivateFrameworks/MultitouchSupport.framework/MultitouchSupport"))

(define CFArrayRef _pointer)
(define CFMutableArrayRef _pointer)
(define CFIndex _long)

(define CFArrayGetCount
  (get-ffi-obj "CFArrayGetCount" libmulti
               (_fun CFArrayRef -> CFIndex)))

(define CFArrayGetValueAtIndex
  (get-ffi-obj "CFArrayGetValueAtIndex" libmulti
               (_fun CFArrayRef CFIndex -> _pointer)))

(define MTDeviceCreateList
  (get-ffi-obj "MTDeviceCreateList" libmulti
               (_fun -> CFMutableArrayRef)))

(define-cstruct _MTPoint ([x _float] [y _float]))
(define-cstruct _MTVector ([position _MTPoint] [velocity _MTPoint]))
(define-cstruct _MTData ([frame _int]
                         [timestamp _double]
                         [identifier _int]
                         [state _int]
                         [unknown1 _int]
                         [unknown2 _int]
                         [normalized _MTVector]
                         [size _float]
                         [unknown3 _int]
                         [angle _float]
                         [major_axis _float]
                         [minor_axis _float]
                         [unknown4 _MTVector]
                         [unknown5_1 _int]
                         [unknown5_2 _int]
                         [unknown6 _float]))
(define MTDataRef _MTData-pointer)


;; A queue that implements locking by atomic actions,
;; since an async-apply function cannot block on a lock.
(define sema (make-semaphore))
(define queue null)
(define (enqueue thunk)
  (set! queue (append queue (list thunk)))
  (semaphore-post sema))
(define (dequeue)
  (semaphore-wait sema)
  (start-atomic)
  (let ([v (car queue)])
    (set! queue (cdr queue))
    (end-atomic)
    v))

(define MTContactCallbackFunction
  (_fun #:async-apply enqueue _int MTDataRef _int _double _int -> _int))

(define MTDeviceRef _pointer)

(define MTRegisterContactFrameCallback
  (get-ffi-obj "MTRegisterContactFrameCallback" libmulti
               (_fun MTDeviceRef MTContactCallbackFunction -> _void)))

(define MTDeviceStart
  (get-ffi-obj "MTDeviceStart" libmulti
               (_fun MTDeviceRef _int -> _void)))


(define (multitouch-register-callback proc)
  (let ([devices (MTDeviceCreateList)])
    (for ([i (in-range (CFArrayGetCount devices))])
         (let ([device (CFArrayGetValueAtIndex devices i)])
           (MTRegisterContactFrameCallback device proc)
           (MTDeviceStart device 0)))))

(multitouch-register-callback
   (lambda (device data-ptr n-fingers timestamp frame)
     (for ([i (in-range n-fingers)])
          (let* ([data (ptr-ref data-ptr _MTData i)]
                 [vector (MTData-normalized data)]
                 [position (MTVector-position vector)]
                 [x (* 100 (MTPoint-x position))]
                 [y (* 100 (MTPoint-y position))]
                 [size (* 30 (MTData-size data))])
            (draw-circle (send canvas get-dc) x y size)))
            ;(printf "d=~a x=~a, y=~a, size=~a\n" i x y size)))
     0))

(displayln "Running")

;; Thread to run async calls in the background:
 (thread (lambda ()
           (let loop ()
             (let ([thunk (dequeue)])
               (thunk)
               (loop)))))

; Demo

(define FRAMEWIDTH 800)
(define FRAMEHIGHT 600)

(define frame (new frame% [label "Multitouch Example"]
                   [width FRAMEWIDTH]
                   [height FRAMEHIGHT]))

(define canvas (new canvas% [parent frame]))

(define yellow-brush (make-object brush% "YELLOW" 'solid))
(define blue-pen (make-object pen% "BLUE" 1 'solid))

(define (convert-x x)
  (/ (* x FRAMEWIDTH) 100))

(define (convert-y y)
  (- FRAMEHIGHT (/ (* y FRAMEHIGHT) 100)))

(define (draw-circle dc x y size)
  (send dc set-pen blue-pen)
  (send dc set-brush yellow-brush)
  (send dc draw-ellipse (convert-x x) (convert-y y) size size))

(send frame show #t)
(send frame center)
(send frame set-cursor (make-object cursor% 'blank))

; Wait a second to let the window get ready
(sleep/yield 1)

Full Headers

/*
 *  MultitouchSupport.h
 *  TouchSynthesis
 *
 *  Created by Nathan Vander Wilt on 1/13/10.
 *  Copyright 2010 Calf Trail Software, LLC. All rights reserved.
 *
 */

typedef struct {
	float x;
	float y;
} MTPoint;

typedef struct {
	MTPoint position;
	MTPoint velocity;
} MTVector;

enum {
	MTTouchStateNotTracking = 0,
	MTTouchStateStartInRange = 1,
	MTTouchStateHoverInRange = 2,
	MTTouchStateMakeTouch = 3,
	MTTouchStateTouching = 4,
	MTTouchStateBreakTouch = 5,
	MTTouchStateLingerInRange = 6,
	MTTouchStateOutOfRange = 7
};
typedef uint32_t MTTouchState;

typedef struct {
	int32_t frame;
	double timestamp;
	int32_t pathIndex;	// "P" (~transducerIndex)
	MTTouchState state;
	int32_t fingerID;	// "F" (~identity)
	int32_t handID;		// "H" (always 1)
	MTVector normalizedVector;
	float zTotal;		// "ZTot" (~quality, multiple of 1/8 between 0 and 1)
	int32_t field9;		// always 0
	float angle;
	float majorAxis;
	float minorAxis;
	MTVector absoluteVector;	// "mm"
	int32_t field14;	// always 0
	int32_t field15;	// always 0
	float zDensity;		// "ZDen" (~density)
} MTTouch;

//typedef const void* MTDeviceRef;
typedef CFTypeRef MTDeviceRef;

double MTAbsoluteTimeGetCurrent();
bool MTDeviceIsAvailable();		// true if can create default device

CFArrayRef MTDeviceCreateList();		// creates for driver types 0, 1, 4, 2, 3
MTDeviceRef MTDeviceCreateDefault();
MTDeviceRef MTDeviceCreateFromDeviceID(int64_t);
MTDeviceRef MTDeviceCreateFromService(io_service_t);
MTDeviceRef MTDeviceCreateFromGUID(uuid_t);		// GUID's compared by pointer, not value!
void MTDeviceRelease(MTDeviceRef);

CFRunLoopSourceRef MTDeviceCreateMultitouchRunLoopSource(MTDeviceRef);
OSStatus MTDeviceScheduleOnRunLoop(MTDeviceRef, CFRunLoopRef, CFStringRef);

OSStatus MTDeviceStart(MTDeviceRef, int);
OSStatus MTDeviceStop(MTDeviceRef);
bool MTDeviceIsRunning(MTDeviceRef);


bool MTDeviceIsValid(MTDeviceRef);
bool MTDeviceIsBuiltIn(MTDeviceRef) __attribute__ ((weak_import));	// no 10.5
bool MTDeviceIsOpaqueSurface(MTDeviceRef);
io_service_t MTDeviceGetService(MTDeviceRef);
OSStatus MTDeviceGetSensorSurfaceDimensions(MTDeviceRef, int*, int*);
OSStatus MTDeviceGetFamilyID(MTDeviceRef, int*);
OSStatus MTDeviceGetDeviceID(MTDeviceRef, uint64_t*) __attribute__ ((weak_import));	// no 10.5
OSStatus MTDeviceGetDriverType(MTDeviceRef, int*);
OSStatus MTDeviceGetActualType(MTDeviceRef, int*);
OSStatus MTDeviceGetGUID(MTDeviceRef, uuid_t*);

typedef void (*MTFrameCallbackFunction)(MTDeviceRef device,
										MTTouch touches[], size_t numTouches,
										double timestamp, size_t frame);
void MTRegisterContactFrameCallback(MTDeviceRef, MTFrameCallbackFunction);

typedef void (*MTFrameCallbackRefconFunction)(MTDeviceRef device,
										MTTouch touches[], size_t numTouches,
										double timestamp, size_t frame, void* refcon);
void MTRegisterContactFrameCallbackWithRefcon(MTDeviceRef, MTFrameCallbackRefconFunction, void* refcon);
void MTUnregisterContactFrameCallback(MTDeviceRef, MTFrameCallbackRefconFunction);


typedef void (*MTPathCallbackFunction)(MTDeviceRef device, long pathID, long state, MTTouch* touch);
MTPathCallbackFunction MTPathPrintCallback;
void MTRegisterPathCallback(MTDeviceRef, MTPathCallbackFunction);

/*
 // callbacks never called (need different flags?)
typedef void (*MTImageCallbackFunction)(MTDeviceRef, void*, void*);
MTImageCallbackFunction MTImagePrintCallback;
void MTRegisterMultitouchImageCallback(MTDeviceRef, MTImageCallbackFunction);
 */

/*
 // these log error
void MTVibratorRunForDuration(MTDeviceRef,long);
void MTVibratorStop(MTDeviceRef);
*/
@mate-h
Copy link

mate-h commented May 12, 2020

Here is a CMake snippet for those interested:

# Link MultitouchSupport Apple framework
find_library( LIBS MultitouchSupport /System/Library/PrivateFrameworks )
message(STATUS "Found library: ${LIBS}" )
target_link_libraries( ${library_name}
    ${LIBS}
)

@rmhsilva
Copy link
Author

Thanks @mate-h!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment