Search code examples
objective-cmacoskeyboard-eventssystemevent

Emulate / send Modifier Key (Cntrl, Alt, fn, Shift) in OSx


I am sending keyboard key press and key release events, which works for all keyboard keys.

But modifier keys works only when the key associated with modifier key is sent from application, and not from real hardware. That is if I send Shift and 'a' from application, it prints 'A' (capital A, which is what is expected).

But if I send 'shift' key down event from application, and enter 'a' from physical keyboard, it prints 'a' (the shift key doesn't seem to be working across different devices). The same applies for other modifier keys such as cmd, alt, and fn keys!.

Is there a way to send modifier keys to system, so that I can emulate modifier keys from my application?. Specifically, I would like to activate modifier key from application, and enter the combination key from physical keyboard.

Here is the code I use to send key press and release events.

- (void)setADBKey:(uint32)key value:(int)value
{
    if (!eventSource)
    {
        eventSource  = CGEventSourceCreate(kCGEventSourceStatePrivate);
    }

    CGEventRef event = CGEventCreateKeyboardEvent(eventSource, key, value!=0);
    CGEventPost(kCGHIDEventTap, event);
    CFRelease(event);
}

I have also tried creating and sending a system event, through the following code

struct 
{

    CGKeyCode keyCode;
    int flag;
    int cgEventFlag;

} modifiers[] = {

    { 56, NX_DEVICELSHIFTKEYMASK, kCGEventFlagMaskShift },
    { 60, NX_DEVICERSHIFTKEYMASK, kCGEventFlagMaskShift },
    { 59, NX_DEVICELCTLKEYMASK, kCGEventFlagMaskControl },
    { 58, NX_DEVICELALTKEYMASK, kCGEventFlagMaskAlternate },
    { 61, NX_DEVICERALTKEYMASK, kCGEventFlagMaskAlternate },
    { 55, NX_DEVICELCMDKEYMASK, kCGEventFlagMaskCommand },
    { 54, NX_DEVICERCMDKEYMASK, kCGEventFlagMaskCommand }

};

- (void)setAdbKey:(uint32)adbkey value:(int)value repeat:(BOOL)repeat
{
    //int adbkey = def_usb_2_adb_keymap[hidkey];

    int modifier = 0;

    for(int i=0; i< ARR_SIZE(modifiers); i++)
    {
        if (adbkey == modifiers[i].keyCode)
        {
            modifier = modifiers[i].cgEventFlag;
            break;
        }
    }

    if (value)
    {
        flags |= modifier;
    }             
    else 
    {
        flags &= ~modifier;
    }

    CGEventRef event = CGEventCreateKeyboardEvent(eventSource, adbkey, value!=0);

    if (repeat)
    {
        CGEventSetIntegerValueField(event, kCGKeyboardEventAutorepeat, (int64_t)1);
    }

    // don't apply modifier flags to a modifier
    if (!modifier)
    {
        CGEventSetFlags(event, flags);
    }

    CGEventPost(kCGHIDEventTap, event);
    CFRelease(event);
}

Both methods work when modifier key and combination key are sent from application, but not when modifier key alone is set from application.


Solution

  • All you have to do is create an event tap and set the mask.

    Sample:

    @interface AppDelegate ()
    
    @property (assign) CFMachPortRef myEventTap;
    @property (assign) CFRunLoopSourceRef myRunLoopSource;
    
    @end
    
    @implementation AppDelegate
    
    CGEventRef MyEventTapCallBack(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) {
        CGEventSetFlags(event, kCGEventFlagMaskShift);
        return event;
    }
    
    - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
        self.myEventTap = CGEventTapCreate(kCGHIDEventTap,
                                                kCGHeadInsertEventTap,
                                                kCGEventTapOptionDefault,
                                                CGEventMaskBit(kCGEventKeyDown) | CGEventMaskBit(kCGEventKeyUp) | CGEventMaskBit(kCGEventFlagsChanged),
                                                MyEventTapCallBack,
                                                (__bridge void *)self);
        if (self.myEventTap) {
            self.myRunLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, self.myEventTap, 0);
            if (self.myRunLoopSource)
                CFRunLoopAddSource(CFRunLoopGetMain(), self.myRunLoopSource, kCFRunLoopCommonModes);
        }
    }
    
    - (void)applicationWillTerminate:(NSNotification *)aNotification {
        if (self.myRunLoopSource) {
            CFRunLoopSourceInvalidate(self.myRunLoopSource);
            CFRelease(self.myRunLoopSource);
        }
        if (self.myEventTap)
            CFRelease(self.myEventTap);
    }
    
    @end