Distributed Notifications Template?

After a bit of poking around it seems that there is no built-in applescript access to the OS X distributed notifications center. I’m wondering if perhaps someone has written a C template that could be easily integrated into an AS Studio project?

Thanks.

As far as I know, there are no “templates” available, and I don’t see why you would need one. I’m not sure how much experience you have with notifications, but they’re pretty straightforward. Once you know which notofication you want to observe, you just send an addobserver message to the default center, and you’ll get notified whenever the system recieves that message. The method used to add the observer accepts a parameter of a selector (method) to execute when the notification is received, and this is your mechanism for actually responding to the notification. There’s little else you need to do, and no other supporting code or files needed… other than that which you write to deal with the notification The only real problem, is that this must be done in obj-c (or carbon), and there’s no easy way for your obj-c method to communicate with your applescript code once you receive the notification. Unless you’re prepared to write all of the code that handles the notification in obj-c or you want to come up with a creative solution to handling the notification and passing it to your script, you may find using notifications in your asstudio project a bit challenging. Perhaps if I knew what notification you were registering for, and more importantly how you intend to deal with it, I could offer more constructive input.

j

jobu, thanks for the reply. I do not know obj-c at all == some day I hope to learn it.

to answer some questions:

  1. my main goal for the moment is to get notifications from iTunes and respond in a script handler.

  2. i think that could be accomplished by having some kind of “template” file in the project which would “listen” and then call an invisible button in the NIB, which would then be connected to an “on clicked” handler.

  3. the above idea, at any rate, is what someone else suggested and more ore less implemented already. He explains it in his blog but it is still Greek to me, especially the bit about how to connect it to an invisible button:

http://schinckel.blogsome.com/2005/06/05/notification-from-applescript-studio/

  1. I want to put this in a project I’m working on called OmniGrowl, which is meant to be a framework for support Growl notifications for applications that don’t natively support Growl. So far I haven’t tapped into the distributed notifications but want to. The project as distributed includes source code, in case you want to look:

http://www.versiontracker.com/dyn/moreinfo/macosx/31505

  1. so mhy secondary goal is include a method that could easily be expanded by people who know only applescript to listen to any notification available and respond with similar invisible buttons.

Thanks

Here’s a simple setup that works for me.

The applescript…

property ITObserver : null

(* Methods to initialize and manage iTunes observation *)
on will finish launching theObject
	set ITObserver to (call method "newITObserver" of class "ITObserver")
	call method "beginObservingiTunes" of ITObserver
end will finish launching

on will quit theObject
	call method "endObservingiTunes" of ITObserver
end will quit

(* Save a reference to the button to 'press' when a notification is caught *)
on awake from nib theObject
	if name of theObject is "someButton" then
		call method "setActionButton:" of ITObserver with parameter theObject
	end if
end awake from nib

The “ITObserver” property in the header is a static reference to an object that you will instantiate, which will be what you call all of your obj-c methods on. You must connect the ‘will finish launching’ and ‘will quit’ handlers to your “File’s Owner” object of your app. In the will finish launching handler, you initialize and save a reference to the ITObserver object, and then tell the object to begin observing itunes for notifications. As good practice, when you quit make sure to call the ‘endobservingitunes’ method to release your observer.

Then connect your “hidden button” to it’s ‘awake from nib’ handler (and also to it’s ‘clicked’ handler so you can actually do something when the button is clicked.) In my case, I named the button “someButton”. In the awakefromnib handler, use the call method seen above to “attach” your button to the ITObserver object. It saves a reference to the button internally, so whenever you receive an itunes notification, it knows which button to press.

The Objective-c…

ITObserver is an nsobject subclass that creates an object to act as a bridge between your AS code and your obj-c code. See my subclassing tutorial for how to create the “ITObserver” subclass. Below is a description of each method and it’s purpose…

“+(id)newITObserver”:
This method is called to initialize an instance of this class. You call this from your applescript to create an object upon which to call the other methods.

