16

I want to monitor screensaver and lockscreen events on an OSX box. As a first pass, I'm fine with them just printing to the console.

Following the advice of another's question, I wrote some Objective C to listen for Cocoa Notifications for the com.apple.screensaver.didstart, com.apple.screensaver.didstop, com.apple.screenIsLocked, and com.apple.screenIsUnlocked events.

// ScreenSaverMonitor.h
#import <Foundation/NSObject.h>
#import <Foundation/NSNotification.h>

@interface ScreenSaverMonitor: NSObject {}
-(id) init;
-(void) receive: (NSNotification*) notification;
@end

// ScreenSaverMonitor.m
#import "ScreenSaverMonitor.h"
#import <Foundation/NSString.h>
#import <Foundation/NSDistributedNotificationCenter.h>
#import <Foundation/NSRunLoop.h>
#import <stdio.h>

@implementation ScreenSaverMonitor
-(id) init {
  NSDistributedNotificationCenter * center 
    = [NSDistributedNotificationCenter defaultCenter];

  [center addObserver: self
          selector:    @selector(receive:)
          name:        @"com.apple.screensaver.didstart"
          object:      nil
  ];
  [center addObserver: self
          selector:    @selector(receive:)
          name:        @"com.apple.screensaver.didstop"
          object:      nil
  ];
  [center addObserver: self
          selector:    @selector(receive:)
          name:        @"com.apple.screenIsLocked"
          object:      nil
  ];
  [center addObserver: self
          selector:    @selector(receive:)
          name:        @"com.apple.screenIsUnlocked"
          object:      nil
  ];
  printf("running loop... (^C to quit)");
  [[NSRunLoop currentRunLoop] run];
  printf("...ending loop");
  return self;
}
-(void) receive: (NSNotification*) notification {
  printf("%s\n", [[notification name] UTF8String] );
}
@end

// ScreenSaverMonitorMain.m
#import "ScreenSaverMonitor.h"

int main( int argc, char ** argv) {
  [[ScreenSaverMonitor alloc] init];
  return 0;
}

It compiles fine, but when I run it, I don't seem to observe any screensaver events (despite having the screensaver come on multiple times):

% gcc -Wall ScreenSaverMonitor.m ScreenSaverMonitorMain.m -o ScreenSaverMonitor -lobjc -framework Cocoa
% ./ScreenSaverMonitor
running loop (^C to quit)...
^C
%

My Objective C and Cocoa knowledge is very rusty, so I'm not sure if I'm using the framework wrong, or if I've registered for the wrong events (nor where to look to find out whether those are the right events or not).

So what is it I'm doing wrong?

Community
  • 1
  • 1
rampion
  • 87,131
  • 49
  • 199
  • 315

3 Answers3

10

You've commented your problem already.

while(1); // busy wait's bad, I know, but easy to implement

The above is almost ALWAYS a bad idea.

NSDistributedNotificationCenter actually requires a running main thread NSRunLoop to operate.

http://developer.apple.com/library/ios/#documentation/cocoa/Conceptual/Notifications/Articles/NotificationCenters.html#//apple_ref/doc/uid/20000216-BAJGDAFC

Creating and spinning a run loop from the main() of a command line application on OS X is a fairly simple thing to do. There are plenty of examples available with a quick search.

GoannaGuy
  • 710
  • 4
  • 7
  • Good idea! I replaced `while(1);` with `[[NSRunLoop currentRunLoop] run];`, but still no dice. – rampion Jun 05 '13 at 19:58
  • I'm not sure what you're running into here. I've taken your edited code above, fixed a compiler error and made minor logging change, and it seems to be working fine for me. Specifically, I changed the #import line to be #import and changed the printf statements to fprintf to stderr. (This works better as stderr is flushed immediately rather than buffered as printf is. – GoannaGuy Jun 06 '13 at 03:52
  • 3
    Actually, my tweaked version, which I consolidated to a single source file, is available here: http://pastie.org/8013106 It includes a modified build command at the bottom which builds a bit quicker, since it links against Foundation.framework instead of the larger Cocoa.framework (but either should work fine) – GoannaGuy Jun 06 '13 at 04:05
  • Your single source file compiles and runs fine for me too, but it doesn't seem to detect screensaver events or lockscreen events. Maybe it's a OSX version thing? I'm running 10.6.8. – rampion Jun 06 '13 at 12:52
  • They seem to be present in 10.7.x and 10.8.x in my testing. A little googling seems to show that they should be present in 10.5.x and newer with a subset of notifications present as far back as 10.4.x. – GoannaGuy Jun 06 '13 at 16:25
  • gah, figured out what was going wrong. I was running my executable [within tmux, which was disconnecting me from the userspace events, as it has done before](http://sourceforge.net/mailarchive/forum.php?thread_name=CAHy5oWQaLYPh36QELpcy7qPU30ZFuBXjEmKJviKsFV5UUERJhQ%40mail.gmail.com&forum_name=tmux-users). – rampion Jun 11 '13 at 14:27
8

Edit: further testing showed that com.apple.screensaver.didlaunch is also available, the code here is tested on 10.6.8 and 10.8.4

Your code will work if you remove [[NSRunLoop currentRunLoop] run]; and initialize ScreenSaverMonitor from a Cocoa application's applicationDidFinishLaunching: method. ( Just create a new Cocoa application project in XCode and add your code where appropriate ).

ScreenSaverMonitor.h

#import <Foundation/Foundation.h>

@interface ScreenSaverMonitor : NSObject
-(id) init;
-(void) receive: (NSNotification*) notification;

@end

ScreenSaverMonitor.m

#import "ScreenSaverMonitor.h"
#import <Foundation/NSString.h>
#import <Foundation/NSDistributedNotificationCenter.h>
#import <Foundation/NSRunLoop.h>
#import <stdio.h>

@implementation ScreenSaverMonitor
-(id) init {
    NSDistributedNotificationCenter * center
    = [NSDistributedNotificationCenter defaultCenter];

    [center addObserver: self
               selector:    @selector(receive:)
                   name:        @"com.apple.screensaver.didlaunch"
                 object:      nil
     ];

    [center addObserver: self
               selector:    @selector(receive:)
                   name:        @"com.apple.screensaver.didstart"
                 object:      nil
     ];
    [center addObserver: self
               selector:    @selector(receive:)
                   name:        @"com.apple.screensaver.didstop"
                 object:      nil
     ];
    [center addObserver: self
               selector:    @selector(receive:)
                   name:        @"com.apple.screenIsLocked"
                 object:      nil
     ];
    [center addObserver: self
               selector:    @selector(receive:)
                   name:        @"com.apple.screenIsUnlocked"
                 object:      nil
     ];
    return self;
}
-(void) receive: (NSNotification*) notification {
    printf("%s\n", [[notification name] UTF8String] );
}

@end

AppDelegate.h

#import <Cocoa/Cocoa.h>
#import "ScreenSaverMonitor.h"

@interface AppDelegate : NSObject <NSApplicationDelegate>

@property (assign) IBOutlet NSWindow *window;
@property (retain) ScreenSaverMonitor *monitor;
@end

AppDelegate.m

#import "AppDelegate.h"
#import "ScreenSaverMonitor.h"

@implementation AppDelegate
@synthesize monitor;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    // Insert code here to initialize your application
    self.monitor = [[ScreenSaverMonitor alloc] init];

}

@end

main.m

#import <Cocoa/Cocoa.h>

int main(int argc, char *argv[])
{
    return NSApplicationMain(argc, (const char **)argv);
}
szayat
  • 408
  • 3
  • 9
2

It appears the strategy you're trying to use for this won't work, because the com.apple.screensaver.* notifications are no longer supported.
In the replies to this equivalent question, it is mentioned that: 'for Snow Leopard the screenIsLocked and screenIsUnlocked notifications aren't available anymore.'
You can register for screen sleeps, which of course is not the same but may be an acceptable alternative for you, by listening to the NSWorkspaceScreensDidSleepNotification notification, or to the computer going to sleep by listening to the NSWorkspaceWillSleepNotification. Sample code can be found on this forum.

Sidenote: if you use nil for the name, you will receive lots of events:

[center addObserver:self
           selector:@selector(receive:)
               name:nil
             object:nil
];

If you do that, you will see that you're doing everything basically right, because you will receive all kind of events - but no screensaver ones.

Community
  • 1
  • 1
ecotax
  • 1,933
  • 17
  • 22
  • 1
    As of 10.10, the com.apple.screensaver.* notifications are still sent (as are screenIsLocked/screenIsUnlocked). Also, `NSDistributedNotificationCenter` won't let you register for all notifications, at least in a sandboxed app. You get the warning, "*** attempt to register for all distributed notifications thwarted by sandboxing." – zpasternack Jul 21 '15 at 04:46