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

P i
  • 29,020
  • 36
  • 159
  • 267
  • 3
    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. – bhaller May 30 '17 at 21:24
  • 2
    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`). – Ken Thomases Jun 01 '17 at 20:42
  • 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. – Caffeinated Jun 08 '17 at 14:17
  • Done all that... – P i Jun 08 '17 at 15:04
  • @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 implement – Caffeinated Jun 08 '17 at 15:08
  • @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 way – Caffeinated Jun 08 '17 at 15:11
  • hmm, this seems relevant - https://superuser.com/questions/295644/assign-left-mouse-click-to-a-keyboard-key ; suppose you got an extra keyboard just to use as a mouse ? – Caffeinated Jun 08 '17 at 16:21

1 Answers1

0

Karabiner now works on macOS 10.12 and later.

Michael S.
  • 118
  • 1
  • 4