“-(void)beginObservingiTunes” & “-(void)endObservingiTunes”:
As their names imply, they are used to tell your app to begin or end listening for itunes notifications. You should always call them in pair, because it’s good practice to undo anything you’ve done… to avoid memory leaks and other conflicts.

“-(void)handleiTunesNotification:(NSNotification *)aNotification”:
The way this method is configured, it simply clicks your button in your AS code. If you were writing everything in obj-c, this is where you would write your obj-c code to perform your actions in response to the notification. Instead, you’re simply passing the action on to the clicked handler of the button.

“-(void)setActionButton:(NSButton *)theButton”:
This method takes an argument of a button from your AS code, and sets a persistent reference to the button you want to click when the notification is handled.

Comments…
Note that this is a hack. While it’s pretty stable, it is still probably not a preferred way of doing this. Ideally, when you get to a point where you need to do some of this lower-level stuff, you’d want to transition to a lower-level language. If not set up properly, this whole system may break. One weak link or handler called at the wrong time will make this whole system fail.

As you mention, there may be some use for this to other people, but I hardly see the need for making this so modular as to write a complex “template” for it. You certainly could create more methods that would dynamically register and unregister for notifications, but you’d need to know which notifications you wanted to register for and then manually do that. If you’re putting this into another project that handles other kind of notifications, the level of complexity may become much more intense. You have to consider a well-rounded approach to this task, as it’s not just as simple as you may think. What if you want to handle dozens of events? Must you create dozens of buttons and then attach them manually? Do you want to catch all notifications from a particular object or all objects? Where do you want to evaluate which object and which notification are sent? It’s a simple proposition to catch one notification, but notifications are not always as cut and dry as this simple example. Some objects send dozens of notifications as they are interacted with, so a comprehensive understanding of which notifications you want to intercept, or a comprehensive system of evaluating notifications as they come in, is required. In the case of catching itunes events, it’s pretty easy because it sends a generic event every time it changes. But it also has a dictionary of data that corresponds with the notification which I assume most people would want to evaluate. Also, in this example, I simply register to receive one notification type from all objects. But in reality, people will want to catch specific notifications, from multiple objects. The level of complexity jumps dramatically, and every different notification for each object adds another button you must create simply to handle one minute change in the notification.

While handling notifications is relatively benign, I feel like you should take the time to do things right and learn how to write in a language that supports this sort of thing without resorting to such trickery. You’re essentially gui scripting yourself, which is not an intelligent or responsible way of doing things in most cases. As I said, the approach I post works fine, but it’s a workaround in my opinion, and is not the best method. It’s best suited to handling one or two specific notifications, and gets quite complex once you start thinking about making a “template” that can be used by anyone for anything. Just try to get your own setup working, and then if you feel like you gain enough from that process to write a useful extension or subclass that others could benefit from, then feel free to pour your time into it. From what I can tell, you’ll need a significantly deeper understanding of notifications and how they work before you’re ready for that, though.

Anyways, the above worked fine for me, so have fun and let me know if I can clarify anything for you. Don’t hurt your brain trying to get it to work. :smiley:

j

Jobu,

Thanks so much. I implemented the method given your great code in about 10 minutes and it works fine. I agree this isn’t the “best” method, but a) it works, and b) I think I can, after studying this somewhat, implement similar methods for other notifications. I’ll post more later after I’ve truly digested it all. Cheers and merry xmas.

Hi Jobu (or anyone else who can help here),

Sorry to bring this up again.

I tried to implement this in another application, but am having problems. When I change iTunes tracks I get this error:

Can’t make class id 5 of class id 4 into type reference. (-1700)

The run log shows no error:


2007-03-09 09:30:00.697 Cast Away[25235] Created iTunes Observer
2007-03-09 09:30:00.699 Cast Away[25235] Begin Observing
2007-03-09 09:30:17.584 Cast Away[25235] Handle Notification

