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
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.
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
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
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?
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).