How to receive NSNotifications while manually using NSRunloop - cocoa

I can observe NSWorkspaceWillPowerOffNotification notifications in the application delegate of a GUI program, but not in a command line utility. Any ideas why the following does not work? Or is there a different approach I should consider?
Edit: Cold it be that NSWorkspace's notification center doesn't work for a GUI-less program? (I have verified that |notificationCenter| is not nil)
#interface Helper : NSObject
-(void)userLogout:(NSNotification*)notification;
#end
#implementation Helper
-(void)userLogout:(NSNotification*)notification
{
NSLog(#"Notification Received");
}
#end
int main(int argc, const char * argv[])
{
#autoreleasepool {
Helper* helper = [[Helper alloc] init];
NSNotificationCenter* notificationCenter = [[NSWorkspace sharedWorkspace] notificationCenter];
[notificationCenter addObserver:helper
selector:#selector(userLogout:)
name:NSWorkspaceWillPowerOffNotification
object:nil];
NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
[runLoop run];
}
return 0;
}

Related

OSX LaunchAgent LaunchDaemon Communication over XPC

I'm trying to communicate my xpc service from LaunchDaemon.
LaunchDaemon is command line executable and LaunchAgent is xpc service.
Initiating my connection from main service(LaunchDaemon) like this:
NSXPCConnection * _connectionToService;
_connectionToService = [[NSXPCConnection alloc] initWithMachServiceName:#"com.XpcService" options:NSXPCConnectionPrivileged];
_connectionToService.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:#protocol(XpcServiceProtocol)];
_connectionToService.interruptionHandler = ^{ NSLog(#"Connection Terminated"); };
_connectionToService.invalidationHandler = ^{ NSLog(#"Connection Invalidated"); };
[_connectionToService resume];
//here calling required functions
Xpc-service listening like this:
#interface ServiceDelegate : NSObject <NSXPCListenerDelegate>
#end
#implementation ServiceDelegate
-(BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection: (NSXPCConnection *)newConnection {
newConnection.exportedInterface = [NSXPCInterface interfaceWithProtocol:#protocol(XpcServiceProtocol)];
XpcService *exportedObject = [XpcService new];
newConnection.exportedObject = exportedObject;
[newConnection resume];
return YES;
}
#end
int main(int argc, const char *argv[]) {
ServiceDelegate *delegate = [ServiceDelegate new];
NSXPCListener *listener = [[NSXPCListener alloc] initWithMachServiceName:#"com.XpcService"];
listener.delegate = delegate;
[listener resume];
return 0;
}
In my case i'm getting Connection Invalidated error, my xpc couldn't even be started.
LaunchAgent & LaunchDaemon loaded perfectly, code signing was also be done. help me to find out what might be caused the problem? Thanks in advance.

NSWorkspaceWillPowerOffNotification never called

I am trying to run a program in a background process that will register every shutdown event in the system.
Doing so by registering to NSWorkspaceWillPowerOffNotification as show below:
#import <AppKit/AppKit.h>
#interface ShutDownHandler : NSObject <NSApplicationDelegate>
- (void)computerWillShutDownNotification:(NSNotification *)notification;
#end
int main(int argc, char* argv[]) {
NSNotificationCenter *notCenter;
notCenter = [[NSWorkspace sharedWorkspace] notificationCenter];
ShutDownHandler* sdh = [ShutDownHandler new];
[NSApplication sharedApplication].delegate = sdh;
[notCenter addObserver:sdh
selector:#selector(computerWillShutDownNotification:)
name:NSWorkspaceWillPowerOffNotification
object:nil];
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[[NSFileManager defaultManager] createFileAtPath:#"./output.txt" contents:nil attributes:nil];
});
[[NSRunLoop currentRunLoop] run];
return 0;
}
#implementation ShutDownHandler
- (void)computerWillShutDownNotification:(NSNotification *)notification {
NSFileHandle* file = [NSFileHandle fileHandleForUpdatingAtPath: #"./output.txt"];
[file seekToEndOfFile];
NSDateFormatter* fmt = [NSDateFormatter new];
[fmt setDateFormat:#"yyyy-MM-dd HH:mm:ss"];
NSDate* current = [NSDate date];
NSString* dateStr = [fmt stringFromDate:current];
[dateStr writeToFile:#"./output.txt" atomically:YES encoding:NSUTF8StringEncoding error:nil];
}
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
{
return NSTerminateCancel;
}
#end
For some reason that I cannot understand, the NSWorkspaceWillPowerOffNotification handler is never called!
Also added the following to my .plist:
<key>NSSupportsSuddenTermination</key>
<false/>
but still no notification is register when shutting down my system.
Any idea as to why??
For future reference:
I was not able to register to the above event and see it fire.
Finally, I went with a different approach:
apple open-source power management
Here, you can see we have notifications names such as:
"com.apple.system.loginwindow.logoutNoReturn"
You can register to those, and that will do the trick.
Hope this will help someone one day :)
When you say you've ran your code as a background process, do you mean that it's based on launchd daemon handled according to plist file in /Library/LaunchDeamon ? In this case, the notification will not be sent. Try running it under Cocoa application
Seems like this notification doesn't work on LaunchDaemon linked against AppKit.
I'm still looking for a way to get these notifications on background deamon process.
i can make NSWorkspaceWillPowerOffNotification and applicationShouldTerminate: to work by adding NSApplicationActivationPolicyAccessory and replacing the run loop with [NSApp run]. it still won't be very useful if you want to continue to monitor all logouts (and/or power off attempts) since it only works if the process is launched while the user has already logged in, and it can only detect the current UI login session's power-off/logout notification and "app should terminate" delegate.
"apps" that belong to the current UI login session get terminated by the OS when the user logs out. non-app (no [NSApp run]) processes will continue to run even after the user logs out.
so, if you wan to continue to monitor the user logouts (or power off attempts), you would need a non-app (no [NSApp run]) process.
NSWorkspaceSessionDidBecomeActiveNotification and NSWorkspaceSessionDidResignActiveNotification work without [NSApp run] if you want to monitor if the user switched out or back in.
or you can try the System Configuration APIs if it works for your use case.
"Technical Q&A QA1133: Determining console user login status" https://developer.apple.com/library/archive/qa/qa1133/_index.html
sounds like com.apple.system.loginwindow.logoutNoReturn was the best option for whatever you wanted to do. good to know!
here is the updated code (although it's not really useful):
// cc powreoff-notification.m -o poweroff-notification -framework AppKit
#import <AppKit/AppKit.h>
#interface ShutDownHandler : NSObject <NSApplicationDelegate>
- (void)computerWillShutDownNotification:(NSNotification *)notification;
#end
int main(int argc, char* argv[]) {
#autoreleasepool {
NSLog(#"%s", __func__);
NSNotificationCenter* notCenter = [[NSWorkspace sharedWorkspace] notificationCenter];
ShutDownHandler* sdh = [ShutDownHandler new];
[NSApplication sharedApplication].delegate = sdh;
[notCenter addObserver:sdh
selector:#selector(computerWillShutDownNotification:)
name:NSWorkspaceWillPowerOffNotification
object:nil];
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[[NSFileManager defaultManager] createFileAtPath:#"./output.txt" contents:nil attributes:nil];
});
// without NSApplicationActivationPolicyAccessory, the [NSApp run] process gets killed on
// poweroff/logout instead of getting the power off notification or applicationShouldTerminate:.
[NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory];
// without [NSApp run], you won't get the power off notification or applicationShouldTerminate:.
//[[NSRunLoop currentRunLoop] run];
[NSApp run];
// this process needs to be launched while the user has already logged in. otherwise,
// you won't get the power off notification or applicationShouldTerminate:.
}
return 0;
}
#implementation ShutDownHandler
- (void)computerWillShutDownNotification:(NSNotification *)notification {
NSLog(#"%s", __func__);
NSFileHandle* file = [NSFileHandle fileHandleForUpdatingAtPath: #"./output.txt"];
[file seekToEndOfFile];
NSDateFormatter* fmt = [NSDateFormatter new];
[fmt setDateFormat:#"yyyy-MM-dd HH:mm:ss\n"];
NSDate* current = [NSDate date];
NSString* dateStr = [fmt stringFromDate:current];
[dateStr writeToFile:#"./output.txt" atomically:YES encoding:NSUTF8StringEncoding error:nil];
}
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
{
NSLog(#"%s", __func__);
return NSTerminateCancel;
}
#end

addGlobalMonitorForEventsMatchingMask not working

I am having trouble with getting an assistive-enabled application (XCode in the development case) to capture global keyDown events. I've seen lots of code examples like the below, but this doesn't work for me on 10.9.4.
#import <AppKit/AppKit.h>
#import <Foundation/Foundation.h>
// 10.9+ only, see this url for compatibility:
// http://stackoverflow.com/questions/17693408/enable-access-for-assistive-devices-programmatically-on-10-9
BOOL checkAccessibility()
{
NSDictionary* opts = #{(__bridge id)kAXTrustedCheckOptionPrompt: #YES};
return AXIsProcessTrustedWithOptions((__bridge CFDictionaryRef)opts);
}
int main(int argc, const char * argv[])
{
#autoreleasepool {
if (checkAccessibility()) {
NSLog(#"Accessibility Enabled");
}
else {
NSLog(#"Accessibility Disabled");
}
NSLog(#"registering keydown mask");
[NSEvent addGlobalMonitorForEventsMatchingMask:NSKeyDownMask
handler:^(NSEvent *event){
NSLog(#"keydown: %#", event.characters);
}];
NSLog(#"entering run loop.");
[[NSRunLoop currentRunLoop] run];
}
return 0;
}
The output received is:
2014-08-25 17:26:36.054 test[64725:303] Accessibility Enabled
2014-08-25 17:26:36.055 test[64725:303] registering keydown mask
2014-08-25 17:26:36.067 test[64725:303] entering run loop.
Once here, no other logging occurs, regardless of which keys I hit or what application has focus when I hit them.
FWIW, I'm trying to write an assistive application, not a key-logger or other evil thing. I've looked at the other instances of this question, but they seem to deal with either 1) the application not being assistive-enabled or 2) not receiving certain 'special' command keys that one would need CGEvents to receive. I am not seeing any keys, even simple ones (it's been running through my typing of this post and nothing was logged). TIA!
So, thanks to Ken Thomases' question above, I was able to work out how to do this. The key detail is that I am using a command line application template (I don't have any need for a UI, so I was trying to keep things minimal). News to me, but obvious in hindsight, just creating a run loop doesn't create an event loop. In order to replicate the creation of an event loop within a command line application, more of the guts of a typical cocoa application have to be brought into play. First, you have to have a class implementing the NSApplicationDelegate protocol, and that delegate will be where the application code lives, leaving the main method to simply do the following:
#import <Foundation/Foundation.h>
#include "AppDelegate.h"
int main(int argc, const char * argv[])
{
#autoreleasepool {
AppDelegate *delegate = [[AppDelegate alloc] init];
NSApplication * application = [NSApplication sharedApplication];
[application setDelegate:delegate];
[NSApp run];
}
}
This is a nib-less, menubar-less application, just like the usual command line application template, but it does have a true event loop, due to the [NSApp run] call. Then the application code that I used to have in my main method above moved into the app delegate:
#import "AppDelegate.h"
#implementation AppDelegate
// 10.9+ only, see this url for compatibility:
// http://stackoverflow.com/questions/17693408/enable-access-for-assistive-devices-programmatically-on-10-9
BOOL checkAccessibility()
{
NSDictionary* opts = #{(__bridge id)kAXTrustedCheckOptionPrompt: #YES};
return AXIsProcessTrustedWithOptions((__bridge CFDictionaryRef)opts);
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
if (checkAccessibility()) {
NSLog(#"Accessibility Enabled");
}
else {
NSLog(#"Accessibility Disabled");
}
NSLog(#"registering keydown mask");
[NSEvent addGlobalMonitorForEventsMatchingMask:NSKeyDownMask
handler:^(NSEvent *event){
NSLog(#"keydown: %#", event.characters);
}];
}
#end
And just for completeness sake and future readers, the header file looks like this:
#import <AppKit/AppKit.h>
#import <Foundation/Foundation.h>
#interface AppDelegate : NSObject <NSApplicationDelegate>
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification;
#end

On Snow Leopard, why is -[<ABImageClient> consumeImageData:forTag:] not being called after [ABPerson beginLoadingImageDataForClient:] is called?

I'm trying to load image date for person entries in the shared ABAddressBook. In particular, I'm calling
-[ABPerson beginLoadingImageDataForClient:]
and passing as the argument an object which adopts ABImageClient and implements
-[<ABPersonClient> consumeImageData:forTag:]
The approach I'm using works fine on Mountain Lion, but fails on Snow Leopard. In particular, -consumeImageData:forTag: never gets called.
The following sample command line program demonstrates my approach:
#import <Foundation/Foundation.h>
#import <AddressBook/AddressBook.h>
#interface ImageConsumer : NSObject <ABImageClient>
#property (nonatomic, strong) NSMutableDictionary *imagesForNumbers;
#end
#implementation ImageConsumer
- (id)init
{
self = [super init];
if (self) {
self.imagesForNumbers = [NSMutableDictionary dictionary];
}
return self;
}
- (void)consumeImageData:(NSData *)data forTag:(NSInteger)tag
{
[self.imagesForNumbers setObject:data forKey:[NSNumber numberWithInteger:tag]];
NSLog(#"%s: loaded data of length %zu for tag %zd", __PRETTY_FUNCTION__, data.length, tag);
}
#end
int main(int argc, const char * argv[])
{
#autoreleasepool {
NSArray *persons = [[ABAddressBook sharedAddressBook] people];
ImageConsumer *imageConsumer = [[ImageConsumer alloc] init];
for (ABPerson *person in persons) {
NSInteger loadingKey = [person beginLoadingImageDataForClient:imageConsumer];
NSLog(#"requested data person named %# %# and received tag %zd", [person valueForProperty:kABFirstNameProperty], [person valueForProperty:kABLastNameProperty], loadingKey);
}
[[NSRunLoop mainRunLoop] run];
}
return 0;
}
Am I misusing the ABAddressBook calls for loading image data for instances of ABPerson? Is this a bug with ABAddressBook on Snow Leopard? If so, is there a work-around?

Can't get NSMouseMoved events from nextEventMatchingMask with an NSOpenGLView

I'm trying to create a basic opengl window with mouse input using a typical win-style event loop. The problem is that I'm pulling my hair out trying to get a NSMouseMoved event to generate. The following code outputs debug info on Mouse Up, Mouse Down, Mouse Drag, etc but no Mouse Move even though I've told the window setAcceptsMouseMovedEvents:YES. So, any ideas on how to get mouse move to work in the following example?
Obviously, the way I'm creating the window is very un-cocoa like, but I'm trying to port a makefile-based windows c++ codebase that does some tricky threading things. That's why I'm sticking with a style similar to a win32 loop using GetMsg().
Also, to build I'm just using:
gcc -o hellogl hellogl.m -framework Foundation -framework Cocoa -framework OpenGL
Thanks for the help!
#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
#include <OpenGL/gl.h>
#include <stdlib.h>
#interface BaseWinDelegate : NSWindow<NSWindowDelegate>
#end
#implementation BaseWinDelegate
- (void) windowWillClose:(NSNotification*)notification
{
printf("Closing.\n");
NSEvent * evt = [NSEvent otherEventWithType:NSApplicationDefined
location: NSMakePoint(0,0)
modifierFlags: 0
timestamp: 0.0
windowNumber: 0
context: nil
subtype: 0
data1: 0
data2: 0];
[NSApp postEvent:evt atStart:NO];
}
#end
#interface BaseView : NSOpenGLView
- (void) update;
- (void) drawRect:(NSRect)rect;
- (void) reshape;
#end
#implementation BaseView
- (void) drawRect:(NSRect)rect
{
glClearColor(0.2f,0.2f,0.2f,0.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
[[self openGLContext] flushBuffer];
}
- (void) update
{
printf("Update.\n");
}
- (void) reshape
{
NSRect rect;
[[self openGLContext] update];
rect = [self bounds];
printf("Reshape - %f, %f\n", rect.size.width,rect.size.height);
}
#end
int main(int argc, const char * argv[])
{
printf("Starting.\n");
NSAutoreleasePool * myPool = [[NSAutoreleasePool alloc] init ];
NSApplicationLoad();
NSRect rect = NSMakeRect(100,100,640,480);
NSWindow * win = [[NSWindow alloc] initWithContentRect:rect
styleMask:NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask
backing: NSBackingStoreBuffered
defer: NO];
NSOpenGLPixelFormatAttribute attributes[] =
{
NSOpenGLPFADoubleBuffer,
0
};
NSOpenGLPixelFormat* pf = [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes];
BaseView * pView = [[BaseView alloc] initWithFrame:rect pixelFormat:pf];
BaseWinDelegate * myDelegate = [BaseWinDelegate alloc];
[win setDelegate:myDelegate];
[win setContentView: pView];
[win makeKeyAndOrderFront: NSApp];
[win setAcceptsMouseMovedEvents:YES];
do
{
NSEvent * evt = [NSApp nextEventMatchingMask : NSAnyEventMask
untilDate : [NSDate distantFuture]
inMode : NSDefaultRunLoopMode
dequeue : YES ];
NSEventType evtType = [evt type];
if (evtType == NSApplicationDefined)
{
break;
}
printf("%d\n",(int)evtType);
[NSApp sendEvent : evt];
} while (1);
[myPool drain];
return EXIT_SUCCESS;
}
Ok, figured it out. The problem is that it's not a foreground process. So adding this code fixes it.
ProcessSerialNumber psn;
GetCurrentProcess(&psn);
TransformProcessType(&psn, kProcessTransformToForegroundApplication);
SetFrontProcess(&psn);
That was preventing the window from becoming the key window and sending off mouse moved events. Hope it helps someone else (i.e. the 0.1% of people who like to handle their own event loop).

Resources