Suspecting a conflict with the other application running ITObserver, I tried changing names of variables from “ITObserver” to “ITObserverCA”. This did not seem to work either – but then I don’t know what i"m doing with the C code. The name of the hidden button is still “ITObserver”. The .h and .m files are renamed to ITObserverCA.h & ITObserverCA.m

After making these changes I still get the above error. Any ideas? THANKS!


property ITObserverCA : null
....

on will finish launching the_object
		set ITObserverCA to (call method "newITObserverCA" of class "ITObserverCA")
		call method "beginObservingiTunesCA" of ITObserverCA
...
on awake from nib the_object
	if object_name is "ITObserver" then
		call method "setActionButton:" of ITObserverCA with parameter the_object
	end if

on will quit theObject
		call method "endObservingiTunesCA" of ITObserverCA
...

...
on clicked theObject
	if ...
	else if name of theObject is "ITObserver" then
		my checkitunes()
...



//
//  ITObserver.h
//  Cast Away
//
#import <Cocoa/Cocoa.h>

@interface ITObserverCA : NSObject {
    NSButton *asActionButton;
}

+(id)newITObserverCA;

-(void)beginObservingiTunesCA;
-(void)endObservingiTunesCA;

-(void)handleiTunesNotification:(NSNotification *)aNotification;
-(void)setActionButton:(NSButton *)theButton;
@end


//  ITObserver.m
//  Cast Away

#import "ITObserverCA.h"

@implementation ITObserverCA

+(id)newITObserverCA {
    NSLog(@"Created iTunes Observer");
    return [[super alloc] init];
}

-(void)dealloc {
    [super dealloc];
}

-(void)beginObservingiTunesCA {
    NSLog(@"Begin Observing");
    [[NSDistributedNotificationCenter defaultCenter]
        addObserver:self 
        selector:@selector(handleiTunesNotification:) 
        name:@"com.apple.iTunes.playerInfo" 
        object:nil];
}

-(void)endObservingiTunesCA {
    NSLog(@"End Observing");
    [[NSDistributedNotificationCenter defaultCenter] 
        removeObserver:self 
        name:@"com.apple.iTunes.playerInfo" 
        object:nil];
}

-(void)handleiTunesNotification:(NSNotification *)aNotification {
    NSLog(@"Handle Notification");
    [asActionButton performClick:nil];
}

-(void)setActionButton:(NSButton *)theButton {
    asActionButton = theButton;
}

@end

The error you’re getting is obviously happening when you’re making a reference to something… likely a reference to an object that is not being properly initialized, or an item that you should have a persistent reference to but do not. I would start by putting log calls into the script in lots of various places to try to figure out exactly where the problem is. You need to narrow it down to the exact single line of code that is producing the problem. There’s probably a line somewhere that reads “set blah to snart” or a call method on an improperly initialized object. Oftentimes, when you do call methods that create AS references to objects you create with custom obj-c code, if the code returns an error or nil, your applescript still treats the variable as an object. But then later on when you try to act on what you assume is a reference to an object stored in the variable, but it’s a null reference instead, you get an error because there’s no valid object to act on.

Just off-hand, it appears as if the problem is occurring in some other bit of code besides what you’ve posted. The error is an applescript error, so it’s being generated by a line of AS that’s trying to act on an object or derive a reference from a variable. Once you figure out which line of code is the culprit, maybe we can help more.

j

Thanks as always for your helpful comments. Unfortunately the error is DEFINITELY in the code i just posted. Commented out, no error at all. When it is all left in, the on clicked handler shows only this:


else if name of theObject is "ITObserverCA" then
		-- this button on the hidden "buttons" window is triggered with the ITObserver class defined in C by notificaitons from iTunes
		--my checkitunes()

In other words – it does nothing at all. But the error is still generated.

Note that I tried to change the names of everything (now including the button) from ITObserver to ITObserverCA to avoid conflict with another application with the same code and names. But handle notification and set action button i didn’t change because it didn’t seem right to mess with.

thanks!