2
votes

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.

2
Instead of creating the event source with kCGEventSourceStatePrivate, have you tried kCGEventSourceStateCombinedSessionState or kCGEventSourceStateHIDSystemState?Ken Thomases

2 Answers

2
votes

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
1
votes

This is my appDelegate and how I got it working.

import Cocoa
import CoreGraphics
import Foundation

var shift = false;
var cntrl = false;
var option = false;
var command = false;

func myCGEventCallback(proxy: CGEventTapProxy, type: CGEventType, event: CGEvent, refcon: UnsafeMutableRawPointer?) -> Unmanaged<CGEvent>? {

    var mask: CGEventFlags = []
    if(shift) {
        mask.insert(CGEventFlags.maskShift)
     }
    if(cntrl) {
        mask.insert(CGEventFlags.maskControl)
     }
     if(option) {
         mask.insert(CGEventFlags.maskAlternate)
     }
    if(command) {
        mask.insert(CGEventFlags.maskCommand)
    }
    if(mask.rawValue != 0) {
        event.flags = mask;
    }

    return Unmanaged.passRetained(event)
}

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

func applicationDidFinishLaunching(_ aNotification: Notification) {

    // Insert code here to initialize your application
    let mask: CGEventMask = 1 << CGEventType.keyDown.rawValue
    let eventTap:CFMachPort = CGEvent.tapCreate(tap: .cghidEventTap, place: .headInsertEventTap, options: .defaultTap, eventsOfInterest: mask, callback: myCGEventCallback, userInfo: nil)!

    let runLoopSource:CFRunLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0);

    CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, CFRunLoopMode.commonModes);
    CGEvent.tapEnable(tap: eventTap, enable: true);

    CFRunLoopRun();
}

func applicationWillTerminate(_ aNotification: Notification) {
    // Insert code here to tear down your application
}

public func SetShift() {
    shift = !shift;
}
public func SetCtrl() {
    cntrl = !cntrl;
}
public func SetCommand() {
    command = !command;
}
public func SetOption() {
    option = !option;
}
}