3

Is there a way to configure the Magic Trackpad with tap-to-click without having tap-to-click on the built-in trackpad of a MacBook Air?

2 Answers 2

2
+50

Have you already taken a look at MagicPrefs (free)? It lets you assign custom actions to various gestures and touches, maybe this will let you realise what you want. Since I don´t have a Magic Trackpad near me, I can´t verify this though.

1
  • MagicPrefs seems to fit the bill; so unless I find some problems after testing it a bit, I'll accept this.
    – hotpaw2
    Mar 1, 2011 at 1:31
1

Yes it is possible to have tap to click on the external magic trackpad only. First disconnect the magic trackpad and ensure tap to click is disabled in system preferences. Then run

defaults write com.apple.driver.AppleBluetoothMultitouch.trackpad Clicking -bool true

When you reconnect the magic trackpad, it should have tap to click enabled without the internal trackpad having tap to click.

For reference, to enable tap to click on the internal trackpad you can do

defaults read com.apple.AppleMultitouchTrackpad Clicking

But this will require a reboot. Disclaimer that this was only tested on 10.9.5 (with magic trackpad v1)


For newer os versions, this no longer works. Instead you can combine a contact-frame callback with a call to private preferencepane framework in order to achieve the same. The below sample code will enable tap-to-click when a magic trackpad is connected, and disable tap-to-click when the magic trackpad is disconnected. You may want to modify code as needed to enable/disable in a more fine-grain fashion based on whether the last input was on magic or internal trackpad.

#include <math.h>
#include <unistd.h>
#include <CoreFoundation/CoreFoundation.h>
#import <Foundation/Foundation.h>
#include <CoreGraphics/CoreGraphics.h>
#import <Cocoa/Cocoa.h>
#import <AppKit/NSAccessibility.h>


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();

extern "C" {
    CFMutableArrayRef MTDeviceCreateList(void);
    CFStringRef mt_CreateSavedNameForDevice(MTDeviceRef);
    void MTDeviceGetTransportMethod(MTDeviceRef, int *);
    int MTDeviceGetParserType(MTDeviceRef);
    int MTDeviceStop(MTDeviceRef);
    int MTDeviceSetSurfaceOrientation(MTDeviceRef, int);
    int MTDeviceRelease(MTDeviceRef);
    int MTDeviceGetParserOptions(MTDeviceRef);
    void MTRegisterContactFrameCallback(MTDeviceRef, MTContactCallbackFunction);
    void MTUnregisterContactFrameCallback(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);
}

@interface MTTGestureBackEnd
+ (id)sharedInstance;
@property long long tapBehavior; // @dynamic tapBehavior;
@end


int lastFinger = -1

int callback(int device, Finger *data, int nFingers, double timestamp, int frame) {
    if (nFingers == 1) {
        lastFinger = data[0].fingerid;
        //printf("%d\n", lastFinger);
    }
    else {
        lastFinger = -1;
    }

    return 0;
}


MTDeviceRef device = NULL;
void initMagicTrackpad() {
    printf("Init device\n");
    NSArray* devices = (__bridge_transfer NSArray *) MTDeviceCreateList();
        for(int i = 0; i < devices.count; i++)
        {
            MTDeviceRef deviceI = (__bridge MTDeviceRef) devices[i];
            int x;
            MTDeviceGetTransportMethod(deviceI, &x);
            printf("%d ", x);
            int y = MTDeviceGetParserType(deviceI);
            int z = MTDeviceGetParserOptions(deviceI);
            printf("%d %d\n", y, z);
            if (x != 4) /* bluetooth */ {
                continue;
            }
            device = (__bridge_retained MTDeviceRef) devices[i];
            MTRegisterContactFrameCallback(device, callback);
            MTDeviceStart(device, 0);
            MTDeviceSetSurfaceOrientation(device, 2);
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
                MTDeviceSetSurfaceOrientation(device, 2);
            });
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
                MTDeviceSetSurfaceOrientation(device, 2);
            });
            [[MTTGestureBackEnd sharedInstance] setTapBehavior: 1];
            break;
        }
    if (device == NULL) {
        printf("No BT trackpad found\n");
    }
}

void stopMagicTrackpad() {
    if (device == NULL) return;
    printf("Stop magic trackpad\n");
    MTUnregisterContactFrameCallback(device, callback); // work
    MTDeviceStop(device);
    MTDeviceRelease(device);
    [[MTTGestureBackEnd sharedInstance] setTapBehavior: 0];
}


static void magicTrackpadAdded(void* refCon, io_iterator_t iterator) {
    io_service_t device;
    printf("Magic trackpad added\n");
    while ((device = IOIteratorNext(iterator))) {
        IOObjectRelease(device);
    }
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
        initMagicTrackpad();
    });
}

static void magicTrackpadRemoved(void* refCon, io_iterator_t iterator) {
    io_service_t object;
        printf("Magic trackpad removed\n");
    while ((object = IOIteratorNext(iterator))) {
        IOObjectRelease(object);
    }
    stopMagicTrackpad();
}

void registerMTNotification() {
    IONotificationPortRef notificationObject = IONotificationPortCreate(kIOMasterPortDefault);
    CFRunLoopSourceRef notificationRunLoopSource = IONotificationPortGetRunLoopSource(notificationObject);
    CFRunLoopAddSource(CFRunLoopGetMain(), notificationRunLoopSource, kCFRunLoopDefaultMode);
       
    CFMutableDictionaryRef matchingDict = IOServiceNameMatching("BNBTrackpadDevice");
    matchingDict = (CFMutableDictionaryRef) CFRetain(matchingDict);

    //MagicTrackpad added notification
    io_iterator_t magicTrackpadAddedIterator;
    IOServiceAddMatchingNotification(notificationObject, kIOFirstMatchNotification, matchingDict, magicTrackpadAdded, NULL, &magicTrackpadAddedIterator);
    // Run out the iterator or notifications won't start (you can also use it to iterate the available devices).
    io_service_t d;
    while ((d = IOIteratorNext(magicTrackpadAddedIterator))) { IOObjectRelease(d); }

    //MagicTrackpad removed notification
    io_iterator_t magicTrackpadRemovedIterator;
    IOServiceAddMatchingNotification(notificationObject, kIOTerminatedNotification, matchingDict, magicTrackpadRemoved, NULL, &magicTrackpadRemovedIterator);
    while ((d = IOIteratorNext(magicTrackpadRemovedIterator))) { IOObjectRelease(d); }
   
}
   

int main() {
    initMagicTrackpad();
    registerMTNotification();

    printf("Ctrl-C to abort\n");
    [[NSRunLoop mainRunLoop] run];
    sleep(-1);
    return 0;
}
4
  • Tested on macOS 12.6, sadly did not work.
    – BadAtLaTeX
    Jun 29, 2023 at 17:03
  • @gr4nt3d Yeah this does not seem to work on newer osx versions, despite the fact that com.apple.driver.AppleBluetoothMultitouch.trackpad is still separate from com.apple.AppleMultitouchTrackpad. It's possible to simulate the same result by registering a contact frame callback to check if external trackpad is active and then toggling tap to click if so.
    – 1110101001
    Jul 3, 2023 at 5:05
  • @ ... indeed. I found both of those settings too and tried all sorts of ways of manipulating them: int, bool, setting through the 'System Preferences' and changing back via terminal for the other one (note that one of those is not being used at all anymore - forgot which). Could you like to an answer or source to said workaround?
    – BadAtLaTeX
    Jul 3, 2023 at 8:53
  • 1
    @gr4nt3d I have added sample code of how you can combine contact frame callback with call to preference pane support framework to accomplish this.
    – 1110101001
    Jul 3, 2023 at 18:54

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .