问题
I wrote an AppleScript
to mount a SparseBundle
image and I want it to be executed exactly when Time Machine
launches.
Right now, I periodically check if Time Machine is running with AppleScript
using on idle
statement:
on idle
....
return <interval>
end idle
which isn't a robust way. In my opinion adding an event trigger for Application Launch
event would be a better approach.
Could you please help?
An Objective-C
or Python
sample code (I'd prefer Python
) is more than welcome.
回答1:
What you are looking for is, NSDistributedNotificationCenter or NSWorkspace , these cocoa classes post notifications of application events, For workspace, things like application launches, mounting of drives etc.
To do this in python, you need PyObjC, which is basically python bindings for apple's cocoa classes. The documentation is sparse on their website, and there's a reason, as the documentation would be basically be the same as the Apple docs, so they only include the differences between the pyobjc api, and the cocoa API. If you understand how the objective c api is converted to python you are good to go. Check here: http://pyobjc.sourceforge.net/documentation/pyobjc-core/intro.html
I have included an example below which listens for Distributed notifications using python. The code below basically adds an observer and listens for itunes notifications. You could follow a similar structure, but instead add an observer for NSWorkspace. To figure out what you should be listening to, there is an application that will display all notifications going through your system. It's called notification watcher . Use this to figure out what you should be listening to. You could also convert the objective c code to python.
What the code below is doing
- Defines a new class which inherits from NSObject, as defined by PyObjC
- Defines a method, which gets passed the actual notification and prints it out
- Creates an instance of Foundation.NSDistributedNotificationCenter.defaultCenter
- Creates an instance of GetSongs
- Registers an observer, passing it the class, the method that gets called when a notification is received and which application & event to monitor i.e "com.apple.iTunes.playerInfo"
- Runs the event loop,
One thing that will trip you up, accessing attributes (objective c attributes) do not work the same as accessing python attributes. i.e in python you do class_name.att
for objective c in python you have to call it like a function i.e from my example below: song.userInfo()
import Foundation
from AppKit import *
from PyObjCTools import AppHelper
class GetSongs(NSObject):
def getMySongs_(self, song):
print "song:", song
song_details = {}
ui = song.userInfo()
print 'ui:', ui
for x in ui:
song_details[x] = ui.objectForKey_(x)
print song_details
nc = Foundation.NSDistributedNotificationCenter.defaultCenter()
GetSongs = GetSongs.new()
nc.addObserver_selector_name_object_(GetSongs, 'getMySongs:', 'com.apple.iTunes.playerInfo',None)
NSLog("Listening for new tunes....")
AppHelper.runConsoleEventLoop()
Here's an example of the actual output... (YES BRITNEY ROCKS!, NOT! ;)
song NSConcreteNotification 0x104c0a3b0 {name = com.apple.iTunes.playerInfo; object = com.apple.iTunes.player; userInfo = {
Album = Circus;
"Album Rating" = 0;
"Album Rating Computed" = 1;
Artist = "Britney Spears";
"Artwork Count" = 1;
Genre = Pop;
"Library PersistentID" = 8361352612761174229;
Location = "file://localhost/Users/izze/Music/iTunes/iTunes%20Music/Britney%20Spears/Circus/02%20Circus.mp3";
Name = Circus;
PersistentID = 4028778662306031905;
"Play Count" = 0;
"Play Date" = "2010-06-26 08:20:57 +0200";
"Player State" = Playing;
"Playlist PersistentID" = 7784218291109903761;
"Rating Computed" = 1;
"Skip Count" = 1;
"Skip Date" = "2010-06-26 12:20:57 +0200";
"Store URL" = "itms://itunes.com/link?n=Circus&an=Britney%20Spears&pn=Circus";
"Total Time" = 192444;
"Track Count" = 16;
"Track Number" = 2;
}}
ui {
Album = Circus;
"Album Rating" = 0;
"Album Rating Computed" = 1;
Artist = "Britney Spears";
"Artwork Count" = 1;
Genre = Pop;
"Library PersistentID" = 8361352612761174229;
Location = "file://localhost/Users/izze/Music/iTunes/iTunes%20Music/Britney%20Spears/Circus/02%20Circus.mp3";
Name = Circus;
PersistentID = 4028778662306031905;
"Play Count" = 0;
"Play Date" = "2010-06-26 08:20:57 +0200";
"Player State" = Playing;
"Playlist PersistentID" = 7784218291109903761;
"Rating Computed" = 1;
"Skip Count" = 1;
"Skip Date" = "2010-06-26 12:20:57 +0200";
"Store URL" = "itms://itunes.com/link?n=Circus&an=Britney%20Spears&pn=Circus";
"Total Time" = 192444;
"Track Count" = 16;
"Track Number" = 2;
}
{u'Album Rating Computed': 1, u'Album': u'Circus', u'Rating Computed': True, u'Name': u'Circus', u'Artist': u'Britney Spears', u'Track Number': 2, u'Skip Date': 2010-06-26 12:20:57 +0200, u'Library PersistentID': 8361352612761174229L, u'Player State': u'Playing', u'Total Time': 192444L, u'Genre': u'Pop', u'Playlist PersistentID': 7784218291109903761L, u'Album Rating': 0, u'Location': u'file://localhost/Users/izze/Music/iTunes/iTunes%20Music/Britney%20Spears/Circus/02%20Circus.mp3', u'Skip Count': 1, u'Track Count': 16L, u'Artwork Count': 1, u'Play Date': 2010-06-26 08:20:57 +0200, u'PersistentID': 4028778662306031905L, u'Play Count': 0, u'Store URL': u'itms://itunes.com/link?n=Circus&an=Britney%20Spears&pn=Circus'}
回答2:
This isn't too tough to do in Objc-C. You can access notifications for all applications through NSWorkspace and NSNotificationCenter. Create an object and register one of it's methods for notifications of type NSWorkspaceDidTerminateApplicationNotification. Something like:
@interface NotificationObserver : NSObject { }
- (void) applicationDidLaunch:(NSNotification*)notification;
@end
@implementation NotificationObserver : NSObject
- (void) applicationDidLaunch:(NSNotification*)notification
{
// Check the notification to see if Time Machine is being launched.
}
@end
void watch(void)
{
NSNotificationCenter* notificationCenter
= [[NSWorkspace sharedWorkspace] sharednotificationCenter];
NotificationObserver* observer = [[NotificationObserver alloc] init];
[notificationCenter addObserver:observer
selector:@selector(applicationDidTerminate:)
name:@"NSWorkspaceDidTerminateApplicationNotification"
object:nil];
}
回答3:
This isn't an answer to your question, but it may solve your problem.
Why not just have your AppleScript launch Time Machine after it mounts the disk image? Then, instead of launching Time Machine directly, always invoke Time Machine via your script. You can even paste the Time Machine icon on to your AppleScript file, and name it "Time Machine" to make the illusion complete. :-)
来源:https://stackoverflow.com/questions/4304647/how-to-listen-for-an-application-launch-event-in-mac-os-x