Remap `fn` to left mouse button on OSX
Asked Answered
E

1

8

I get bad tendinitis from clicking the mouse all day.

In the past I used Karabiner to remap the fn key to simulate a left mouse button. However it doesn't work with Sierra.

I tried to accomplish this in Cocoa, and it correctly performs mouse-down/up when I press and release fn.

However it doesn't handle double-click / triple-click.

Also when dragging, (e.g. dragging a window, or selecting some text) nothing happens visually until I key-up, whereupon it completes.

How can I adapt my code to implement this?


First I create an event tap:

- (BOOL)tapEvents
{
    _modifiers = [NSEvent modifierFlags];

    if ( ! _eventTap ) {
        NSLog( @"Initializing an event tap." );

        // kCGHeadInsertEventTap -- new event tap should be inserted before
        //   any pre-existing event taps at the same location,
        _eventTap = CGEventTapCreate( kCGHIDEventTap, // kCGSessionEventTap,
                                      kCGHeadInsertEventTap,
                                      kCGEventTapOptionDefault,
                                           CGEventMaskBit( kCGEventKeyDown )
                                         | CGEventMaskBit( kCGEventFlagsChanged )
                                         | CGEventMaskBit( NSSystemDefined )
                                         ,
                                      (CGEventTapCallBack)_tapCallback,
                                      (__bridge void *)(self));
        if ( ! _eventTap ) {
            NSLog(@"unable to create event tap. must run as root or "
                    "add privlidges for assistive devices to this app.");
            return NO;
        }
    }
    CGEventTapEnable( _eventTap, YES );

    return [self isTapActive];
}

Now I implement the callback:

CGEventRef _tapCallback(
                        CGEventTapProxy proxy,
                        CGEventType     type,
                        CGEventRef      event,
                        Intercept*     listener
                        )
{
    //Do not make the NSEvent here.
    //NSEvent will throw an exception if we try to make an event from the tap timout type
    @autoreleasepool {
        if( type == kCGEventTapDisabledByTimeout ) {
            NSLog(@"event tap has timed out, re-enabling tap");
            [listener tapEvents];
            return nil;
        }
        if( type != kCGEventTapDisabledByUserInput ) {
            return [listener processEvent:event];
        }
    }
    return event;
}

Finally I implement a processEvent that will pass through any event apart from fn key up/down, which will get converted to left mouse up/down:

- (CGEventRef)processEvent:(CGEventRef)cgEvent
{
    //NSLog( @"- - - - - - -" );

    NSEvent* event = [NSEvent eventWithCGEvent:cgEvent];

    //NSLog(@"%d,%d", event.data1, event.data2);
    //NSEventType type = [event type];

    NSUInteger m = event.modifierFlags &
        ( NSCommandKeyMask | NSAlternateKeyMask | NSShiftKeyMask | NSControlKeyMask | NSAlphaShiftKeyMask | NSFunctionKeyMask );

    NSUInteger flags_changed = _modifiers ^ m;
    _modifiers = m;

    switch( event.type ) {
        case NSFlagsChanged:
        {
            assert(flags_changed);

            //NSLog(@"NSFlagsChanged: %d, event.modifierFlags: %lx", event.keyCode, event.modifierFlags);
            if( flags_changed & NSFunctionKeyMask ) {
                bool isDown = _modifiers & NSFunctionKeyMask;
                CGEventType evType = isDown ? kCGEventLeftMouseDown : kCGEventLeftMouseUp;
                CGPoint pt = [NSEvent mouseLocation];
                CGPoint mousePoint = CGPointMake(pt.x, [NSScreen mainScreen].frame.size.height - pt.y);

                CGEventRef theEvent = CGEventCreateMouseEvent(NULL, evType, mousePoint, kCGMouseButtonLeft);
                CGEventSetType(theEvent, evType);
                CGEventPost(kCGHIDEventTap, theEvent);
                CFRelease(theEvent);

                //return theEvent;
            }

            break;
        }
    }

    _lastEvent = [event CGEvent];
    CFRetain(_lastEvent); // must retain the event. will be released by the system
    return _lastEvent;
}

EDIT: Performing a double click using CGEventCreateMouseEvent()

EDIT: OSX assign left mouse click to a keyboard key

Emitter answered 30/5, 2017 at 11:29 Comment(7)
I don't have anything to contribute to the question, but I would note that Apple is quite committed to supporting accessibility issues, which this would seem to be. I would suggest trying to find the Right Person at Apple to bring this up with as an OS-supported feature that would go in the Accessibility panel in System Prefs. Actually, are you aware of the Mouse Keys feature already in Accessibility? I've never tried it, but I notice that it does allow you to use the "5" key on the numeric keypad as a proxy for the mouse button.Airlie
For double- and triple-click, you're going to have to set the kCGMouseEventClickState value of the event using CGEventSetIntegerValueField(). You can figure out if the clicks were rapid enough by tracking the time of the last and comparing against [NSEvent doubleClickInterval]. You should track location, too, since I think large moves between clicks reset the click count. For dragging, you'll have to convert mouse-moved events to mouse-dragged events (kCGEventLeftMouseDragged, NSEventTypeLeftMouseDragged).Hokeypokey
How ab. switching hands (at least temp'y ) . Ever thought of a foot-pedal for clicking ? I know it sounds goofy, but it maybe a help . Lastly, change up the mice you use... maybe try a wheel-ball mouse like logitech's marble-mouse.Uriisa
Done all that...Emitter
@Pi - hmmm . . , Well, I have to echo what bhaller said - maybe bring it to the attention of Apple ? It certainly cannot hurt. You know what, I will also tell them about it. It's a great idea, and fairly straightforward to implementUriisa
@KenThomases - Do you think maybe the dragging might be something he can skip for now? It sounds like that would be difficult to implement in smooth wayUriisa
hmm, this seems relevant - superuser.com/questions/295644/… ; suppose you got an extra keyboard just to use as a mouse ?Uriisa
K
0

Karabiner now works on macOS 10.12 and later.

Kong answered 28/4, 2020 at 23:42 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.