It should not actually be too difficult to get the magic trackpad 2 to work on 10.9 or lower, complete with native multitouch gestures (excluding force touch of course(. Basically, you will need 2 parts:
1) Kernel driver to communicate with MT2 and receive frames containing transducer (finger) locations and click status. This part is already well reverse-engineered thanks to Linux drivers for MT2, so they can directly re-used, with only a bit of effort needed to port the bluetooth (l2cap?) communication to an iokit driver.
2) Simulate a MT1 using the above inputs. A similar project already exists in the hackintosh community, VoodooInput, where arbitrary trackpad devices can map onto a simulated MT2 thereby gaining native multitouch support. IIUC the way that works is that they spoof the device code of the MT2, and then IOKit driver matching will automagically match the native multitouch HID driver to the nub exposed by the simulated hardware device. Then all you have to do is convert the actual device input to the magic trackpad frame format which is sent out from simulated device, and the osx multitouch stack takes care of the rest. While VoodooInput simulates a MT2, we want to simulate a MT1 so we cannot use the code exactly, but since the MT1 frame format is also decently well documented (since it has a linux driver), much of code can be reused here too.
Btw last I checked the MT1 drivers for linux were a bit incomplete in that they didn't reverse engineer some of the fields of the received frame. I think one of them was the finger id. It's actually quite easy to do this reverse engineering work from userspace since the private multitouch framework has a callback you can register to dump the raw frame and compare it against the parsed frame contents.
Here's some random code I threw together while playing around with this, in case it's useful to anyone.
So in principle it should only be a long-weekend project for someone who's decently comfortable with C. I post this here in hopes that it might inspire someone to go ahead and write the thing (it can also be a good learning experience).
1) Kernel driver to communicate with MT2 and receive frames containing transducer (finger) locations and click status. This part is already well reverse-engineered thanks to Linux drivers for MT2, so they can directly re-used, with only a bit of effort needed to port the bluetooth (l2cap?) communication to an iokit driver.
2) Simulate a MT1 using the above inputs. A similar project already exists in the hackintosh community, VoodooInput, where arbitrary trackpad devices can map onto a simulated MT2 thereby gaining native multitouch support. IIUC the way that works is that they spoof the device code of the MT2, and then IOKit driver matching will automagically match the native multitouch HID driver to the nub exposed by the simulated hardware device. Then all you have to do is convert the actual device input to the magic trackpad frame format which is sent out from simulated device, and the osx multitouch stack takes care of the rest. While VoodooInput simulates a MT2, we want to simulate a MT1 so we cannot use the code exactly, but since the MT1 frame format is also decently well documented (since it has a linux driver), much of code can be reused here too.
Btw last I checked the MT1 drivers for linux were a bit incomplete in that they didn't reverse engineer some of the fields of the received frame. I think one of them was the finger id. It's actually quite easy to do this reverse engineering work from userspace since the private multitouch framework has a callback you can register to dump the raw frame and compare it against the parsed frame contents.
Here's some random code I threw together while playing around with this, in case it's useful to anyone.
So in principle it should only be a long-weekend project for someone who's decently comfortable with C. I post this here in hopes that it might inspire someone to go ahead and write the thing (it can also be a good learning experience).
Objective-C:
#include <math.h>
#include <unistd.h>
#include <CoreFoundation/CoreFoundation.h>
#import <Foundation/Foundation.h>
// IOKit Fundamentals: https://developer.apple.com/library/archive/documentation/DeviceDrivers/Conceptual/IOKitFundamentals/Introduction/Introduction.html
// https://developer.apple.com/library/archive/documentation/Darwin/Conceptual/KernelProgramming/IOKit/IOKit.html#//apple_ref/doc/uid/TP30000905-CH213-TPXREF104
// https://encyclopediaofdaniel.com/blog/making-the-magic-trackpad-work/
/**
the IOKit structure is as follows: VoodooI2C publishes an IOHIDDevice (i.e VoodooI2CHIDDevice). IOHIDDevice creates an IOHIDInterface nub. AppleMultitouch(Trackpad|Mouse)HIDEventDriver (which is a subclass of IOHIDEventDriver) attaches to IOHIDInterface.
AppleMultitouch(Trackpad|Mouse)HIDEventDriver is a subclass of AppleMultitouchInputHIDEventDriver which is responsible for interfacing with the mutitouch library - it instanties an AppleMultitouchDevice object and passes the report calls back and forth from the multitouch library to the hid device
AppleMultitouch(Trackpad|Mouse)HIDEventDriver and AppleMultitouchInputHIDEventDriver can be found in AppleTopCaseHIDEventDriver
AppleMultitouchDevice can be found in AppleMultitouchDriver
http://mirror.informatimago.com/next/developer.apple.com/hardwaredrivers/customusbdrivers.html
**/
typedef struct { float x,y; } mtPoint;
typedef struct { mtPoint pos,vel; } mtReadout;
typedef struct {
int frame;
double timestamp;
int identifier, state, fingerid, handid;
mtReadout normalized;
float size;
int zero1;
float angle, majorAxis, minorAxis; // ellipsoid
mtReadout mm;
int zero2[2];
float density;
} Finger;
typedef void *MTDeviceRef;
typedef int (*MTContactCallbackFunction)(int,Finger*,int,double,int);
typedef void (*MTPathCallbackFunction)(int, long, long,Finger*);
typedef void (*MTFullFrameCallbackFunction)(int /*device*/, uint8_t* /*data*/, int /*size?*/);
MTDeviceRef MTDeviceCreateDefault();
CFMutableArrayRef MTDeviceCreateList(void);
CFStringRef mt_CreateSavedNameForDevice(MTDeviceRef);
void MTDeviceGetTransportMethod(MTDeviceRef, int *);
int MTDeviceGetParserType(MTDeviceRef);
int MTDeviceGetParserOptions(MTDeviceRef);
void MTRegisterContactFrameCallback(MTDeviceRef, MTContactCallbackFunction);
void MTRegisterPathCallback(MTDeviceRef, MTPathCallbackFunction);
void MTRegisterFullFrameCallback(MTDeviceRef, MTFullFrameCallbackFunction, int /*0x0 in practice*/, int /*unused?*/);
void MTDeviceStart(MTDeviceRef, int); // thanks comex
void mt_HandleMultitouchFrame(int, int);
// Sample frame data:
// |28| |68| |10 28 77| |5a 90| |b| |17 ellipse major 35 ellipse minor| |c5 = size in 1/8 increment| |82 45 = /*state + figner id*/|
void fullframeCallback(int device, uint8_t* data, int size) {
if (size == 13) {
printf("%d ", data[12] & 0xF);
}
// for(int i = 0; i < size; i++)
// printf("%x ", data[i]);
// printf("\n");
}
void pathCallback(int device, long pathID, long state, Finger* touch) {
//printf("Inside callback\n");
if (state == 3 || state == 4)
printf("%d %d %d\n", touch->identifier, touch->fingerid, touch->handid);
}
int callback(int device, Finger *data, int nFingers, double timestamp, int frame) {
for (int i=0; i<nFingers; i++) {
Finger *f = &data[i];
//if (f->state == 3 || f->state == 4)
printf("%d\n", f->fingerid);
// 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 [finger id %d / hand id %d?] size %6.3f, density %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->fingerid, f->handid,
// f->size, f->density);
}
return 0;
}
int main() {
printf("%p\n", (void *)(size_t) &mt_HandleMultitouchFrame);
NSArray* devices = CFBridgingRelease(MTDeviceCreateList());
for(int i = 0; i < devices.count; i++)
{
MTDeviceRef device = (__bridge MTDeviceRef)devices[i];
int x;
MTDeviceGetTransportMethod(device, &x);
printf("%d ", x);
int y = MTDeviceGetParserType(device);
int z = MTDeviceGetParserOptions(device);
printf("%d %d\n", y, z);
if (x != 4) continue;
//NSString *yourFriendlyNSString = (__bridge NSString *) mt_CreateSavedNameForDevice(device);
//NSLog(@"%@",yourFriendlyNSString);
MTRegisterContactFrameCallback(device, callback);
MTRegisterFullFrameCallback(device, fullframeCallback, 0, 0);
// MTRegisterPathCallback(device, pathCallback);
MTDeviceStart(device, 0);
}
printf("Ctrl-C to abort\n");
sleep(-1);
return 0;
}
Last edited: