CGEventCreateKeyboardEvent and CGEventTapLocation - macos

I'm having a hard time dealing with custom keyboard events. I want to be able to send any key with / without kCGEventFlagMasks like command, alt, ctrl, shift...
e.g. I want to send the current frontmost app, let's assume it's TextEdit, the key combo cmd+t, which should show the font window.
But currently only t is added to the current document of TextEdit. I tried posting the event with custom set CGEventFlags and alternatively generating keyDown and keyUp events for cmd around the t event. The free app Key Codes shows me when I use the CGEventFlags set, that cmd+t has been pressed, but this key combo isn't passed to TextEdit, only the single t...
Here's my code:
...
flags = (flags | kCGEventFlagMaskCommand);
[self writeString:#"" withFlags:flags];
...
(void)writeString:(NSString *)valueToSet withFlags:(int)flags
{
UniChar buffer;
CGEventRef keyEventDown = CGEventCreateKeyboardEvent(NULL, 1, true);
CGEventRef keyEventUp = CGEventCreateKeyboardEvent(NULL, 1, false);
CGEventSetFlags(keyEventDown,0);
CGEventSetFlags(keyEventUp,0);
for (int i = 0; i < [valueToSet length]; i++) {
[valueToSet getCharacters:&buffer range:NSMakeRange(i, 1)];
CGEventKeyboardSetUnicodeString(keyEventDown, 1, &buffer);
CGEventSetFlags(keyEventDown,flags);
CGEventPost(kCGSessionEventTap, keyEventDown);
CGEventKeyboardSetUnicodeString(keyEventUp, 1, &buffer);
CGEventSetFlags(keyEventUp,flags);
CGEventPost(kCGSessionEventTap, keyEventUp);
}
CFRelease(keyEventUp);
CFRelease(keyEventDown);
}
I'm also having a hard time understanding the meaning of a CGEventTapLocation. I don't know which event tap would be the most suitable for my task. The description in the Quartz Event Reference doesn't enlighten me:
kCGHIDEventTap
Specifies that an event tap is placed at the point where HID system events enter the window server.
kCGSessionEventTap
Specifies that an event tap is placed at the point where HID system and remote control events enter a login session.
kCGAnnotatedSessionEventTap
Specifies that an event tap is placed at the point where session events have been annotated to flow to an application.
I've seen many posts about that topic, but none of 'em helped me solve my problem...
Thanks for your help!

That's not the answer to the question but my current workaround via AppleScript:
appleScriptString = [NSString stringWithFormat:#"tell application \"System Events\" to key code %d using %#",keyCode, modifierDownString];
which I feed to that function:
- (void)runScript:(NSString*)scriptText
{
NSDictionary *error = nil;
NSAppleEventDescriptor *appleEventDescriptor;
NSAppleScript *appleScript;
appleScript = [[NSAppleScript alloc] initWithSource:scriptText];
appleEventDescriptor = [appleScript executeAndReturnError:&error];
[appleScript release];
}

Related

How to determine pressed modifier keys when a Cocoa app is started?

In an app I'm developing, a database is used to store all user data, usually located somewhere in Library/Application Support. In order to enable the user to switch the database, I want to implement a functionality similar to iTunes or iPhoto, where the app asks for the library's or database's location if the option key is pressed upon starting the app.
How can I check the currently pressed (modifier) keys if no NSEvent is at hand?
I already tried:
[NSResponder flagsChanged:(NSEvent *)theEvent] – Probably not called because the option key is already down before the window and any responders are instantiated.
[[NSApplication sharedApplication] currentEvent] – Returns nil.
Put this in applicationDidFinishLaunching:
NSUInteger modifiers = ([NSEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask);
if (modifiers == NSAlternateKeyMask) {
// do your stuff here
}
Beginning with macOS 10.12, this should be:
NSUInteger modifiers = ([NSEvent modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask);
if (modifiers == NSEventModifierFlagOption) {
// do your stuff here
}
Swift 5 variant:
func applicationDidFinishLaunch(_ aNotification: Notification?) {
if NSEvent.modifierFlags.intersection(.deviceIndependentFlagsMask) == .option {
// Your code
}
}

OSX assign left mouse click to a keyboard key

I would like to be able to assign a key on my keyboard to be equivalent to a left mouse click.
Ideally it needs to act such that holding the key down is also equivalent to holding the left mouse button down.
I'd like this capability as a user, additionally a programmatic solution (cocoa/applescript etc) would be great too.
Not exactly what you want, but in the System preferences -> Universal access you can turn on mouse keys - and with them you can move (and click) mouse by keyboard. docs here:
http://docs.info.apple.com/article.html?path=Mac/10.6/en/cdb_moskys.html
http://docs.info.apple.com/article.html?path=Mac/10.6/en/8565.html
http://docs.info.apple.com/article.html?artnum=61472
Or,
With the "ControllerMate.app" is possible to do this, but it is commercial app.
This can be done by writing some code:
Write a global handler to receive the type of event you want to watch
[NSEvent addGlobalMonitorForEventsMatchingMask:NSKeyDownMask
handler:^(NSEvent *event){
NSLog(#"%i", [event keyCode]);
//todo invoke mouse clicking code;
}];
Then write the mouse click code:
// get current mouse pos
CGEventRef ourEvent = CGEventCreate(NULL);
CGPoint point = CGEventGetLocation(ourEvent);
NSLog(#"Location? x= %f, y = %f", (float)point.x, (float)point.y);
// perform a click
CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateCombinedSessionState);
CGEventRef theEvent = CGEventCreateMouseEvent(source, kCGEventLeftMouseDown, point, kCGMouseButtonLeft);
CGEventSetType(theEvent, kCGEventLeftMouseDown);
CGEventPost(kCGHIDEventTap, theEvent);
CFRelease(theEvent);

Programmatically open Mac Help menu

I'm integrating a GTK# application into Mac OS X. GTK on Mac OS X is a wrapper over some Cocoa and Carbon fundamentals. We have some platform-specific stuff directly using Carbon global menu APIs (it's more low-level and flexible than Cocoa, and we don't need to be 64-bit).
It seems that GTK swallows up all the keyboard events before Carbon dispatches them as commands. This makes sense, because there is no mapping of Carbon commands into the GTK world. In general, this isn't a problem, because we have a global key event handler and dispatch everything via our own command system. However, this seems to be preventing Cmd-? from opening the Help search menu, and I cannot find a way to do this programmatically.
Menu Manager's MenuSelect function is promising, but I haven't figured out a way to determine the coordinate automatically, and for some reason it only works when I hit the combination twice...
Alternatively, a way to dispatch the Cmd-? keystroke to Carbon's command handling or synthesize the command event directly would be good, but I haven't had any luck in that area.
Carbon's ProcessHICommand isn't any use without a command ID and I can't figure out what it is (if there is one)
Regarding Cocoa, I can get hold of the NSWindow and call InterpretKeyEvents, but I haven't had any luck success synthesizing the NSEvent - it just beeps. The event I'm using is
var evt = NSEvent.KeyEvent (NSEventType.KeyDown, System.Drawing.PointF.Empty,
NSEventModifierMask.CommandKeyMask | NSEventModifierMask.ShiftKeyMask,
0, win.WindowNumber, NSGraphicsContext.CurrentContext, "?", "?",
false, (ushort) keycode);
Keycode is determined from a GTK keymap to be 44. I confirmed that the keycode was correct using a plain MonoMac (Cocoa) app but InterpretKeyEvents did not work with the event in that app either. And I can't find any selector associated with the command.
You can use accessibility APIs to fake a press on the menu item.
NSString *helpMenuTitle = [[[[NSApplication sharedApplication] mainMenu] itemWithTag:HELP_MENU_TAG] title];
AXUIElementRef appElement = AXUIElementCreateApplication(getpid());
AXUIElementRef menuBar;
AXError error = AXUIElementCopyAttributeValue(appElement,
kAXMenuBarAttribute,
(CFTypeRef *)&menuBar);
if (error) {
return;
}
CFIndex count = -1;
error = AXUIElementGetAttributeValueCount(menuBar, kAXChildrenAttribute, &count);
if (error) {
CFRelease(menuBar);
return;
}
NSArray *children = nil;
error = AXUIElementCopyAttributeValues(menuBar, kAXChildrenAttribute, 0, count, (CFArrayRef *)&children);
if (error) {
CFRelease(menuBar);
return;
}
for (id child in children) {
AXUIElementRef element = (AXUIElementRef)child;
id title;
AXError error = AXUIElementCopyAttributeValue(element,
kAXTitleAttribute,
(CFTypeRef *)&title);
if ([title isEqualToString:helpMenuTitle]) {
AXUIElementPerformAction(element, kAXPressAction);
CFRelease(title);
break;
}
CFRelease(title);
}
CFRelease(menuBar);
[children release];
You could do this via calling from C / Objective-C a AppleScript (GUI) script , that would essentially do the pointing and clicking of a user just as a user would do, to open the help menu program-matically.

Send a MouseEvent to a NSView (WebView) from code

i am trying to send mouseevent (MouseClick or RightMouseClick) to a NSView... in my case a WebView, that contains a loaded Website. I want to script some clicks on links etc. How is it possible to create a NSEvent and send it to the WebView to perform clicks?
Thanks a lot
ksman
This is a bit late; maybe you found it, maybe you didn't. I recently had to figure this out, though, so I'll document it here and hopefully help someone.
NSPoint where;
where.x = x;
where.y = y;
NSView* resp = [someView hitTest:where];
NSEventType evtType = NSLeftMouseDown;
NSEvent *mouseEvent = [NSEvent mouseEventWithType:evtType
location:where
modifierFlags:nil
timestamp:GetCurrentEventTime()
windowNumber:0
context:[o->helper context]
eventNumber:nil
clickCount:1
pressure:nil];
[resp mouseDown:mouseEvent];

Getting "global" mouse position in Mac OS X

How can I get in Mac OS X "global" mouse position - I mean how can I in cocoa/cf/whatever find out cursor position even if it's outside the window, and even if my window is inactive?
I know it's somehow possible (even without admin permissions), because I've seen something like that in Java - but I want to write it in ObjC
Sorry for my English - I hope you'll understand what I mean ;)
NSPoint mouseLoc;
mouseLoc = [NSEvent mouseLocation]; //get current mouse position
NSLog(#"Mouse location: %f %f", mouseLoc.x, mouseLoc.y);
If you want it to continuously get the coordinates then make sure you have an NSTimer or something similar
Matt S. is correct that if you can use the NSEvent API to get the mouse location. However, you don't have to poll in order to continuously get coordinates. You can use a CGEventTap instead:
- (void) startEventTap {
//eventTap is an ivar on this class of type CFMachPortRef
eventTap = CGEventTapCreate(kCGHIDEventTap, kCGHeadInsertEventTap, kCGEventTapOptionListenOnly, kCGEventMaskForAllEvents, myCGEventCallback, NULL);
CGEventTapEnable(eventTap, true);
}
CGEventRef myCGEventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) {
if (type == kCGEventMouseMoved) {
NSLog(#"%#", NSStringFromPoint([NSEvent mouseLocation]));
}
return event;
}
This way, your function myCGEventCallback will fire every time the mouse moves (regardless of whether your app is frontmost or not), without you having to poll for the information. Don't forget to CGEventTapEnable(eventTap, false) and CFRelease(eventTap) when you're done.
If you're not in cocoa land and an event tap is not appropriate for the situation,
CGEventRef event = CGEventCreate(nil);
CGPoint loc = CGEventGetLocation(event);
CFRelease(event);
works.
For a continuous update on the global cursor position, here is what the code looks like (Swift 4.0.3):
override func viewDidLoad()
{
super.viewDidLoad()
NSEvent.addGlobalMonitorForEvents(matching: NSEvent.EventTypeMask.mouseMoved, handler: {(mouseEvent:NSEvent) in
let position = mouseEvent.locationInWindow
})
}
Note, the variable 'position' is an NSPoint object with x and y coordinates (among many other attributes). This will not return the coordinates if your application is in focus or when you are clicking and dragging. There are separate events for all of those: simply change the NSEvent.EventTypeMask.mouseMoved to a different EventTypeMask
Matt is correct, but in order to continuously get the coordinates, I believe the other option is to use event monitor provided by NSEvent. Try addGlobalMonitorForEventsMatchingMask:handler: and addLocalMonitorForEventsMatchingMask:handler:. Check NSEvent Class Reference for more detail.

Resources