I've successfully got a demo app intercepting keyboard events. Here's the handler for them.
CGEventRef keyUpCallback (CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) {
NSLog(#"KeyUp event tapped!");
return;
}
I want to do different things depending on which application sent the event. How can I tell which application it is?
The application that is receiving keystrokes is presumably the active application, so you could handle the event differently depending on which application is active. You can use the activeapplication method from NSWorkspace to get the name of the active application.
See also this thread about getting the active application.
Here how it goes:
int64_t processIdTarget = CGEventGetIntegerValueField(event, kCGEventTargetUnixProcessID);
int64_t processIdSource = CGEventGetIntegerValueField(event, kCGEventSourceUnixProcessID);
processIdSource shows you application sender and processIdTarget stands for receiver.
For example you can open virtual keyboard and send events with it. As virtual keyboard is process in user space you will get it's pid as processIdSource. But for most of cases you will get 0 as processIdSource.
After you got application's pid you can create NSRunningApplication instance and get bunch of information from it.
This shows process ID of the application that sent the event:
NSLog(#"Target PID:%lld",CGEventGetIntegerValueField(event, kCGEventTargetUnixProcessID));
Related
I'm trying to send a toast notification with a parameter to a Windows Phone 8.1 device, so when a user taps on the toast the app will show a particular page within the app, as it described in this MSDN article https://msdn.microsoft.com/library/windows/apps/jj662938(v=vs.105).aspx
This is a snippet that I use to add a parameter:
<wp:Param>?cmd=command1 </wp:Param>
this how I read it in the app:
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e){
base.OnNavigatedTo(e);
string strVal1 = this.NavigationContext.QueryString["cmd"];
}
After making these changes it works correctly, but only for this first notification. In all subsequent notifications the value of the parameter read within the app is same as it was in the first notification, no matter what I've sent from the server.
Here is an example:
I'm sending a notification to device that has with this parameter in the notification payload: <wp:Param>?cmd=command1 </wp:Param> The value in this.NavigationContext.QueryString("cmd") is command1, as it should be.
Then I send a second notification with this parameter parameter
<wp:Param>?cmd=command2 </wp:Param>
the cmd's value in the this.NavigationContext.QueryString("cmd") is still returned as "command1" instead of command2 as it should be. And it works like that for all subsequent notifications until I force-restart the app.
Here is what I've tried:
Checked the Uri value in the NavigationEventArgs argument of the OnNavigatedTo event and it's same as what I see in the NavigationContext.
Double checked notification payload sent from the server (it's correct)
Checked msdn/stackoverflow/google.
Checked on both 8.1 emulator and the device (it works the same)
Questions:
Do I have to do something in the app (idk, clear NavigationContext or something), so it will be different for the next notification?
Does anyone actually has a live-app that uses partial arguments (not server-driven navigation with full url, but a partial url passed to the app + corresponding navigation from within the app) and it works for multiple subsequent notifications?
Figured out what the problem was. Two OnNavigated events are fired when app is relaunched. First one has a type of System.Windows.Navigation.NavigationMode.Reset and it has the data of the old page being reset (in my case, with the data from old notification). The second one is System.Windows.Navigation.NavigationMode.New and has the data from the new notification.
A simple check of navigation mode to make sure it's of the type .New fixed the issue:
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e){
base.OnNavigatedTo(e);
if (e.NavigationMode == System.Windows.Navigation.NavigationMode.New)){
// custom navigation logic based on the data within <wp:Param/>
}
}
When Mission Control runs, it prevents applications from receiving keyboard and mouse events. It also leaves the last application running thinking that it still has focus. This is a problem for me because I don't receive keyUp or mouseUp events if I start Mission Control with a mouse button or a key held down and my application will behave as if that mouse button or key is held down.
I would like a way to either read both keyboard and mouse events even when Mission Control is active, or a way of detecting that Mission Control is active. Ideally, I would like to be able to do the latter since I effectively can't use my application when Mission Control is running.
I've tried a couple of things with no luck:
Use addGlobalMonitorForEventsMatchingMask to register a global monitor for keyboard and mouse events. This captures mouse events (but not keyboard events, although the documentation says keyDown events should be sent to the global monitor) when I switch to another application, but Mission Control doesn't seem to let events propagate to global monitors.
Check [[NSRunningApplication currentApplication] {isActive, ownsMenuBar}].
Apparently, my application is active even though it's not receiving events!
Check [NSApp keyWindow] != nil.
Apparently, one of my windows should be receiving key events. None of them are.
Check if Mission Control is one of the running applications returned by [NSWorkspace runningApplications]. Mission Control does not show up in this list when it's running.
Edit:
I've finally worked around this problem (albeit not in a very satisfactory way). For the mouse, it turns out that you can query the state of the pressed buttons with [NSEvent pressedMouseButtons]. I simply keep track of what I think the mouse state should be from NSLeftMouseDown and NSLeftMouseUp events and compare that to [NSEvent pressedMouseButtons] every so often to make sure that they're consistent. If they're not, then I know that something has hijacked my NSLeftMouseUp event and act accordingly.
For the keyboard, I could not find a way to query the keyboard state, so I couldn't do a similar workaround. I ended up disabling application switching using presentation options when keys are pressed.
At least in OS X 10.10, you can use this code to check if Mission Control is active or not:
func missionControlIsActive() -> Bool
{
var result: Bool = false
let windowInfosRef = CGWindowListCopyWindowInfo(CGWindowListOption(kCGWindowListOptionOnScreenOnly), CGWindowID(0)) // CGWindowID(0) is equal to kCGNullWindowID
let windowList: NSArray = windowInfosRef.takeRetainedValue() // We own the returned CFArrayRef
for entry in windowList
{
if (entry.objectForKey("kCGWindowOwnerName") as! String) == "Dock"
{
var bounds: NSDictionary = entry.objectForKey("kCGWindowBounds") as! NSDictionary
if (bounds.objectForKey("Y") as! NSNumber) == -1
{
result = true
}
}
}
return result
}
In a nutshell, the code checks if a specific window owned by the OS X Dock process is visible on the screen and if it is in a specific position. If both conditions are met, Mission Control will be active right now. Code will work in a sandboxed app and no privileges for assistive devices are required.
Did you try on bash level using NSTask? Something like ps -faxU <username> should list all running processes and then you could parse the output, or indeed you could use ps -faxU <username> | grep -i "mission control" (At the top of my head I am not sure how the process may be called, but sth like "mission control" seems legit). Not the most elegant solution maybe, but if nothing else works it may be worth it.
May be i'm missing something, but have you tried to use event taps instead of global monitoring?
It does appear that DTrace has some ability to see Mission Control being activated. Try running:
sudo fs_usage -filesys | grep Mission
from the command line and then launching the Mission Control app from the /Application folder.
You should see a lot of output related to Mission Control starting up. Unfortunately, this same output did not appear by using the keyboard short cut or swiping. Of course, using DTrace in production code is not something I would actually recommend.
C++ and Qt implementation works in latest OS X.
bool Window::missionControlIsActive() {
bool result = false;
CFArrayRef windows = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly+kCGWindowListExcludeDesktopElements, kCGNullWindowID);
for (int i = 0; i < CFArrayGetCount(windows) ; i++) {
auto cfMutableDictionaryRef_dict = (CFMutableDictionaryRef)CFArrayGetValueAtIndex( windows, i );
auto cfStringRef_name = (CFStringRef)CFDictionaryGetValue(cfMutableDictionaryRef_dict, kCGWindowName);
if (QString::fromCFString(cfStringRef_name) != u"") continue;
auto cfStringRef_ownerName = (CFStringRef)CFDictionaryGetValue(cfMutableDictionaryRef_dict, kCGWindowOwnerName);
if (QString::fromCFString(cfStringRef_ownerName) != u"Dock") continue;
auto cfDictRef_bounds = (CFDictionaryRef)CFDictionaryGetValue(cfMutableDictionaryRef_dict, kCGWindowBounds);
auto cfNumRef_bounds_Y = (CFNumberRef)CFDictionaryGetValue(cfDictRef_bounds, QString("Y").toCFString());
double num;
CFNumberGetValue(cfNumRef_bounds_Y, kCFNumberFloat64Type, &num);
if (num > 1.0 and num < 1000000) continue;
result = true;
break;
}
CFRelease(windows);
return result;
}
Using this code, I may register a global event handler:
[NSEvent addGlobalMonitorForEventsMatchingMask: NSKeyDownMask
handler: ^(NSEvent *incomingEvent) {
NSString *chars = [[incomingEvent characters] lowercaseString];
unichar character = [chars characterAtIndex:0];
// do something useful
NSLog(#"keydown globally! Which key? This key: %c", character);
}];
Unfortunately, events get passed along to this monitor, if support for assistive devices is enabled. Without assistive devices being enabled, no events get passed along.
Form the documentation:
Key-related events may only be monitored if accessibility is enabled or if your
application is trusted for accessibility access (see AXIsProcessTrusted).
I wonder, if another method exists, which passes along events without forcing the user to enable specific features of OS X.
While I didn't find a solution in Apple's docs, a solution must exist. E.g. the MAS-downloaded version of Alfred allows to define a hotkey.
Interestingly, Alfred's preferences only shows special keys and points out, that certain special key combinations may not work.
Since I basically want to show / hide a 'global' non-activating panel, I probably should simply prepare a system service. Should I?
You could try creating a Quartz Event Tap, with kCGSessionEventTap as the location.
Quartz Event Services Reference
Sample code from Mac OS X Internals
I would like to be able to gather info like how often certain windows are opened, what types of user data are accessed, how often menu items are clicked, etc. Does anyone know of a 3rd party (open source or commercial) Cocoa/Obj-C library or plugin that would allow me to gather this info?
I have used pinch media in the past, and they merged with Flurry. Library was simple to use and was setup in around 40 minutes.
I don't know any library for that but at least to get informed about when the user switches the front application you can install an event handler like this:
EventTypeSpec eventType;
eventType.eventClass = kEventClassApplication;
eventType.eventKind = kEventAppFrontSwitched;
EventHandlerUPP handlerUPP = NewEventHandlerUPP(FrontAppSwitchedDetector_callback);
OSStatus status=InstallApplicationEventHandler(handlerUPP,1,&eventType,self,&_eventHandlerRef);
... and when receiving an callback you may get the current front application process:
pascal OSStatus FrontAppSwitchedDetector_callback(EventHandlerCallRef nextHandler,EventRef theEvent,void* userData)
{
ProcessSerialNumber newSerial;
GetFrontProcess(&newSerial);
//to something with that ....
return (CallNextEventHandler(nextHandler, theEvent));
}
I'm using some Carbon code in my Cocoa project for handling global key events (shortcuts) from other applications. Currently I have setup a kEventHotKeyReleased event handler and I can successfully obtain hot keys when my application is not active. That triggers some operation in my application.
The problem I have with the behavior of kEventHotKeyReleased is:
Say for example I press the Cmd-Shift-P key combination. As soon as I release the "P" key the hot key event is triggered. I need to be able to trigger the event (or manually trigger it) when all of the keys are unpressed (i.e: the Cmd and Shift keys are released too).
It is easy to monitor for hot keys but I have seen nothing for monitoring individual keystrokes. If I could monitor the modifier key states I would be in business.
Any hints on how to do this?
Thanks in advance!
UPDATE:
I've tried using kEventRawKeyUp and kEventRawKeyModifiersChanged but while kEventHotKeyReleased works those two don't even though I set them up in the exact same way as kEventHotKeyReleased.
EventTypeSpec eventTypes[] = {{kEventClassKeyboard, kEventHotKeyReleased}, {kEventClassKeyboard, kEventRawKeyUp}};
// Changing the order in the list does not help, nor does removing kEventHotKeyReleased
OSStatus err = InstallApplicationEventHandler(&globalHotkeyHandler, GetEventTypeCount(eventTypes), eventTypes, NULL, NULL);
// err == noErr after this line
The globalHotKeyHandler method is called for kEventHotKeyReleased, but not for kEventRawKeyUp for some reason I can't seem to grasp. Here's what my globalHotKeyHandler method looks like:
OSStatus globalHotkeyHandler(EventHandlerCallRef nextHandler, EventRef anEvent, void *userData) {
NSLog(#"Something happened!");
}
Is there an additional call that needs to be made or something else I forgot?
N.B: At first glance, it seems like it could be that Access for Assistive Devices is disabled but it is not. So I'm pretty clueless.
UPDATE 2:
I investigated a bit on the CGEventTap Leibowitzn suggested and I came up with this setup:
CFMachPortRef keyUpEventTap = CGEventTapCreate(kCGHIDEventTap,kCGHeadInsertEventTap,kCGEventTapOptionListenOnly,kCGEventKeyUp,&keyUpCallback,NULL);
CFRunLoopSourceRef keyUpRunLoopSourceRef = CFMachPortCreateRunLoopSource(NULL, keyUpEventTap, 0);
CFRelease(keyUpEventTap);
CFRunLoopAddSource(CFRunLoopGetCurrent(), keyUpRunLoopSourceRef, kCFRunLoopDefaultMode);
CFRelease(keyUpRunLoopSourceRef);
... and the callback:
CGEventRef keyUpCallback (CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) {
NSLog(#"KeyUp event tapped!");
return event;
}
As you can see I'm using kCGEventKeyUp as the mask for the event tap but somehow I'm receiving mouse down events ??!??
UPDATE 3:
Ok forget that, I overlooked the line in the doc that said to use CGEventMaskBit(kCGEventKeyUp) for this parameter, so the correct call is:
CGEventTapCreate(kCGHIDEventTap,kCGHeadInsertEventTap,kCGEventTapOptionListenOnly,CGEventMaskBit(kCGEventKeyUp),&keyUpCallback,NULL);
I'm still having a problem though: modifier keys do not trigger the kCGEventKeyUp...
UPDATE 4:
Ok forget that again... I'm bound to answer to my own questions 5 minutes after asking them today huh!
To intercept modifier keys, use kCGEventFlagsChanged:
CGEventTapCreate(kCGHIDEventTap,kCGHeadInsertEventTap,kCGEventTapOptionListenOnly,CGEventMaskBit(kCGEventFlagsChanged),&callbackFunction,NULL);
So in essence I got the key and modifier key state detection working, but I'm still interested in knowing why kEventRawKeyUp doesn't work...
N.B: Also note that I'm developing on Tiger with the goal of having support for new and older versions of the OS as much as possible. CGEventTap is 10.4+ only so I'll be using this for now but a backwards-compatible solution would be welcome.
One option is to use EventTaps. This lets you monitor all keyboard events. See:
http://developer.apple.com/mac/library/documentation/Carbon/Reference/QuartzEventServicesRef/Reference/reference.html#//apple_ref/c/func/CGEventTapCreate
Unfortunately event taps will stop working if an application is requesting secure input. For example Quicken.
OSStatus err = InstallApplicationEventHandler(&globalHotkeyHandler, GetEventTypeCount(eventTypes), eventTypes, NULL, NULL);
This is not global. This installs the handler only when your own application is active, and (I believe) after the Carbon Event Manager's own event filters.
You need to use InstallEventHandler, which takes an event target as its first parameter (InstallApplicationEventHandler is a macro that passes the application event target).
For events that occur while your application is not active, the target you want is GetEventMonitorTarget(). For events that occur while your application is active, the target you want is GetEventDispatcherTarget(). To catch events no matter what application is active, install your handler on both targets.
Nowadays, though, I'd just use CGEventTaps, as Leibowitzn suggested.