|
boredzo@1385
|
1 |
//
|
|
boredzo@2438
|
2 |
// GrowlApplicationController.m
|
|
boredzo@1385
|
3 |
// Growl
|
|
boredzo@1385
|
4 |
//
|
|
boredzo@1385
|
5 |
// Created by Karl Adam on Thu Apr 22 2004.
|
|
boredzo@2438
|
6 |
// Renamed from GrowlController by Mac-arena the Bored Zo on 2005-06-28.
|
|
ingmarstein@3040
|
7 |
// Copyright 2004-2006 The Growl Project. All rights reserved.
|
|
boredzo@1385
|
8 |
//
|
|
boredzo@1385
|
9 |
// This file is under the BSD License, refer to License.txt for details
|
|
boredzo@1385
|
10 |
|
|
boredzo@2438
|
11 |
#import "GrowlApplicationController.h"
|
|
ingmarstein@2450
|
12 |
#import "GrowlPreferencesController.h"
|
|
boredzo@1385
|
13 |
#import "GrowlApplicationTicket.h"
|
|
evands@2925
|
14 |
#import "GrowlApplicationNotification.h"
|
|
ingmarstein@2452
|
15 |
#import "GrowlTicketController.h"
|
|
boredzo@2501
|
16 |
#import "GrowlNotificationTicket.h"
|
|
boredzo@3109
|
17 |
#import "GrowlPathway.h"
|
|
ingmarstein@3169
|
18 |
#import "GrowlPathwayController.h"
|
|
boredzo@4045
|
19 |
#import "GrowlPropertyListFilePathway.h"
|
|
boredzo@4047
|
20 |
#import "GrowlPathUtilities.h"
|
|
ingmarstein@2100
|
21 |
#import "NSStringAdditions.h"
|
|
ingmarstein@2695
|
22 |
#import "GrowlDisplayPlugin.h"
|
|
boredzo@1692
|
23 |
#import "GrowlPluginController.h"
|
|
rudy@2961
|
24 |
#import "GrowlIdleStatusController.h"
|
|
boredzo@1385
|
25 |
#import "GrowlDefines.h"
|
|
boredzo@1385
|
26 |
#import "GrowlVersionUtilities.h"
|
|
boredzo@1385
|
27 |
#import "SVNRevision.h"
|
|
ingmarstein@1798
|
28 |
#import "GrowlLog.h"
|
|
ingmarstein@1918
|
29 |
#import "GrowlNotificationCenter.h"
|
|
ingmarstein@1885
|
30 |
#import "MD5Authenticator.h"
|
|
ingmarstein@2641
|
31 |
#include "CFGrowlAdditions.h"
|
|
ingmarstein@2641
|
32 |
#include "CFURLAdditions.h"
|
|
ingmarstein@2641
|
33 |
#include "CFDictionaryAdditions.h"
|
|
ingmarstein@2641
|
34 |
#include "CFMutableDictionaryAdditions.h"
|
|
ingmarstein@2618
|
35 |
#include "cdsa.h"
|
|
ingmarstein@2618
|
36 |
#include <SystemConfiguration/SystemConfiguration.h>
|
|
boredzo@3079
|
37 |
#include <sys/errno.h>
|
|
boredzo@3079
|
38 |
#include <string.h>
|
|
ingmarstein@1885
|
39 |
#include <sys/socket.h>
|
|
ingmarstein@2285
|
40 |
#include <sys/fcntl.h>
|
|
ingmarstein@1984
|
41 |
#include <netinet/in.h>
|
|
boredzo@1385
|
42 |
|
|
ingmarstein@1581
|
43 |
// check every 24 hours
|
|
ingmarstein@1581
|
44 |
#define UPDATE_CHECK_INTERVAL 24.0*3600.0
|
|
ingmarstein@1581
|
45 |
|
|
evands@3703
|
46 |
//Notifications posted by GrowlApplicationController
|
|
evands@3703
|
47 |
#define UPDATE_AVAILABLE_NOTIFICATION @"Growl update available"
|
|
evands@3703
|
48 |
#define USER_WENT_IDLE_NOTIFICATION @"User went idle"
|
|
evands@3703
|
49 |
#define USER_RETURNED_NOTIFICATION @"User returned"
|
|
evands@3703
|
50 |
|
|
boredzo@4051
|
51 |
static OSStatus soundCompletionCallbackProc(SystemSoundActionID actionID, void *refcon);
|
|
boredzo@4051
|
52 |
|
|
rudy@2905
|
53 |
extern CFRunLoopRef CFRunLoopGetMain(void);
|
|
rudy@2905
|
54 |
|
|
bgannin@3450
|
55 |
@interface GrowlApplicationController (PRIVATE)
|
|
ingmarstein@2018
|
56 |
- (void) notificationClicked:(NSNotification *)notification;
|
|
ingmarstein@2018
|
57 |
- (void) notificationTimedOut:(NSNotification *)notification;
|
|
boredzo@1385
|
58 |
@end
|
|
boredzo@1385
|
59 |
|
|
boredzo@3080
|
60 |
/*applications that go full-screen (games in particular) are expected to capture
|
|
boredzo@3080
|
61 |
* whatever display(s) they're using.
|
|
boredzo@3080
|
62 |
*we [will] use this to notice, and turn on auto-sticky or something (perhaps
|
|
boredzo@3080
|
63 |
* to be decided by the user), when this happens.
|
|
boredzo@3080
|
64 |
*/
|
|
evands@3611
|
65 |
#if 0
|
|
boredzo@3079
|
66 |
static BOOL isAnyDisplayCaptured(void) {
|
|
boredzo@3079
|
67 |
BOOL result = NO;
|
|
boredzo@3079
|
68 |
|
|
boredzo@3079
|
69 |
CGDisplayCount numDisplays;
|
|
boredzo@3079
|
70 |
CGDisplayErr err = CGGetActiveDisplayList(/*maxDisplays*/ 0U, /*activeDisplays*/ NULL, &numDisplays);
|
|
boredzo@3079
|
71 |
if (err != noErr)
|
|
boredzo@3079
|
72 |
[[GrowlLog sharedController] writeToLog:@"Checking for captured displays: Could not count displays: %li", (long)err];
|
|
ingmarstein@3117
|
73 |
else {
|
|
boredzo@3079
|
74 |
CGDirectDisplayID *displays = malloc(numDisplays * sizeof(CGDirectDisplayID));
|
|
boredzo@3079
|
75 |
CGGetActiveDisplayList(numDisplays, displays, /*numDisplays*/ NULL);
|
|
boredzo@3079
|
76 |
|
|
boredzo@3079
|
77 |
if (!displays)
|
|
boredzo@3079
|
78 |
[[GrowlLog sharedController] writeToLog:@"Checking for captured displays: Could not allocate list of displays: %s", strerror(errno)];
|
|
boredzo@3079
|
79 |
else {
|
|
boredzo@3079
|
80 |
for (CGDisplayCount i = 0U; i < numDisplays; ++i) {
|
|
boredzo@3079
|
81 |
if (CGDisplayIsCaptured(displays[i])) {
|
|
boredzo@3079
|
82 |
result = YES;
|
|
boredzo@3079
|
83 |
break;
|
|
boredzo@3079
|
84 |
}
|
|
boredzo@3079
|
85 |
}
|
|
boredzo@3079
|
86 |
|
|
boredzo@3079
|
87 |
free(displays);
|
|
boredzo@3079
|
88 |
}
|
|
boredzo@3079
|
89 |
}
|
|
boredzo@3079
|
90 |
|
|
boredzo@3079
|
91 |
return result;
|
|
boredzo@3079
|
92 |
}
|
|
evands@3611
|
93 |
#endif
|
|
boredzo@3079
|
94 |
|
|
tick@3610
|
95 |
//static struct Version version = { 0U, 8U, 0U, releaseType_svn, 0U, };
|
|
evands@3892
|
96 |
#warning Having to update this struct manually is ugly. Use the info.plist.
|
|
boredzo@3937
|
97 |
#warning And once code is in to automagically update this from Info.plist, the documentation in GrowlVersionUtilities.h should also be updated.
|
|
boredzo@4129
|
98 |
static struct Version version = { 1U, 1U, 5U, releaseType_svn, 0U, };
|
|
boredzo@1499
|
99 |
//XXX - update these constants whenever the version changes
|
|
boredzo@1385
|
100 |
|
|
ingmarstein@2556
|
101 |
static void checkVersion(CFRunLoopTimerRef timer, void *context) {
|
|
ingmarstein@2573
|
102 |
#pragma unused(timer)
|
|
ingmarstein@2556
|
103 |
GrowlPreferencesController *preferences = [GrowlPreferencesController sharedController];
|
|
ingmarstein@2556
|
104 |
|
|
ingmarstein@2565
|
105 |
if (![preferences isBackgroundUpdateCheckEnabled])
|
|
ingmarstein@2556
|
106 |
return;
|
|
ingmarstein@2556
|
107 |
|
|
ingmarstein@2573
|
108 |
GrowlApplicationController *appController = (GrowlApplicationController *)context;
|
|
boredzo@3082
|
109 |
NSURL *versionCheckURL = [appController versionCheckURL];
|
|
boredzo@3082
|
110 |
|
|
boredzo@3082
|
111 |
NSDictionary *productVersionDict = [[NSDictionary alloc] initWithContentsOfURL:versionCheckURL];
|
|
ingmarstein@2556
|
112 |
|
|
ingmarstein@2556
|
113 |
NSString *currVersionNumber = [GrowlApplicationController growlVersion];
|
|
ingmarstein@2556
|
114 |
NSString *latestVersionNumber = [productVersionDict objectForKey:@"Growl"];
|
|
ingmarstein@2556
|
115 |
|
|
ingmarstein@2556
|
116 |
NSString *downloadURLString = [productVersionDict objectForKey:@"GrowlDownloadURL"];
|
|
ingmarstein@2556
|
117 |
|
|
ingmarstein@2556
|
118 |
/* do nothing and be quiet if there is no active connection, if the
|
|
ingmarstein@2556
|
119 |
* version dictionary could not be downloaded, or if the version dictionary
|
|
ingmarstein@2556
|
120 |
* is missing either of these keys.
|
|
ingmarstein@2556
|
121 |
*/
|
|
ingmarstein@2556
|
122 |
if (downloadURLString && latestVersionNumber) {
|
|
ingmarstein@2556
|
123 |
[preferences setObject:[NSDate date] forKey:LastUpdateCheckKey];
|
|
ingmarstein@2556
|
124 |
if (compareVersionStringsTranslating1_0To0_5(latestVersionNumber, currVersionNumber) > 0) {
|
|
ingmarstein@2649
|
125 |
CFStringRef title = CFCopyLocalizedString(CFSTR("Update Available"), /*comment*/ NULL);
|
|
ingmarstein@2649
|
126 |
CFStringRef description = CFCopyLocalizedString(CFSTR("A newer version of Growl is available online. Click here to download it now."), /*comment*/ NULL);
|
|
ingmarstein@2649
|
127 |
[GrowlApplicationBridge notifyWithTitle:(NSString *)title
|
|
ingmarstein@2649
|
128 |
description:(NSString *)description
|
|
evands@3703
|
129 |
notificationName:UPDATE_AVAILABLE_NOTIFICATION
|
|
ingmarstein@2556
|
130 |
iconData:[appController applicationIconDataForGrowl]
|
|
ingmarstein@2556
|
131 |
priority:1
|
|
ingmarstein@2556
|
132 |
isSticky:YES
|
|
evands@3703
|
133 |
clickContext:downloadURLString
|
|
boredzo@3984
|
134 |
identifier:UPDATE_AVAILABLE_NOTIFICATION];
|
|
ingmarstein@2649
|
135 |
CFRelease(title);
|
|
ingmarstein@2649
|
136 |
CFRelease(description);
|
|
ingmarstein@2556
|
137 |
}
|
|
ingmarstein@2556
|
138 |
}
|
|
ingmarstein@2556
|
139 |
|
|
ingmarstein@2556
|
140 |
[productVersionDict release];
|
|
ingmarstein@2556
|
141 |
}
|
|
ingmarstein@2556
|
142 |
|
|
boredzo@2438
|
143 |
@implementation GrowlApplicationController
|
|
boredzo@2438
|
144 |
|
|
boredzo@2438
|
145 |
+ (GrowlApplicationController *) sharedController {
|
|
ofri@2581
|
146 |
return [self sharedInstance];
|
|
ofri@2581
|
147 |
}
|
|
ofri@2581
|
148 |
|
|
ofri@2581
|
149 |
- (id) initSingleton {
|
|
ofri@2581
|
150 |
if ((self = [super initSingleton])) {
|
|
ingmarstein@3007
|
151 |
CSSM_RETURN crtn = cdsaInit();
|
|
ingmarstein@3007
|
152 |
if (crtn) {
|
|
boredzo@2466
|
153 |
NSLog(@"ERROR: Could not initialize CDSA.");
|
|
ingmarstein@3007
|
154 |
cssmPerror("cdsaInit", crtn);
|
|
boredzo@2466
|
155 |
[self release];
|
|
boredzo@2466
|
156 |
return nil;
|
|
boredzo@2466
|
157 |
}
|
|
boredzo@2466
|
158 |
|
|
ingmarstein@2588
|
159 |
// initialize GrowlPreferencesController before observing GrowlPreferencesChanged
|
|
ingmarstein@2588
|
160 |
GrowlPreferencesController *preferences = [GrowlPreferencesController sharedController];
|
|
ingmarstein@2588
|
161 |
|
|
boredzo@1385
|
162 |
NSDistributedNotificationCenter *NSDNC = [NSDistributedNotificationCenter defaultCenter];
|
|
boredzo@1385
|
163 |
|
|
boredzo@1385
|
164 |
[NSDNC addObserver:self
|
|
ingmarstein@1571
|
165 |
selector:@selector(preferencesChanged:)
|
|
boredzo@1385
|
166 |
name:GrowlPreferencesChanged
|
|
boredzo@1385
|
167 |
object:nil];
|
|
boredzo@1385
|
168 |
[NSDNC addObserver:self
|
|
ingmarstein@1571
|
169 |
selector:@selector(showPreview:)
|
|
ingmarstein@1445
|
170 |
name:GrowlPreview
|
|
ingmarstein@1445
|
171 |
object:nil];
|
|
ingmarstein@1445
|
172 |
[NSDNC addObserver:self
|
|
ingmarstein@1571
|
173 |
selector:@selector(shutdown:)
|
|
boredzo@1385
|
174 |
name:GROWL_SHUTDOWN
|
|
boredzo@1385
|
175 |
object:nil];
|
|
boredzo@1385
|
176 |
[NSDNC addObserver:self
|
|
ingmarstein@1571
|
177 |
selector:@selector(replyToPing:)
|
|
boredzo@1385
|
178 |
name:GROWL_PING
|
|
boredzo@1385
|
179 |
object:nil];
|
|
ingmarstein@1760
|
180 |
|
|
ingmarstein@1893
|
181 |
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
|
|
ingmarstein@1893
|
182 |
[nc addObserver:self
|
|
ingmarstein@1893
|
183 |
selector:@selector(notificationClicked:)
|
|
ingmarstein@1893
|
184 |
name:GROWL_NOTIFICATION_CLICKED
|
|
ingmarstein@1893
|
185 |
object:nil];
|
|
ingmarstein@1893
|
186 |
[nc addObserver:self
|
|
ingmarstein@1893
|
187 |
selector:@selector(notificationTimedOut:)
|
|
ingmarstein@1893
|
188 |
name:GROWL_NOTIFICATION_TIMED_OUT
|
|
ingmarstein@1893
|
189 |
object:nil];
|
|
ingmarstein@1905
|
190 |
|
|
ingmarstein@2452
|
191 |
ticketController = [GrowlTicketController sharedController];
|
|
boredzo@1385
|
192 |
|
|
evands@3703
|
193 |
[GrowlApplicationBridge setGrowlDelegate:self];
|
|
evands@3703
|
194 |
|
|
boredzo@1385
|
195 |
[self versionDictionary];
|
|
boredzo@1385
|
196 |
|
|
boredzo@3083
|
197 |
NSString *file = [[NSBundle mainBundle] pathForResource:@"GrowlDefaults" ofType:@"plist"];
|
|
boredzo@3083
|
198 |
NSURL *fileURL = [NSURL fileURLWithPath:file];
|
|
ingmarstein@2673
|
199 |
NSDictionary *defaultDefaults = (NSDictionary *)createPropertyListFromURL((NSURL *)fileURL, kCFPropertyListImmutable, NULL, NULL);
|
|
ingmarstein@2652
|
200 |
if (defaultDefaults) {
|
|
ingmarstein@2652
|
201 |
[preferences registerDefaults:defaultDefaults];
|
|
ingmarstein@2652
|
202 |
[defaultDefaults release];
|
|
ingmarstein@2652
|
203 |
}
|
|
boredzo@1385
|
204 |
|
|
boredzo@4050
|
205 |
if ([GrowlPathUtilities runningHelperAppBundle] != [NSBundle mainBundle]) {
|
|
boredzo@4050
|
206 |
/*We are not the real GHA.
|
|
boredzo@4050
|
207 |
*We are another GHA that a pre-1.1.3 GAB has invoked to register an application by a plist file.
|
|
boredzo@4050
|
208 |
*This means that we should not start up the pathway controller; we should, instead, start up the plist-file pathway directly, and wait up to one second for -application:openFile: messages, and forward them to the plist-file pathway (as appropriate), and quit one second after the last one.
|
|
boredzo@4050
|
209 |
*/
|
|
boredzo@4050
|
210 |
NSLog(@"%@", @"It appears that at least one other instance of Growl is running. This one will quit.");
|
|
boredzo@4050
|
211 |
quitAfterOpen = YES;
|
|
boredzo@4050
|
212 |
} else {
|
|
boredzo@4050
|
213 |
//This class doesn't exist in the prefpane.
|
|
boredzo@4050
|
214 |
Class pathwayControllerClass = NSClassFromString(@"GrowlPathwayController");
|
|
boredzo@4050
|
215 |
if (pathwayControllerClass)
|
|
boredzo@4050
|
216 |
[pathwayControllerClass sharedController];
|
|
boredzo@4050
|
217 |
}
|
|
boredzo@4050
|
218 |
|
|
boredzo@1385
|
219 |
[self preferencesChanged:nil];
|
|
boredzo@1415
|
220 |
|
|
boredzo@1415
|
221 |
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self
|
|
boredzo@1415
|
222 |
selector:@selector(applicationLaunched:)
|
|
boredzo@1415
|
223 |
name:NSWorkspaceDidLaunchApplicationNotification
|
|
boredzo@1415
|
224 |
object:nil];
|
|
boredzo@1415
|
225 |
|
|
ingmarstein@1474
|
226 |
growlIcon = [[NSImage imageNamed:@"NSApplicationIcon"] retain];
|
|
ingmarstein@1448
|
227 |
|
|
rudy@2961
|
228 |
GrowlIdleStatusController_init();
|
|
ingmarstein@2597
|
229 |
[nc addObserver:self
|
|
ingmarstein@2597
|
230 |
selector:@selector(idleStatus:)
|
|
ingmarstein@2597
|
231 |
name:@"GrowlIdleStatus"
|
|
ingmarstein@2597
|
232 |
object:nil];
|
|
ingmarstein@2386
|
233 |
|
|
ingmarstein@1581
|
234 |
NSDate *lastCheck = [preferences objectForKey:LastUpdateCheckKey];
|
|
ingmarstein@1581
|
235 |
NSDate *now = [NSDate date];
|
|
ingmarstein@1581
|
236 |
if (!lastCheck || [now timeIntervalSinceDate:lastCheck] > UPDATE_CHECK_INTERVAL) {
|
|
ingmarstein@2556
|
237 |
checkVersion(NULL, self);
|
|
ingmarstein@1581
|
238 |
lastCheck = now;
|
|
ingmarstein@1581
|
239 |
}
|
|
ingmarstein@2556
|
240 |
CFRunLoopTimerContext context = {0, self, NULL, NULL, NULL};
|
|
rudy@3049
|
241 |
updateTimer = CFRunLoopTimerCreate(kCFAllocatorDefault, [[lastCheck addTimeInterval:UPDATE_CHECK_INTERVAL] timeIntervalSinceReferenceDate], UPDATE_CHECK_INTERVAL, 0, 0, checkVersion, &context);
|
|
rudy@2828
|
242 |
CFRunLoopAddTimer(CFRunLoopGetMain(), updateTimer, kCFRunLoopCommonModes);
|
|
ingmarstein@1918
|
243 |
|
|
ingmarstein@1941
|
244 |
// create and register GrowlNotificationCenter
|
|
ingmarstein@1918
|
245 |
growlNotificationCenter = [[GrowlNotificationCenter alloc] init];
|
|
ingmarstein@1941
|
246 |
growlNotificationCenterConnection = [[NSConnection alloc] initWithReceivePort:[NSPort port] sendPort:nil];
|
|
ingmarstein@2721
|
247 |
//[growlNotificationCenterConnection enableMultipleThreads];
|
|
ingmarstein@1941
|
248 |
[growlNotificationCenterConnection setRootObject:growlNotificationCenter];
|
|
boredzo@2236
|
249 |
if (![growlNotificationCenterConnection registerName:@"GrowlNotificationCenter"])
|
|
boredzo@3289
|
250 |
NSLog(@"WARNING: could not register GrowlNotificationCenter for interprocess access");
|
|
boredzo@4051
|
251 |
|
|
boredzo@4051
|
252 |
soundCompletionCallback = NewSystemSoundCompletionUPP(soundCompletionCallbackProc);
|
|
boredzo@1385
|
253 |
}
|
|
boredzo@1385
|
254 |
|
|
boredzo@1385
|
255 |
return self;
|
|
boredzo@1385
|
256 |
}
|
|
boredzo@1385
|
257 |
|
|
ingmarstein@2597
|
258 |
- (void) idleStatus:(NSNotification *)notification {
|
|
evands@2932
|
259 |
if ([[notification object] isEqualToString:@"Idle"]) {
|
|
evands@2932
|
260 |
GrowlPreferencesController *preferences = [GrowlPreferencesController sharedController];
|
|
evands@2932
|
261 |
int idleThreshold;
|
|
evands@2932
|
262 |
NSNumber *value = [preferences objectForKey:@"IdleThreshold"];
|
|
evands@2932
|
263 |
NSString *description;
|
|
ingmarstein@2943
|
264 |
|
|
evands@2932
|
265 |
idleThreshold = (value ? [value intValue] : MACHINE_IDLE_THRESHOLD);
|
|
evands@2932
|
266 |
description = [NSString stringWithFormat:NSLocalizedString(@"No activity for more than %d seconds.", nil), idleThreshold];
|
|
ingmarstein@2943
|
267 |
if ([preferences stickyWhenAway])
|
|
evands@2932
|
268 |
description = [description stringByAppendingString:NSLocalizedString(@" New notifications will be sticky.", nil)];
|
|
ingmarstein@2943
|
269 |
|
|
evands@2932
|
270 |
[GrowlApplicationBridge notifyWithTitle:NSLocalizedString(@"User went idle", nil)
|
|
evands@2932
|
271 |
description:description
|
|
evands@3703
|
272 |
notificationName:USER_WENT_IDLE_NOTIFICATION
|
|
evands@2932
|
273 |
iconData:growlIconData
|
|
evands@2932
|
274 |
priority:-1
|
|
evands@2932
|
275 |
isSticky:NO
|
|
evands@2932
|
276 |
clickContext:nil
|
|
evands@2932
|
277 |
identifier:nil];
|
|
evands@2932
|
278 |
} else {
|
|
evands@2932
|
279 |
[GrowlApplicationBridge notifyWithTitle:NSLocalizedString(@"User returned", nil)
|
|
evands@2932
|
280 |
description:NSLocalizedString(@"User activity detected. New notifications will not be sticky by default.", nil)
|
|
evands@3703
|
281 |
notificationName:USER_RETURNED_NOTIFICATION
|
|
evands@2932
|
282 |
iconData:growlIconData
|
|
evands@2932
|
283 |
priority:-1
|
|
evands@2932
|
284 |
isSticky:NO
|
|
evands@2932
|
285 |
clickContext:nil
|
|
evands@2932
|
286 |
identifier:nil];
|
|
ingmarstein@2597
|
287 |
}
|
|
ingmarstein@2597
|
288 |
}
|
|
ingmarstein@2597
|
289 |
|
|
ofri@2581
|
290 |
- (void) destroy {
|
|
boredzo@1429
|
291 |
//free your world
|
|
evands@2925
|
292 |
[mainThread release]; mainThread = nil;
|
|
boredzo@3112
|
293 |
Class pathwayControllerClass = NSClassFromString(@"GrowlPathwayController");
|
|
boredzo@3112
|
294 |
if (pathwayControllerClass)
|
|
boredzo@3112
|
295 |
[(id)[pathwayControllerClass sharedController] setServerEnabled:NO];
|
|
evands@2925
|
296 |
[destinations release]; destinations = nil;
|
|
evands@2925
|
297 |
[growlIcon release]; growlIcon = nil;
|
|
boredzo@4023
|
298 |
[defaultDisplayPlugin release]; defaultDisplayPlugin = nil;
|
|
ingmarstein@2649
|
299 |
|
|
boredzo@3082
|
300 |
[versionCheckURL release];
|
|
ingmarstein@2597
|
301 |
|
|
rudy@2961
|
302 |
GrowlIdleStatusController_dealloc();
|
|
ingmarstein@3169
|
303 |
|
|
rudy@3049
|
304 |
CFRunLoopTimerInvalidate(updateTimer);
|
|
rudy@3049
|
305 |
CFRelease(updateTimer);
|
|
ingmarstein@2597
|
306 |
|
|
ingmarstein@2597
|
307 |
[[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:nil];
|
|
ingmarstein@1579
|
308 |
|
|
ingmarstein@1941
|
309 |
[growlNotificationCenterConnection invalidate];
|
|
evands@2925
|
310 |
[growlNotificationCenterConnection release]; growlNotificationCenterConnection = nil;
|
|
evands@2925
|
311 |
[growlNotificationCenter release]; growlNotificationCenter = nil;
|
|
ingmarstein@1941
|
312 |
|
|
ingmarstein@1971
|
313 |
cdsaShutdown();
|
|
boredzo@4051
|
314 |
|
|
boredzo@4051
|
315 |
DisposeSystemSoundCompletionUPP(soundCompletionCallback);
|
|
ingmarstein@2591
|
316 |
|
|
ofri@2581
|
317 |
[super destroy];
|
|
boredzo@1429
|
318 |
}
|
|
boredzo@1429
|
319 |
|
|
boredzo@3069
|
320 |
#pragma mark Guts
|
|
boredzo@1385
|
321 |
|
|
ingmarstein@1445
|
322 |
- (void) showPreview:(NSNotification *) note {
|
|
evands@3507
|
323 |
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
|
ingmarstein@1445
|
324 |
NSString *displayName = [note object];
|
|
boredzo@3061
|
325 |
GrowlDisplayPlugin *displayPlugin = (GrowlDisplayPlugin *)[[GrowlPluginController sharedController] displayPluginInstanceWithName:displayName author:nil version:nil type:nil];
|
|
boredzo@1500
|
326 |
|
|
evands@3709
|
327 |
NSString *desc = [[NSString alloc] initWithFormat:NSLocalizedString(@"This is a preview of the %@ display", "Preview message shown when clicking Preview in the system preferences pane. %@ becomes the name of the display style being used."), displayName];
|
|
ingmarstein@1643
|
328 |
NSNumber *priority = [[NSNumber alloc] initWithInt:0];
|
|
ingmarstein@2365
|
329 |
NSNumber *sticky = [[NSNumber alloc] initWithBool:NO];
|
|
ingmarstein@1637
|
330 |
NSDictionary *info = [[NSDictionary alloc] initWithObjectsAndKeys:
|
|
ingmarstein@2695
|
331 |
@"Growl", GROWL_APP_NAME,
|
|
ingmarstein@2695
|
332 |
@"Preview", GROWL_NOTIFICATION_NAME,
|
|
evands@3709
|
333 |
NSLocalizedString(@"Preview", "Title of the Preview notification shown to demonstrate Growl displays"), GROWL_NOTIFICATION_TITLE,
|
|
ingmarstein@1643
|
334 |
desc, GROWL_NOTIFICATION_DESCRIPTION,
|
|
ingmarstein@1643
|
335 |
priority, GROWL_NOTIFICATION_PRIORITY,
|
|
ingmarstein@1643
|
336 |
sticky, GROWL_NOTIFICATION_STICKY,
|
|
ingmarstein@1643
|
337 |
growlIcon, GROWL_NOTIFICATION_ICON,
|
|
ingmarstein@1637
|
338 |
nil];
|
|
ingmarstein@1643
|
339 |
[desc release];
|
|
ingmarstein@1643
|
340 |
[priority release];
|
|
ingmarstein@1643
|
341 |
[sticky release];
|
|
ingmarstein@2695
|
342 |
GrowlApplicationNotification *notification = [[GrowlApplicationNotification alloc] initWithDictionary:info];
|
|
ingmarstein@1637
|
343 |
[info release];
|
|
ingmarstein@2695
|
344 |
[displayPlugin displayNotification:notification];
|
|
ingmarstein@2695
|
345 |
[notification release];
|
|
evands@3507
|
346 |
[pool release];
|
|
ingmarstein@1445
|
347 |
}
|
|
ingmarstein@1445
|
348 |
|
|
evands@4152
|
349 |
/*!
|
|
evands@4152
|
350 |
* @brief Get address data for a Growl server
|
|
evands@4152
|
351 |
*
|
|
evands@4152
|
352 |
* @param name The name of the server
|
|
evands@4152
|
353 |
* @result An NSData which contains a (struct sockaddr *)'s data. This may actually be a sockaddr_in or a sockaddr_in6.
|
|
evands@4152
|
354 |
*/
|
|
evands@4152
|
355 |
- (NSData *)addressDataForGrowlServerWithName:(NSString *)name
|
|
evands@4152
|
356 |
{
|
|
evands@4152
|
357 |
NSNetService *service = [[[NSNetService alloc] initWithDomain:@"local." type:@"_growl._tcp." name:name] autorelease];
|
|
evands@4152
|
358 |
if (!service) {
|
|
evands@4152
|
359 |
/* No such service exists. The computer is probably offline. */
|
|
evands@4152
|
360 |
return nil;
|
|
evands@4152
|
361 |
}
|
|
evands@4152
|
362 |
|
|
evands@4152
|
363 |
/* Work for 8 seconds to resolve the net service to an IP and port. We should be running
|
|
evands@4152
|
364 |
* on a thread, so blocking is fine.
|
|
evands@4152
|
365 |
*/
|
|
evands@4152
|
366 |
[service scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:@"PrivateGrowlMode"];
|
|
evands@4152
|
367 |
[service resolveWithTimeout:8.0];
|
|
evands@4152
|
368 |
CFAbsoluteTime deadline = CFAbsoluteTimeGetCurrent() + 8.0;
|
|
evands@4152
|
369 |
CFTimeInterval remaining;
|
|
evands@4152
|
370 |
while ((remaining = (deadline - CFAbsoluteTimeGetCurrent())) > 0 && [[service addresses] count] == 0) {
|
|
evands@4152
|
371 |
CFRunLoopRunInMode((CFStringRef)@"PrivateGrowlMode", remaining, true);
|
|
evands@4152
|
372 |
}
|
|
evands@4152
|
373 |
[service stop];
|
|
evands@4152
|
374 |
|
|
evands@4152
|
375 |
NSArray *addresses = [service addresses];
|
|
evands@4152
|
376 |
if (![addresses count]) {
|
|
evands@4152
|
377 |
/* Lookup failed */
|
|
evands@4152
|
378 |
return nil;
|
|
evands@4152
|
379 |
}
|
|
evands@4152
|
380 |
|
|
evands@4152
|
381 |
return [addresses objectAtIndex:0];
|
|
evands@4152
|
382 |
}
|
|
evands@4152
|
383 |
|
|
boredzo@2273
|
384 |
- (void) forwardDictionary:(NSDictionary *)dict withSelector:(SEL)forwardMethod {
|
|
ingmarstein@2372
|
385 |
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
|
ingmarstein@2372
|
386 |
|
|
boredzo@2274
|
387 |
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
|
ingmarstein@2388
|
388 |
NSNumber *requestTimeout = [defaults objectForKey:@"ForwardingRequestTimeout"];
|
|
ingmarstein@2388
|
389 |
NSNumber *replyTimeout = [defaults objectForKey:@"ForwardingReplyTimeout"];
|
|
boredzo@2273
|
390 |
NSEnumerator *enumerator = [destinations objectEnumerator];
|
|
boredzo@2273
|
391 |
NSDictionary *entry;
|
|
boredzo@2273
|
392 |
while ((entry = [enumerator nextObject])) {
|
|
ingmarstein@2661
|
393 |
if (getBooleanForKey(entry, @"use") && getBooleanForKey(entry, @"active")) {
|
|
evands@4152
|
394 |
/* Note: This assumes that all forwarding destinations are within the local network.
|
|
evands@4152
|
395 |
* When domain names and IPs can be used, this needs to change.
|
|
evands@4152
|
396 |
*/
|
|
evands@4152
|
397 |
NSData *destAddress = [self addressDataForGrowlServerWithName:[entry objectForKey:@"computer"]];
|
|
evands@4152
|
398 |
if (!destAddress) {
|
|
evands@4152
|
399 |
/* No destination address. Nothing to see here; move along. */
|
|
evands@4152
|
400 |
#ifdef DEBUG
|
|
evands@4152
|
401 |
NSLog(@"Could not obtain destination address for %@", [entry objectForKey:@"computer"]);
|
|
evands@4152
|
402 |
#endif
|
|
evands@4152
|
403 |
continue;
|
|
evands@4152
|
404 |
}
|
|
boredzo@3064
|
405 |
NSString *password = [entry objectForKey:@"password"];
|
|
evands@4152
|
406 |
|
|
evands@4152
|
407 |
/* Send via DO if possible */
|
|
ingmarstein@2285
|
408 |
NSSocketPort *serverPort = [[NSSocketPort alloc]
|
|
boredzo@2273
|
409 |
initRemoteWithProtocolFamily:AF_INET
|
|
boredzo@2273
|
410 |
socketType:SOCK_STREAM
|
|
boredzo@2273
|
411 |
protocol:IPPROTO_TCP
|
|
boredzo@2273
|
412 |
address:destAddress];
|
|
boredzo@2273
|
413 |
|
|
boredzo@2273
|
414 |
NSConnection *connection = [[NSConnection alloc] initWithReceivePort:nil
|
|
boredzo@2273
|
415 |
sendPort:serverPort];
|
|
boredzo@2273
|
416 |
MD5Authenticator *auth = [[MD5Authenticator alloc] initWithPassword:password];
|
|
boredzo@2273
|
417 |
[connection setDelegate:auth];
|
|
boredzo@2274
|
418 |
|
|
ingmarstein@2388
|
419 |
if (requestTimeout && [requestTimeout respondsToSelector:@selector(floatValue)])
|
|
ingmarstein@2388
|
420 |
[connection setRequestTimeout:[requestTimeout floatValue]];
|
|
ingmarstein@2388
|
421 |
if (replyTimeout && [replyTimeout respondsToSelector:@selector(floatValue)])
|
|
ingmarstein@2388
|
422 |
[connection setReplyTimeout:[replyTimeout floatValue]];
|
|
boredzo@2274
|
423 |
|
|
boredzo@2273
|
424 |
@try {
|
|
boredzo@2273
|
425 |
NSDistantObject *theProxy = [connection rootProxy];
|
|
boredzo@2273
|
426 |
[theProxy setProtocolForProxy:@protocol(GrowlNotificationProtocol)];
|
|
ingmarstein@2673
|
427 |
NSProxy <GrowlNotificationProtocol> *growlProxy = (NSProxy <GrowlNotificationProtocol> *)theProxy;
|
|
boredzo@2273
|
428 |
[growlProxy performSelector:forwardMethod withObject:dict];
|
|
ingmarstein@2634
|
429 |
} @catch (NSException *e) {
|
|
evands@4080
|
430 |
NSString *addressString = createStringWithAddressData(destAddress);
|
|
evands@4080
|
431 |
NSString *hostName = createHostNameForAddressData(destAddress);
|
|
evands@4080
|
432 |
if ([[e name] isEqualToString:NSFailedAuthenticationException]) {
|
|
ingmarstein@2372
|
433 |
NSLog(@"Authentication failed while forwarding to %@ (%@)",
|
|
ingmarstein@2634
|
434 |
addressString, hostName);
|
|
evands@4080
|
435 |
} else {
|
|
evands@4080
|
436 |
NSLog(@"Warning: Exception %@ while forwarding Growl registration or notification (%@) to %@ (%@). Is that system on and connected?",
|
|
evands@4080
|
437 |
e, NSStringFromSelector(forwardMethod), addressString, hostName);
|
|
evands@4080
|
438 |
}
|
|
evands@4080
|
439 |
[addressString release];
|
|
evands@4080
|
440 |
[hostName release];
|
|
evands@4080
|
441 |
|
|
boredzo@2273
|
442 |
} @finally {
|
|
boredzo@2273
|
443 |
[connection invalidate];
|
|
boredzo@2273
|
444 |
[serverPort invalidate];
|
|
boredzo@2273
|
445 |
[serverPort release];
|
|
boredzo@2273
|
446 |
[connection release];
|
|
boredzo@2273
|
447 |
[auth release];
|
|
boredzo@2273
|
448 |
}
|
|
boredzo@2273
|
449 |
}
|
|
boredzo@2273
|
450 |
}
|
|
ingmarstein@2372
|
451 |
|
|
ingmarstein@2372
|
452 |
[pool release];
|
|
boredzo@2273
|
453 |
}
|
|
boredzo@2273
|
454 |
|
|
ingmarstein@2288
|
455 |
- (void) forwardNotification:(NSDictionary *)dict {
|
|
ingmarstein@2288
|
456 |
[self forwardDictionary:dict withSelector:@selector(postNotificationWithDictionary:)];
|
|
ingmarstein@2288
|
457 |
}
|
|
ingmarstein@2288
|
458 |
|
|
ingmarstein@2288
|
459 |
- (void) forwardRegistration:(NSDictionary *)dict {
|
|
ingmarstein@2288
|
460 |
[self forwardDictionary:dict withSelector:@selector(registerApplicationWithDictionary:)];
|
|
ingmarstein@2288
|
461 |
}
|
|
ingmarstein@2288
|
462 |
|
|
boredzo@4051
|
463 |
#pragma mark Retrieving sounds
|
|
boredzo@4051
|
464 |
|
|
boredzo@4051
|
465 |
- (OSStatus) getFSRef:(out FSRef *)outRef forSoundNamed:(NSString *)soundName {
|
|
boredzo@4051
|
466 |
BOOL foundIt = NO;
|
|
boredzo@4051
|
467 |
|
|
boredzo@4051
|
468 |
NSArray *soundTypes = [NSSound soundUnfilteredFileTypes];
|
|
boredzo@4051
|
469 |
|
|
boredzo@4051
|
470 |
//Throw away all the HFS types, leaving only filename extensions.
|
|
boredzo@4051
|
471 |
NSPredicate *noHFSTypesPredicate = [NSPredicate predicateWithFormat:@"NOT (self BEGINSWITH \"'\")"];
|
|
boredzo@4051
|
472 |
soundTypes = [soundTypes filteredArrayUsingPredicate:noHFSTypesPredicate];
|
|
boredzo@4051
|
473 |
|
|
boredzo@4051
|
474 |
//If there are no types left, abort.
|
|
boredzo@4051
|
475 |
if ([soundTypes count] == 0U)
|
|
boredzo@4051
|
476 |
return unknownFormatErr;
|
|
boredzo@4051
|
477 |
|
|
boredzo@4051
|
478 |
//We only want the filename extensions, not the HFS types.
|
|
boredzo@4051
|
479 |
//Also, we want the longest one last so that we can use lastObject's length to allocate the buffer.
|
|
boredzo@4051
|
480 |
NSSortDescriptor *sortDesc = [[[NSSortDescriptor alloc] initWithKey:@"length" ascending:YES] autorelease];
|
|
boredzo@4051
|
481 |
NSArray *sortDescs = [NSArray arrayWithObject:sortDesc];
|
|
boredzo@4051
|
482 |
soundTypes = [soundTypes sortedArrayUsingDescriptors:sortDescs];
|
|
boredzo@4051
|
483 |
|
|
boredzo@4051
|
484 |
NSMutableArray *filenames = [NSMutableArray arrayWithCapacity:[soundTypes count]];
|
|
boredzo@4051
|
485 |
NSEnumerator *soundTypeEnum;
|
|
boredzo@4051
|
486 |
NSString *soundType;
|
|
boredzo@4051
|
487 |
soundTypeEnum = [soundTypes objectEnumerator];
|
|
boredzo@4051
|
488 |
while ((soundType = [soundTypeEnum nextObject])) {
|
|
boredzo@4051
|
489 |
[filenames addObject:[soundName stringByAppendingPathExtension:soundType]];
|
|
boredzo@4051
|
490 |
}
|
|
boredzo@4051
|
491 |
|
|
boredzo@4051
|
492 |
NSEnumerator *filenamesEnum;
|
|
boredzo@4051
|
493 |
NSString *filename;
|
|
boredzo@4051
|
494 |
|
|
boredzo@4051
|
495 |
//The additions are for appending '.' plus the longest filename extension.
|
|
boredzo@4051
|
496 |
size_t filenameLen = [soundName length] + 1U + [[soundTypes lastObject] length];
|
|
boredzo@4051
|
497 |
unichar *filenameBuf = malloc(filenameLen * sizeof(unichar));
|
|
boredzo@4051
|
498 |
if (!filenameBuf) return memFullErr;
|
|
boredzo@4051
|
499 |
|
|
boredzo@4051
|
500 |
FSRef folderRef;
|
|
boredzo@4051
|
501 |
OSStatus err;
|
|
boredzo@4051
|
502 |
|
|
boredzo@4051
|
503 |
err = FSFindFolder(kUserDomain, kSystemSoundsFolderType, kDontCreateFolder, &folderRef);
|
|
boredzo@4051
|
504 |
if (err == noErr) {
|
|
boredzo@4051
|
505 |
//Folder exists. If it didn't, FSFindFolder would have returned fnfErr.
|
|
boredzo@4051
|
506 |
filenamesEnum = [filenames objectEnumerator];
|
|
boredzo@4051
|
507 |
while ((filename = [filenamesEnum nextObject])) {
|
|
boredzo@4051
|
508 |
[filename getCharacters:filenameBuf];
|
|
boredzo@4051
|
509 |
err = FSMakeFSRefUnicode(&folderRef, [filename length], filenameBuf, kTextEncodingUnknown, outRef);
|
|
boredzo@4051
|
510 |
if (err == noErr) {
|
|
boredzo@4051
|
511 |
foundIt = YES;
|
|
boredzo@4051
|
512 |
break;
|
|
boredzo@4051
|
513 |
}
|
|
boredzo@4051
|
514 |
}
|
|
boredzo@4051
|
515 |
}
|
|
boredzo@4051
|
516 |
|
|
boredzo@4051
|
517 |
if (!foundIt) {
|
|
boredzo@4051
|
518 |
err = FSFindFolder(kLocalDomain, kSystemSoundsFolderType, kDontCreateFolder, &folderRef);
|
|
boredzo@4051
|
519 |
if (err == noErr) {
|
|
boredzo@4051
|
520 |
//Folder exists. If it didn't, FSFindFolder would have returned fnfErr.
|
|
boredzo@4051
|
521 |
filenamesEnum = [filenames objectEnumerator];
|
|
boredzo@4051
|
522 |
while ((filename = [filenamesEnum nextObject])) {
|
|
boredzo@4051
|
523 |
[filename getCharacters:filenameBuf];
|
|
boredzo@4051
|
524 |
err = FSMakeFSRefUnicode(&folderRef, [filename length], filenameBuf, kTextEncodingUnknown, outRef);
|
|
boredzo@4051
|
525 |
if (err == noErr) {
|
|
boredzo@4051
|
526 |
foundIt = YES;
|
|
boredzo@4051
|
527 |
break;
|
|
boredzo@4051
|
528 |
}
|
|
boredzo@4051
|
529 |
}
|
|
boredzo@4051
|
530 |
}
|
|
boredzo@4051
|
531 |
}
|
|
boredzo@4051
|
532 |
|
|
boredzo@4051
|
533 |
if (!foundIt) {
|
|
boredzo@4051
|
534 |
err = FSFindFolder(kSystemDomain, kSystemSoundsFolderType, kDontCreateFolder, &folderRef);
|
|
boredzo@4051
|
535 |
if (err == noErr) {
|
|
boredzo@4051
|
536 |
//Folder exists. If it didn't, FSFindFolder would have returned fnfErr.
|
|
boredzo@4051
|
537 |
filenamesEnum = [filenames objectEnumerator];
|
|
boredzo@4051
|
538 |
while ((filename = [filenamesEnum nextObject])) {
|
|
boredzo@4051
|
539 |
[filename getCharacters:filenameBuf];
|
|
boredzo@4051
|
540 |
err = FSMakeFSRefUnicode(&folderRef, [filename length], filenameBuf, kTextEncodingUnknown, outRef);
|
|
boredzo@4051
|
541 |
if (err == noErr) {
|
|
boredzo@4051
|
542 |
foundIt = YES;
|
|
boredzo@4051
|
543 |
break;
|
|
boredzo@4051
|
544 |
}
|
|
boredzo@4051
|
545 |
}
|
|
boredzo@4051
|
546 |
}
|
|
boredzo@4051
|
547 |
}
|
|
boredzo@4051
|
548 |
|
|
boredzo@4051
|
549 |
free(filenameBuf);
|
|
boredzo@4051
|
550 |
|
|
boredzo@4051
|
551 |
return err;
|
|
boredzo@4051
|
552 |
}
|
|
boredzo@4051
|
553 |
|
|
boredzo@3289
|
554 |
#pragma mark Dispatching notifications
|
|
boredzo@3289
|
555 |
|
|
boredzo@1385
|
556 |
- (void) dispatchNotificationWithDictionary:(NSDictionary *) dict {
|
|
evands@3538
|
557 |
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
|
evands@3538
|
558 |
|
|
boredzo@3076
|
559 |
[[GrowlLog sharedController] writeNotificationDictionaryToLog:dict];
|
|
ingmarstein@1798
|
560 |
|
|
boredzo@1385
|
561 |
// Make sure this notification is actually registered
|
|
boredzo@3064
|
562 |
NSString *appName = [dict objectForKey:GROWL_APP_NAME];
|
|
ingmarstein@2452
|
563 |
GrowlApplicationTicket *ticket = [ticketController ticketForApplicationName:appName];
|
|
boredzo@3064
|
564 |
NSString *notificationName = [dict objectForKey:GROWL_NOTIFICATION_NAME];
|
|
evands@2925
|
565 |
if (!ticket || ![ticket isNotificationAllowed:notificationName]) {
|
|
boredzo@1385
|
566 |
// Either the app isn't registered or the notification is turned off
|
|
boredzo@1385
|
567 |
// We should do nothing
|
|
evands@3565
|
568 |
[pool release];
|
|
boredzo@1385
|
569 |
return;
|
|
evands@2925
|
570 |
}
|
|
boredzo@1385
|
571 |
|
|
ingmarstein@1645
|
572 |
NSMutableDictionary *aDict = [dict mutableCopy];
|
|
boredzo@1385
|
573 |
|
|
boredzo@1385
|
574 |
// Check icon
|
|
ingmarstein@2220
|
575 |
Class NSImageClass = [NSImage class];
|
|
boredzo@2238
|
576 |
Class NSDataClass = [NSData class];
|
|
boredzo@1385
|
577 |
NSImage *icon = nil;
|
|
boredzo@3064
|
578 |
id image = [aDict objectForKey:GROWL_NOTIFICATION_ICON];
|
|
boredzo@2236
|
579 |
if (image) {
|
|
ingmarstein@2297
|
580 |
if ([image isKindOfClass:NSImageClass])
|
|
ingmarstein@2297
|
581 |
icon = [image copy];
|
|
ingmarstein@2297
|
582 |
else if ([image isKindOfClass:NSDataClass])
|
|
boredzo@2236
|
583 |
icon = [[NSImage alloc] initWithData:image];
|
|
boredzo@2236
|
584 |
}
|
|
boredzo@2236
|
585 |
if (!icon)
|
|
ingmarstein@1583
|
586 |
icon = [[ticket icon] copy];
|
|
boredzo@2236
|
587 |
|
|
boredzo@1385
|
588 |
if (icon) {
|
|
boredzo@3064
|
589 |
[aDict setObject:icon forKey:GROWL_NOTIFICATION_ICON];
|
|
ingmarstein@1583
|
590 |
[icon release];
|
|
boredzo@1385
|
591 |
} else {
|
|
boredzo@3289
|
592 |
//We get here when no image existed, and if an NSData existed, an image could not be created from it.
|
|
boredzo@3289
|
593 |
//In the latter case, we don't need to keep that non-image NSData around.
|
|
boredzo@3065
|
594 |
[aDict removeObjectForKey:GROWL_NOTIFICATION_ICON];
|
|
boredzo@1385
|
595 |
}
|
|
boredzo@1385
|
596 |
|
|
boredzo@1385
|
597 |
// If app icon present, convert to NSImage
|
|
boredzo@2238
|
598 |
icon = nil;
|
|
boredzo@3064
|
599 |
image = [aDict objectForKey:GROWL_NOTIFICATION_APP_ICON];
|
|
ingmarstein@2220
|
600 |
if (image) {
|
|
ingmarstein@2297
|
601 |
if ([image isKindOfClass:NSImageClass])
|
|
ingmarstein@2297
|
602 |
icon = [image copy];
|
|
ingmarstein@2297
|
603 |
else if ([image isKindOfClass:NSDataClass])
|
|
ingmarstein@2220
|
604 |
icon = [[NSImage alloc] initWithData:image];
|
|
boredzo@2238
|
605 |
}
|
|
boredzo@2238
|
606 |
if (icon) {
|
|
boredzo@3064
|
607 |
[aDict setObject:icon forKey:GROWL_NOTIFICATION_APP_ICON];
|
|
boredzo@2238
|
608 |
[icon release];
|
|
ingmarstein@2386
|
609 |
} else
|
|
boredzo@2238
|
610 |
[aDict removeObjectForKey:GROWL_NOTIFICATION_APP_ICON];
|
|
boredzo@1385
|
611 |
|
|
boredzo@1385
|
612 |
// To avoid potential exceptions, make sure we have both text and title
|
|
boredzo@3064
|
613 |
if (![aDict objectForKey:GROWL_NOTIFICATION_DESCRIPTION])
|
|
boredzo@3064
|
614 |
[aDict setObject:@"" forKey:GROWL_NOTIFICATION_DESCRIPTION];
|
|
boredzo@3064
|
615 |
if (![aDict objectForKey:GROWL_NOTIFICATION_TITLE])
|
|
boredzo@3064
|
616 |
[aDict setObject:@"" forKey:GROWL_NOTIFICATION_TITLE];
|
|
boredzo@1385
|
617 |
|
|
ingmarstein@1442
|
618 |
//Retrieve and set the the priority of the notification
|
|
boredzo@2501
|
619 |
GrowlNotificationTicket *notification = [ticket notificationTicketForName:notificationName];
|
|
ingmarstein@1720
|
620 |
int priority = [notification priority];
|
|
ingmarstein@1643
|
621 |
NSNumber *value;
|
|
ingmarstein@2450
|
622 |
if (priority == GrowlPriorityUnset) {
|
|
boredzo@3064
|
623 |
value = [dict objectForKey:GROWL_NOTIFICATION_PRIORITY];
|
|
boredzo@2236
|
624 |
if (!value)
|
|
ingmarstein@1643
|
625 |
value = [NSNumber numberWithInt:0];
|
|
ingmarstein@2386
|
626 |
} else
|
|
ingmarstein@1643
|
627 |
value = [NSNumber numberWithInt:priority];
|
|
boredzo@3064
|
628 |
[aDict setObject:value forKey:GROWL_NOTIFICATION_PRIORITY];
|
|
ingmarstein@1442
|
629 |
|
|
boredzo@2496
|
630 |
GrowlPreferencesController *preferences = [GrowlPreferencesController sharedController];
|
|
ingmarstein@2388
|
631 |
|
|
ingmarstein@1442
|
632 |
// Retrieve and set the sticky bit of the notification
|
|
ingmarstein@1720
|
633 |
int sticky = [notification sticky];
|
|
ingmarstein@2297
|
634 |
if (sticky >= 0)
|
|
ingmarstein@2641
|
635 |
setBooleanForKey(aDict, GROWL_NOTIFICATION_STICKY, sticky);
|
|
ingmarstein@2641
|
636 |
else if ([preferences stickyWhenAway] && !getBooleanForKey(aDict, GROWL_NOTIFICATION_STICKY))
|
|
rudy@2961
|
637 |
setBooleanForKey(aDict, GROWL_NOTIFICATION_STICKY, GrowlIdleStatusController_isIdle());
|
|
boredzo@1385
|
638 |
|
|
boredzo@1428
|
639 |
BOOL saveScreenshot = [[NSUserDefaults standardUserDefaults] boolForKey:GROWL_SCREENSHOT_MODE];
|
|
ingmarstein@2641
|
640 |
setBooleanForKey(aDict, GROWL_SCREENSHOT_MODE, saveScreenshot);
|
|
ingmarstein@2641
|
641 |
setBooleanForKey(aDict, @"ClickHandlerEnabled", [ticket clickHandlersEnabled]);
|
|
ingmarstein@1958
|
642 |
|
|
ingmarstein@2565
|
643 |
if (![preferences squelchMode]) {
|
|
ingmarstein@2695
|
644 |
GrowlDisplayPlugin *display = [notification displayPlugin];
|
|
ingmarstein@1865
|
645 |
|
|
boredzo@2236
|
646 |
if (!display)
|
|
ingmarstein@1865
|
647 |
display = [ticket displayPlugin];
|
|
boredzo@2236
|
648 |
|
|
ingmarstein@2636
|
649 |
if (!display) {
|
|
boredzo@4023
|
650 |
if (!defaultDisplayPlugin) {
|
|
ingmarstein@2636
|
651 |
NSString *displayPluginName = [[GrowlPreferencesController sharedController] defaultDisplayPluginName];
|
|
boredzo@4023
|
652 |
defaultDisplayPlugin = [(GrowlDisplayPlugin *)[[GrowlPluginController sharedController] displayPluginInstanceWithName:displayPluginName author:nil version:nil type:nil] retain];
|
|
boredzo@4024
|
653 |
if (!defaultDisplayPlugin) {
|
|
boredzo@4024
|
654 |
//User's selected default display has gone AWOL. Change to the default default.
|
|
boredzo@4024
|
655 |
NSString *file = [[NSBundle mainBundle] pathForResource:@"GrowlDefaults" ofType:@"plist"];
|
|
boredzo@4024
|
656 |
NSURL *fileURL = [NSURL fileURLWithPath:file];
|
|
boredzo@4024
|
657 |
NSDictionary *defaultDefaults = (NSDictionary *)createPropertyListFromURL((NSURL *)fileURL, kCFPropertyListImmutable, NULL, NULL);
|
|
boredzo@4024
|
658 |
if (defaultDefaults) {
|
|
boredzo@4024
|
659 |
displayPluginName = [defaultDefaults objectForKey:GrowlDisplayPluginKey];
|
|
boredzo@4024
|
660 |
if (!displayPluginName)
|
|
boredzo@4024
|
661 |
GrowlLog_log(@"No default display specified in default preferences! Perhaps your Growl installation is corrupted?");
|
|
boredzo@4024
|
662 |
else {
|
|
boredzo@4024
|
663 |
defaultDisplayPlugin = (GrowlDisplayPlugin *)[[[GrowlPluginController sharedController] displayPluginDictionaryWithName:displayPluginName author:nil version:nil type:nil] pluginInstance];
|
|
boredzo@4024
|
664 |
|
|
boredzo@4024
|
665 |
//Now fix the user's preferences to forget about the missing display plug-in.
|
|
boredzo@4024
|
666 |
[preferences setObject:displayPluginName forKey:GrowlDisplayPluginKey];
|
|
boredzo@4024
|
667 |
}
|
|
boredzo@4024
|
668 |
|
|
boredzo@4024
|
669 |
[defaultDefaults release];
|
|
boredzo@4024
|
670 |
}
|
|
boredzo@4024
|
671 |
}
|
|
ingmarstein@2636
|
672 |
}
|
|
boredzo@4023
|
673 |
display = defaultDisplayPlugin;
|
|
ingmarstein@2636
|
674 |
}
|
|
ingmarstein@1865
|
675 |
|
|
ingmarstein@2695
|
676 |
GrowlApplicationNotification *appNotification = [[GrowlApplicationNotification alloc] initWithDictionary:aDict];
|
|
ingmarstein@2695
|
677 |
[display displayNotification:appNotification];
|
|
ingmarstein@2695
|
678 |
[appNotification release];
|
|
ingmarstein@3169
|
679 |
|
|
boredzo@4051
|
680 |
NSString *soundName = [notification sound];
|
|
boredzo@4051
|
681 |
if (soundName) {
|
|
boredzo@4051
|
682 |
NSError *error = nil;
|
|
boredzo@4051
|
683 |
NSDictionary *userInfo;
|
|
boredzo@4051
|
684 |
|
|
boredzo@4051
|
685 |
FSRef soundRef;
|
|
boredzo@4051
|
686 |
OSStatus err = [self getFSRef:&soundRef forSoundNamed:soundName];
|
|
boredzo@4051
|
687 |
if (err == noErr) {
|
|
boredzo@4051
|
688 |
SystemSoundActionID actionID;
|
|
boredzo@4051
|
689 |
err = SystemSoundGetActionID(&soundRef, &actionID);
|
|
boredzo@4051
|
690 |
if (err == noErr) {
|
|
boredzo@4051
|
691 |
err = SystemSoundSetCompletionRoutine(actionID, CFRunLoopGetCurrent(), /*runLoopMode*/ NULL, soundCompletionCallback, /*refcon*/ NULL);
|
|
boredzo@4051
|
692 |
SystemSoundPlay(actionID);
|
|
evands@4152
|
693 |
userInfo = nil;
|
|
boredzo@4051
|
694 |
} else {
|
|
boredzo@4051
|
695 |
userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
|
|
boredzo@4051
|
696 |
[NSString stringWithFormat:NSLocalizedString(@"Could not load and play sound file named \"%@\": %s", /*comment*/ nil), soundName, GetMacOSStatusCommentString(err)], NSLocalizedDescriptionKey,
|
|
boredzo@4051
|
697 |
nil];
|
|
boredzo@4051
|
698 |
}
|
|
boredzo@4051
|
699 |
} else {
|
|
boredzo@4051
|
700 |
userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
|
|
boredzo@4051
|
701 |
[NSString stringWithFormat:NSLocalizedString(@"Could not find sound file named \"%@\": %s", /*comment*/ nil), soundName, GetMacOSStatusCommentString(err)], NSLocalizedDescriptionKey,
|
|
boredzo@4051
|
702 |
nil];
|
|
boredzo@4051
|
703 |
}
|
|
boredzo@4051
|
704 |
|
|
boredzo@4051
|
705 |
if (err != noErr) {
|
|
boredzo@4051
|
706 |
error = [NSError errorWithDomain:NSOSStatusErrorDomain code:err userInfo:userInfo];
|
|
boredzo@4051
|
707 |
[NSApp presentError:error];
|
|
boredzo@4051
|
708 |
}
|
|
boredzo@4051
|
709 |
}
|
|
ingmarstein@1865
|
710 |
}
|
|
boredzo@1385
|
711 |
|
|
ingmarstein@1918
|
712 |
// send to DO observers
|
|
ingmarstein@1918
|
713 |
[growlNotificationCenter notifyObservers:aDict];
|
|
ingmarstein@1816
|
714 |
|
|
ingmarstein@1816
|
715 |
[aDict release];
|
|
ingmarstein@1804
|
716 |
|
|
ingmarstein@1804
|
717 |
// forward to remote destinations
|
|
boredzo@3064
|
718 |
if (enableForward && ![dict objectForKey:GROWL_REMOTE_ADDRESS]) {
|
|
ofri@2703
|
719 |
if ([NSThread currentThread] == mainThread)
|
|
ofri@2703
|
720 |
[NSThread detachNewThreadSelector:@selector(forwardNotification:)
|
|
ofri@2703
|
721 |
toTarget:self
|
|
ofri@2703
|
722 |
withObject:dict];
|
|
ofri@2703
|
723 |
else
|
|
ofri@2703
|
724 |
[self forwardNotification:dict];
|
|
ingmarstein@2709
|
725 |
}
|
|
evands@3538
|
726 |
|
|
evands@3538
|
727 |
[pool release];
|
|
boredzo@1385
|
728 |
}
|
|
boredzo@1385
|
729 |
|
|
ingmarstein@2644
|
730 |
- (BOOL) registerApplicationWithDictionary:(NSDictionary *)userInfo {
|
|
boredzo@3076
|
731 |
[[GrowlLog sharedController] writeRegistrationDictionaryToLog:userInfo];
|
|
ingmarstein@1798
|
732 |
|
|
boredzo@3064
|
733 |
NSString *appName = [userInfo objectForKey:GROWL_APP_NAME];
|
|
ingmarstein@1448
|
734 |
|
|
ingmarstein@2452
|
735 |
GrowlApplicationTicket *newApp = [ticketController ticketForApplicationName:appName];
|
|
boredzo@1479
|
736 |
|
|
ingmarstein@1579
|
737 |
if (newApp) {
|
|
boredzo@1479
|
738 |
[newApp reregisterWithDictionary:userInfo];
|
|
ingmarstein@1579
|
739 |
} else {
|
|
boredzo@1774
|
740 |
newApp = [[[GrowlApplicationTicket alloc] initWithDictionary:userInfo] autorelease];
|
|
boredzo@1774
|
741 |
}
|
|
boredzo@1774
|
742 |
|
|
boredzo@1774
|
743 |
BOOL success = YES;
|
|
boredzo@1774
|
744 |
|
|
boredzo@1774
|
745 |
if (appName && newApp) {
|
|
ingmarstein@3022
|
746 |
if ([newApp hasChanged])
|
|
ingmarstein@3022
|
747 |
[newApp saveTicket];
|
|
ingmarstein@2452
|
748 |
[ticketController addTicket:newApp];
|
|
ingmarstein@1905
|
749 |
|
|
boredzo@3064
|
750 |
if (enableForward && ![userInfo objectForKey:GROWL_REMOTE_ADDRESS]) {
|
|
ofri@2703
|
751 |
if ([NSThread currentThread] == mainThread)
|
|
ofri@2703
|
752 |
[NSThread detachNewThreadSelector:@selector(forwardRegistration:)
|
|
ofri@2703
|
753 |
toTarget:self
|
|
ofri@2703
|
754 |
withObject:userInfo];
|
|
ofri@2703
|
755 |
else
|
|
ofri@2703
|
756 |
[self forwardRegistration:userInfo];
|
|
ofri@2703
|
757 |
}
|
|
boredzo@3060
|
758 |
} else { //!(appName && newApp)
|
|
boredzo@1774
|
759 |
NSString *filename = [(appName ? appName : @"unknown-application") stringByAppendingPathExtension:GROWL_REG_DICT_EXTENSION];
|
|
boredzo@3663
|
760 |
|
|
boredzo@3663
|
761 |
//We'll be writing the file to ~/Library/Logs/Failed Growl registrations.
|
|
boredzo@3663
|
762 |
NSFileManager *mgr = [NSFileManager defaultManager];
|
|
boredzo@3663
|
763 |
NSString *userLibraryFolder = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, /*expandTilde*/ YES) lastObject];
|
|
boredzo@3663
|
764 |
NSString *logsFolder = [userLibraryFolder stringByAppendingPathComponent:@"Logs"];
|
|
boredzo@3663
|
765 |
[mgr createDirectoryAtPath:logsFolder attributes:nil];
|
|
boredzo@3663
|
766 |
NSString *failedTicketsFolder = [logsFolder stringByAppendingPathComponent:@"Failed Growl registrations"];
|
|
boredzo@3663
|
767 |
[mgr createDirectoryAtPath:failedTicketsFolder attributes:nil];
|
|
boredzo@3663
|
768 |
NSString *path = [failedTicketsFolder stringByAppendingPathComponent:filename];
|
|
boredzo@3663
|
769 |
|
|
boredzo@3663
|
770 |
//NSFileHandle will not create the file for us, so we must create it separately.
|
|
boredzo@3663
|
771 |
[mgr createFileAtPath:path contents:nil attributes:nil];
|
|
boredzo@1774
|
772 |
|
|
boredzo@1774
|
773 |
NSFileHandle *fh = [NSFileHandle fileHandleForWritingAtPath:path];
|
|
boredzo@1774
|
774 |
[fh seekToEndOfFile];
|
|
boredzo@1774
|
775 |
if ([fh offsetInFile]) //we are not at the beginning of the file
|
|
boredzo@1774
|
776 |
[fh writeData:[@"\n---\n\n" dataUsingEncoding:NSUTF8StringEncoding]];
|
|
boredzo@1774
|
777 |
[fh writeData:[[[userInfo description] stringByAppendingString:@"\n"] dataUsingEncoding:NSUTF8StringEncoding]];
|
|
boredzo@1774
|
778 |
[fh closeFile];
|
|
boredzo@1774
|
779 |
|
|
boredzo@1774
|
780 |
if (!appName) appName = @"with no name";
|
|
boredzo@1774
|
781 |
|
|
boredzo@1774
|
782 |
NSLog(@"Failed application registration for application %@; wrote failed registration dictionary %p to %@", appName, userInfo, path);
|
|
boredzo@1774
|
783 |
success = NO;
|
|
boredzo@1774
|
784 |
}
|
|
boredzo@1774
|
785 |
|
|
boredzo@1774
|
786 |
return success;
|
|
boredzo@1385
|
787 |
}
|
|
boredzo@1385
|
788 |
|
|
boredzo@3069
|
789 |
#pragma mark Version of Growl
|
|
boredzo@3069
|
790 |
|
|
ingmarstein@1643
|
791 |
+ (NSString *) growlVersion {
|
|
boredzo@4085
|
792 |
return [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString *)kCFBundleVersionKey];
|
|
boredzo@1385
|
793 |
}
|
|
boredzo@1385
|
794 |
|
|
ingmarstein@2282
|
795 |
- (NSDictionary *) versionDictionary {
|
|
boredzo@1385
|
796 |
if (!versionInfo) {
|
|
ingmarstein@2282
|
797 |
if (version.releaseType == releaseType_svn)
|
|
toby@1514
|
798 |
version.development = strtoul(SVN_REVISION, /*endptr*/ NULL, 10);
|
|
boredzo@1385
|
799 |
|
|
ingmarstein@1643
|
800 |
NSNumber *major = [[NSNumber alloc] initWithUnsignedShort:version.major];
|
|
ingmarstein@1643
|
801 |
NSNumber *minor = [[NSNumber alloc] initWithUnsignedShort:version.minor];
|
|
ingmarstein@1643
|
802 |
NSNumber *incremental = [[NSNumber alloc] initWithUnsignedChar:version.incremental];
|
|
ingmarstein@1643
|
803 |
NSNumber *releaseType = [[NSNumber alloc] initWithUnsignedChar:version.releaseType];
|
|
ingmarstein@1643
|
804 |
NSNumber *development = [[NSNumber alloc] initWithUnsignedShort:version.development];
|
|
ingmarstein@1643
|
805 |
|
|
boredzo@3661
|
806 |
versionInfo = [[NSDictionary alloc] initWithObjectsAndKeys:
|
|
ingmarstein@2450
|
807 |
[GrowlApplicationController growlVersion], (NSString *)kCFBundleVersionKey,
|
|
ingmarstein@2450
|
808 |
|
|
ingmarstein@2450
|
809 |
major, @"Major version",
|
|
ingmarstein@2450
|
810 |
minor, @"Minor version",
|
|
ingmarstein@2450
|
811 |
incremental, @"Incremental version",
|
|
ingmarstein@2450
|
812 |
releaseTypeNames[version.releaseType], @"Release type name",
|
|
ingmarstein@2450
|
813 |
releaseType, @"Release type",
|
|
ingmarstein@2450
|
814 |
development, @"Development version",
|
|
boredzo@1385
|
815 |
|
|
boredzo@1385
|
816 |
nil];
|
|
ingmarstein@1643
|
817 |
|
|
ingmarstein@1643
|
818 |
[major release];
|
|
ingmarstein@1643
|
819 |
[minor release];
|
|
ingmarstein@1643
|
820 |
[incremental release];
|
|
ingmarstein@1643
|
821 |
[releaseType release];
|
|
ingmarstein@1643
|
822 |
[development release];
|
|
boredzo@1385
|
823 |
}
|
|
boredzo@1385
|
824 |
return versionInfo;
|
|
boredzo@1385
|
825 |
}
|
|
ingmarstein@1579
|
826 |
|
|
boredzo@1385
|
827 |
//this method could be moved to Growl.framework, I think.
|
|
boredzo@1385
|
828 |
//pass nil to get GrowlHelperApp's version as a string.
|
|
boredzo@1385
|
829 |
- (NSString *)stringWithVersionDictionary:(NSDictionary *)d {
|
|
boredzo@3075
|
830 |
if (!d)
|
|
boredzo@1385
|
831 |
d = [self versionDictionary];
|
|
boredzo@1385
|
832 |
|
|
boredzo@1385
|
833 |
//0.6
|
|
boredzo@1385
|
834 |
NSMutableString *result = [NSMutableString stringWithFormat:@"%@.%@",
|
|
boredzo@3064
|
835 |
[d objectForKey:@"Major version"],
|
|
boredzo@3064
|
836 |
[d objectForKey:@"Minor version"]];
|
|
boredzo@1385
|
837 |
|
|
boredzo@1385
|
838 |
//the .1 in 0.6.1
|
|
boredzo@3064
|
839 |
NSNumber *incremental = [d objectForKey:@"Incremental version"];
|
|
ingmarstein@2641
|
840 |
if ([incremental unsignedShortValue])
|
|
boredzo@3074
|
841 |
[result appendFormat:@".%@", incremental];
|
|
ingmarstein@2641
|
842 |
|
|
boredzo@3064
|
843 |
NSString *releaseTypeName = [d objectForKey:@"Release type name"];
|
|
boredzo@1385
|
844 |
if ([releaseTypeName length]) {
|
|
boredzo@1385
|
845 |
//"" (release), "b4", " SVN 900"
|
|
boredzo@3064
|
846 |
[result appendFormat:@"%@%@", releaseTypeName, [d objectForKey:@"Development version"]];
|
|
boredzo@1385
|
847 |
}
|
|
boredzo@1385
|
848 |
|
|
boredzo@1385
|
849 |
return result;
|
|
boredzo@1385
|
850 |
}
|
|
boredzo@1385
|
851 |
|
|
boredzo@3082
|
852 |
- (NSURL *) versionCheckURL {
|
|
ingmarstein@2556
|
853 |
if (!versionCheckURL)
|
|
boredzo@3989
|
854 |
versionCheckURL = [[NSURL URLWithString:@"http://growl.info/version.xml"] retain];
|
|
ingmarstein@2556
|
855 |
return versionCheckURL;
|
|
ingmarstein@2556
|
856 |
}
|
|
ingmarstein@2556
|
857 |
|
|
boredzo@4045
|
858 |
#pragma mark Accessors
|
|
boredzo@4045
|
859 |
|
|
boredzo@4045
|
860 |
- (BOOL) quitAfterOpen {
|
|
boredzo@4045
|
861 |
return quitAfterOpen;
|
|
boredzo@4045
|
862 |
}
|
|
boredzo@4045
|
863 |
- (void) setQuitAfterOpen:(BOOL)flag {
|
|
boredzo@4045
|
864 |
quitAfterOpen = flag;
|
|
boredzo@4045
|
865 |
}
|
|
boredzo@4045
|
866 |
|
|
boredzo@3069
|
867 |
#pragma mark What NSThread should implement as a class method
|
|
boredzo@3069
|
868 |
|
|
ofri@2697
|
869 |
- (NSThread *)mainThread {
|
|
ofri@2697
|
870 |
return mainThread;
|
|
ofri@2697
|
871 |
}
|
|
ofri@2697
|
872 |
|
|
boredzo@3069
|
873 |
#pragma mark Notifications (not the Growl kind)
|
|
boredzo@1429
|
874 |
|
|
ingmarstein@2588
|
875 |
- (void) preferencesChanged:(NSNotification *) note {
|
|
evands@4016
|
876 |
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
|
evands@4016
|
877 |
|
|
ingmarstein@1905
|
878 |
//[note object] is the changed key. A nil key means reload our tickets.
|
|
boredzo@1385
|
879 |
id object = [note object];
|
|
boredzo@4050
|
880 |
|
|
boredzo@4050
|
881 |
if (!quitAfterOpen) {
|
|
boredzo@4050
|
882 |
if (!note || (object && [object isEqual:GrowlStartServerKey])) {
|
|
boredzo@4050
|
883 |
Class pathwayControllerClass = NSClassFromString(@"GrowlPathwayController");
|
|
boredzo@4050
|
884 |
if (pathwayControllerClass)
|
|
boredzo@4050
|
885 |
[(id)[pathwayControllerClass sharedController] setServerEnabledFromPreferences];
|
|
boredzo@4050
|
886 |
}
|
|
boredzo@3112
|
887 |
}
|
|
boredzo@3073
|
888 |
if (!note || (object && [object isEqual:GrowlUserDefaultsKey]))
|
|
boredzo@2496
|
889 |
[[GrowlPreferencesController sharedController] synchronize];
|
|
boredzo@3073
|
890 |
if (!note || (object && [object isEqual:GrowlEnabledKey]))
|
|
boredzo@2496
|
891 |
growlIsEnabled = [[GrowlPreferencesController sharedController] boolForKey:GrowlEnabledKey];
|
|
boredzo@3073
|
892 |
if (!note || (object && [object isEqual:GrowlEnableForwardKey]))
|
|
ingmarstein@2565
|
893 |
enableForward = [[GrowlPreferencesController sharedController] isForwardingEnabled];
|
|
boredzo@3073
|
894 |
if (!note || (object && [object isEqual:GrowlForwardDestinationsKey])) {
|
|
ingmarstein@1656
|
895 |
[destinations release];
|
|
boredzo@2496
|
896 |
destinations = [[[GrowlPreferencesController sharedController] objectForKey:GrowlForwardDestinationsKey] retain];
|
|
boredzo@1385
|
897 |
}
|
|
ingmarstein@2452
|
898 |
if (!note || !object)
|
|
ingmarstein@2452
|
899 |
[ticketController loadAllSavedTickets];
|
|
boredzo@3073
|
900 |
if (!note || (object && [object isEqual:GrowlDisplayPluginKey]))
|
|
ingmarstein@2636
|
901 |
// force reload
|
|
boredzo@4023
|
902 |
[defaultDisplayPlugin release];
|
|
boredzo@4023
|
903 |
defaultDisplayPlugin = nil;
|
|
ingmarstein@1571
|
904 |
if (object) {
|
|
boredzo@3073
|
905 |
if ([object isEqual:@"GrowlTicketDeleted"]) {
|
|
ingmarstein@1571
|
906 |
NSString *ticketName = [[note userInfo] objectForKey:@"TicketName"];
|
|
ingmarstein@2452
|
907 |
[ticketController removeTicketForApplicationName:ticketName];
|
|
boredzo@3073
|
908 |
} else if ([object isEqual:@"GrowlTicketChanged"]) {
|
|
ingmarstein@1571
|
909 |
NSString *ticketName = [[note userInfo] objectForKey:@"TicketName"];
|
|
ingmarstein@1571
|
910 |
GrowlApplicationTicket *newTicket = [[GrowlApplicationTicket alloc] initTicketForApplication:ticketName];
|
|
ingmarstein@1571
|
911 |
if (newTicket) {
|
|
ingmarstein@2452
|
912 |
[ticketController addTicket:newTicket];
|
|
ingmarstein@1571
|
913 |
[newTicket release];
|
|
ingmarstein@1571
|
914 |
}
|
|
boredzo@4050
|
915 |
} else if ((!quitAfterOpen) && [object isEqual:GrowlUDPPortKey]) {
|
|
boredzo@3112
|
916 |
Class pathwayControllerClass = NSClassFromString(@"GrowlPathwayController");
|
|
boredzo@3112
|
917 |
if (pathwayControllerClass) {
|
|
boredzo@3112
|
918 |
id pathwayController = [pathwayControllerClass sharedController];
|
|
boredzo@3112
|
919 |
[pathwayController setServerEnabled:NO];
|
|
boredzo@3112
|
920 |
[pathwayController setServerEnabled:YES];
|
|
boredzo@3112
|
921 |
}
|
|
ingmarstein@1571
|
922 |
}
|
|
ingmarstein@1507
|
923 |
}
|
|
evands@4016
|
924 |
|
|
evands@4016
|
925 |
[pool release];
|
|
boredzo@1385
|
926 |
}
|
|
boredzo@1385
|
927 |
|
|
boredzo@1385
|
928 |
- (void) shutdown:(NSNotification *) note {
|
|
ingmarstein@1865
|
929 |
#pragma unused(note)
|
|
ingmarstein@1962
|
930 |
[NSApp terminate:nil];
|
|
boredzo@1385
|
931 |
}
|
|
boredzo@1385
|
932 |
|
|
boredzo@1385
|
933 |
- (void) replyToPing:(NSNotification *) note {
|
|
ingmarstein@1865
|
934 |
#pragma unused(note)
|
|
evands@4016
|
935 |
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
|
evands@4016
|
936 |
|
|
boredzo@3062
|
937 |
[[NSDistributedNotificationCenter defaultCenter] postNotificationName:GROWL_PONG
|
|
boredzo@3062
|
938 |
object:nil
|
|
boredzo@3062
|
939 |
userInfo:nil
|
|
boredzo@3062
|
940 |
deliverImmediately:NO];
|
|
evands@4016
|
941 |
|
|
evands@4016
|
942 |
[pool release];
|
|
boredzo@1385
|
943 |
}
|
|
boredzo@1385
|
944 |
|
|
boredzo@1385
|
945 |
#pragma mark NSApplication Delegate Methods
|
|
boredzo@1385
|
946 |
|
|
ingmarstein@1905
|
947 |
- (BOOL) application:(NSApplication *)theApplication openFile:(NSString *)filename {
|
|
ingmarstein@1865
|
948 |
#pragma unused(theApplication)
|
|
boredzo@2469
|
949 |
BOOL retVal = NO;
|
|
boredzo@1385
|
950 |
NSString *pathExtension = [filename pathExtension];
|
|
boredzo@1479
|
951 |
|
|
boredzo@2469
|
952 |
if ([pathExtension isEqualToString:GROWL_REG_DICT_EXTENSION]) {
|
|
boredzo@4047
|
953 |
//If the auto-quit flag is set, it's probably because we are not the real GHAÑwe're some other GHA that a broken (pre-1.1.3) GAB opened this file with. If that's the case, find the real one and open the file with it.
|
|
boredzo@4047
|
954 |
BOOL registerItOurselves = YES;
|
|
boredzo@4047
|
955 |
NSString *realHelperAppBundlePath = nil;
|
|
boredzo@4047
|
956 |
|
|
boredzo@4047
|
957 |
if (quitAfterOpen) {
|
|
boredzo@4047
|
958 |
//But, just to make sure we don't infinitely loop, make sure this isn't our own bundle.
|
|
boredzo@4047
|
959 |
NSString *ourBundlePath = [[NSBundle mainBundle] bundlePath];
|
|
boredzo@4047
|
960 |
realHelperAppBundlePath = [[GrowlPathUtilities runningHelperAppBundle] bundlePath];
|
|
boredzo@4047
|
961 |
if (![ourBundlePath isEqualToString:realHelperAppBundlePath])
|
|
boredzo@4047
|
962 |
registerItOurselves = NO;
|
|
boredzo@4047
|
963 |
}
|
|
boredzo@4047
|
964 |
|
|
boredzo@4047
|
965 |
if (registerItOurselves) {
|
|
boredzo@4047
|
966 |
//We are the real GHA.
|
|
boredzo@4047
|
967 |
//Have the property-list-file pathway process this registration dictionary file.
|
|
boredzo@4047
|
968 |
GrowlPropertyListFilePathway *pathway = [GrowlPropertyListFilePathway standardPathway];
|
|
boredzo@4047
|
969 |
[pathway application:theApplication openFile:filename];
|
|
boredzo@4047
|
970 |
} else {
|
|
boredzo@4047
|
971 |
//We're definitely not the real GHA, so pass it to the real GHA to be registered.
|
|
boredzo@4047
|
972 |
[[NSWorkspace sharedWorkspace] openFile:filename
|
|
boredzo@4047
|
973 |
withApplication:realHelperAppBundlePath];
|
|
boredzo@4047
|
974 |
}
|
|
ingmarstein@1962
|
975 |
} else {
|
|
boredzo@2469
|
976 |
GrowlPluginController *controller = [GrowlPluginController sharedController];
|
|
boredzo@2469
|
977 |
//the set returned by GrowlPluginController is case-insensitive. yay!
|
|
eridius@2773
|
978 |
if ([[controller registeredPluginTypes] containsObject:pathExtension]) {
|
|
eridius@2773
|
979 |
[controller installPluginFromPath:filename];
|
|
boredzo@2469
|
980 |
|
|
boredzo@2469
|
981 |
retVal = YES;
|
|
boredzo@2469
|
982 |
}
|
|
boredzo@2469
|
983 |
}
|
|
boredzo@2469
|
984 |
|
|
boredzo@2469
|
985 |
/*If Growl is not enabled and was not already running before
|
|
ingmarstein@1962
|
986 |
* (for example, via an autolaunch even though the user's last
|
|
ingmarstein@1962
|
987 |
* preference setting was to click "Stop Growl," setting enabled to NO),
|
|
ingmarstein@1962
|
988 |
* quit having registered; otherwise, remain running.
|
|
ingmarstein@1962
|
989 |
*/
|
|
boredzo@4045
|
990 |
if (!growlIsEnabled && !growlFinishedLaunching) {
|
|
boredzo@4045
|
991 |
//Terminate after one second to give us time to process any other openFile: messages.
|
|
boredzo@4045
|
992 |
[NSObject cancelPreviousPerformRequestsWithTarget:NSApp
|
|
boredzo@4045
|
993 |
selector:@selector(terminate:)
|
|
boredzo@4045
|
994 |
object:nil];
|
|
boredzo@4045
|
995 |
[NSApp performSelector:@selector(terminate:)
|
|
boredzo@4045
|
996 |
withObject:nil
|
|
boredzo@4045
|
997 |
afterDelay:1.0];
|
|
boredzo@4045
|
998 |
}
|
|
ingmarstein@1790
|
999 |
|
|
boredzo@1385
|
1000 |
return retVal;
|
|
boredzo@1385
|
1001 |
}
|
|
boredzo@1385
|
1002 |
|
|
boredzo@1385
|
1003 |
- (void) applicationWillFinishLaunching:(NSNotification *)aNotification {
|
|
ingmarstein@1865
|
1004 |
#pragma unused(aNotification)
|
|
ofri@2697
|
1005 |
mainThread = [[NSThread currentThread] retain];
|
|
ingmarstein@2721
|
1006 |
|
|
boredzo@1385
|
1007 |
BOOL printVersionAndExit = [[NSUserDefaults standardUserDefaults] boolForKey:@"PrintVersionAndExit"];
|
|
ingmarstein@1448
|
1008 |
if (printVersionAndExit) {
|
|
boredzo@1385
|
1009 |
printf("This is GrowlHelperApp version %s.\n"
|
|
boredzo@3103
|
1010 |
"PrintVersionAndExit was set to %hhi, so GrowlHelperApp will now exit.\n",
|
|
boredzo@1385
|
1011 |
[[self stringWithVersionDictionary:nil] UTF8String],
|
|
boredzo@1385
|
1012 |
printVersionAndExit);
|
|
boredzo@1385
|
1013 |
[NSApp terminate:nil];
|
|
boredzo@1385
|
1014 |
}
|
|
boredzo@1385
|
1015 |
|
|
boredzo@1385
|
1016 |
NSFileManager *fs = [NSFileManager defaultManager];
|
|
boredzo@1385
|
1017 |
|
|
boredzo@1385
|
1018 |
NSString *destDir, *subDir;
|
|
boredzo@1385
|
1019 |
NSArray *searchPath = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, /*expandTilde*/ YES);
|
|
boredzo@1385
|
1020 |
|
|
boredzo@1385
|
1021 |
destDir = [searchPath objectAtIndex:0U]; //first == last == ~/Library
|
|
boredzo@1385
|
1022 |
[fs createDirectoryAtPath:destDir attributes:nil];
|
|
boredzo@1385
|
1023 |
destDir = [destDir stringByAppendingPathComponent:@"Application Support"];
|
|
boredzo@1385
|
1024 |
[fs createDirectoryAtPath:destDir attributes:nil];
|
|
boredzo@1385
|
1025 |
destDir = [destDir stringByAppendingPathComponent:@"Growl"];
|
|
boredzo@1385
|
1026 |
[fs createDirectoryAtPath:destDir attributes:nil];
|
|
boredzo@1385
|
1027 |
|
|
boredzo@1385
|
1028 |
subDir = [destDir stringByAppendingPathComponent:@"Tickets"];
|
|
boredzo@1385
|
1029 |
[fs createDirectoryAtPath:subDir attributes:nil];
|
|
boredzo@1385
|
1030 |
subDir = [destDir stringByAppendingPathComponent:@"Plugins"];
|
|
boredzo@1385
|
1031 |
[fs createDirectoryAtPath:subDir attributes:nil];
|
|
boredzo@1385
|
1032 |
}
|
|
boredzo@1385
|
1033 |
|
|
boredzo@1385
|
1034 |
//Post a notification when we are done launching so the application bridge can inform participating applications
|
|
boredzo@1385
|
1035 |
- (void) applicationDidFinishLaunching:(NSNotification *)aNotification {
|
|
ingmarstein@1865
|
1036 |
#pragma unused(aNotification)
|
|
boredzo@3062
|
1037 |
[[NSDistributedNotificationCenter defaultCenter] postNotificationName:GROWL_IS_READY
|
|
boredzo@3062
|
1038 |
object:nil
|
|
boredzo@3062
|
1039 |
userInfo:nil
|
|
boredzo@3062
|
1040 |
deliverImmediately:YES];
|
|
ingmarstein@1962
|
1041 |
growlFinishedLaunching = YES;
|
|
boredzo@4045
|
1042 |
|
|
boredzo@4045
|
1043 |
if (quitAfterOpen) {
|
|
boredzo@4045
|
1044 |
//We provide a delay of 1 second to give NSApp time to send us application:openFile: messages for any .growlRegDict files the GrowlPropertyListFilePathway needs to process.
|
|
boredzo@4045
|
1045 |
[NSApp performSelector:@selector(terminate:)
|
|
boredzo@4045
|
1046 |
withObject:nil
|
|
boredzo@4045
|
1047 |
afterDelay:1.0];
|
|
boredzo@4045
|
1048 |
} else {
|
|
boredzo@4045
|
1049 |
/*If Growl is not enabled and was not already running before
|
|
boredzo@4045
|
1050 |
* (for example, via an autolaunch even though the user's last
|
|
boredzo@4045
|
1051 |
* preference setting was to click "Stop Growl," setting enabled to NO),
|
|
boredzo@4045
|
1052 |
* quit having registered; otherwise, remain running.
|
|
boredzo@4045
|
1053 |
*/
|
|
boredzo@4045
|
1054 |
if (!growlIsEnabled)
|
|
boredzo@4045
|
1055 |
[NSApp terminate:nil];
|
|
boredzo@4045
|
1056 |
}
|
|
boredzo@1385
|
1057 |
}
|
|
boredzo@1385
|
1058 |
|
|
boredzo@1385
|
1059 |
//Same as applicationDidFinishLaunching, called when we are asked to reopen (that is, we are already running)
|
|
boredzo@1385
|
1060 |
- (BOOL) applicationShouldHandleReopen:(NSApplication *)theApplication hasVisibleWindows:(BOOL)flag {
|
|
ingmarstein@1865
|
1061 |
#pragma unused(theApplication, flag)
|
|
ingmarstein@1962
|
1062 |
return NO;
|
|
boredzo@1385
|
1063 |
}
|
|
boredzo@1385
|
1064 |
|
|
ingmarstein@1579
|
1065 |
- (BOOL) applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication {
|
|
ingmarstein@1865
|
1066 |
#pragma unused(theApplication)
|
|
boredzo@1385
|
1067 |
return NO;
|
|
boredzo@1385
|
1068 |
}
|
|
boredzo@1385
|
1069 |
|
|
ingmarstein@1744
|
1070 |
- (void) applicationWillTerminate:(NSNotification *)notification {
|
|
ingmarstein@1865
|
1071 |
#pragma unused(notification)
|
|
ingmarstein@2644
|
1072 |
[GrowlAbstractSingletonObject destroyAllSingletons]; //Release all our controllers
|
|
boredzo@1385
|
1073 |
}
|
|
boredzo@1385
|
1074 |
|
|
boredzo@1415
|
1075 |
#pragma mark Auto-discovery
|
|
boredzo@1415
|
1076 |
|
|
boredzo@1415
|
1077 |
//called by NSWorkspace when an application launches.
|
|
ingmarstein@1744
|
1078 |
- (void) applicationLaunched:(NSNotification *)notification {
|
|
boredzo@1415
|
1079 |
NSDictionary *userInfo = [notification userInfo];
|
|
boredzo@1415
|
1080 |
|
|
ingmarstein@2641
|
1081 |
if (!userInfo)
|
|
ingmarstein@2641
|
1082 |
return;
|
|
ingmarstein@2641
|
1083 |
|
|
evands@3538
|
1084 |
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
|
boredzo@3064
|
1085 |
NSString *appPath = [userInfo objectForKey:@"NSApplicationPath"];
|
|
boredzo@1415
|
1086 |
|
|
ingmarstein@1448
|
1087 |
if (appPath) {
|
|
boredzo@1415
|
1088 |
NSString *ticketPath = [NSBundle pathForResource:@"Growl Registration Ticket" ofType:GROWL_REG_DICT_EXTENSION inDirectory:appPath];
|
|
ingmarstein@2652
|
1089 |
if (ticketPath) {
|
|
ingmarstein@2652
|
1090 |
CFURLRef ticketURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, (CFStringRef)ticketPath, kCFURLPOSIXPathStyle, false);
|
|
ingmarstein@2652
|
1091 |
NSMutableDictionary *ticket = (NSMutableDictionary *)createPropertyListFromURL((NSURL *)ticketURL, kCFPropertyListMutableContainers, NULL, NULL);
|
|
ingmarstein@2652
|
1092 |
|
|
ingmarstein@2652
|
1093 |
if (ticket) {
|
|
boredzo@3064
|
1094 |
NSString *appName = [userInfo objectForKey:@"NSApplicationName"];
|
|
ingmarstein@2652
|
1095 |
|
|
ingmarstein@2652
|
1096 |
//set the app's name in the dictionary, if it's not present already.
|
|
ingmarstein@2652
|
1097 |
if (![ticket objectForKey:GROWL_APP_NAME])
|
|
ingmarstein@2652
|
1098 |
[ticket setObject:appName forKey:GROWL_APP_NAME];
|
|
ingmarstein@2652
|
1099 |
|
|
ingmarstein@2652
|
1100 |
if ([GrowlApplicationTicket isValidTicketDictionary:ticket]) {
|
|
ingmarstein@2652
|
1101 |
NSLog(@"Auto-discovered registration ticket in %@ (located at %@)", appName, appPath);
|
|
ingmarstein@2652
|
1102 |
|
|
ingmarstein@2652
|
1103 |
/* set the app's location in the dictionary, avoiding costly
|
|
ingmarstein@2652
|
1104 |
* lookups later.
|
|
ingmarstein@2652
|
1105 |
*/
|
|
ingmarstein@2080
|
1106 |
NSURL *url = [[NSURL alloc] initFileURLWithPath:appPath];
|
|
ingmarstein@2639
|
1107 |
NSDictionary *file_data = createDockDescriptionWithURL(url);
|
|
boredzo@1479
|
1108 |
id location = file_data ? [NSDictionary dictionaryWithObject:file_data forKey:@"file-data"] : appPath;
|
|
ingmarstein@2639
|
1109 |
[file_data release];
|
|
ingmarstein@2652
|
1110 |
[ticket setObject:location forKey:GROWL_APP_LOCATION];
|
|
ingmarstein@2080
|
1111 |
[url release];
|
|
boredzo@1479
|
1112 |
|
|
boredzo@1479
|
1113 |
//write the new ticket to disk, and be sure to launch this ticket instead of the one in the app bundle.
|
|
ingmarstein@3035
|
1114 |
CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault);
|
|
ingmarstein@3035
|
1115 |
CFStringRef uuidString = CFUUIDCreateString(kCFAllocatorDefault, uuid);
|
|
ingmarstein@3035
|
1116 |
CFRelease(uuid);
|
|
ingmarstein@3035
|
1117 |
ticketPath = [[NSTemporaryDirectory() stringByAppendingPathComponent:(NSString *)uuidString] stringByAppendingPathExtension:GROWL_REG_DICT_EXTENSION];
|
|
ingmarstein@3035
|
1118 |
CFRelease(uuidString);
|
|
ingmarstein@1680
|
1119 |
[ticket writeToFile:ticketPath atomically:NO];
|
|
ingmarstein@2652
|
1120 |
|
|
ingmarstein@2652
|
1121 |
/* open the ticket with ourselves.
|
|
ingmarstein@2652
|
1122 |
* we need to use LS in order to launch it with this specific
|
|
ingmarstein@2652
|
1123 |
* GHA, rather than some other.
|
|
ingmarstein@2652
|
1124 |
*/
|
|
ingmarstein@2652
|
1125 |
CFURLRef myURL = (CFURLRef)copyCurrentProcessURL();
|
|
boredzo@3677
|
1126 |
NSArray *URLsToOpen = [NSArray arrayWithObject:[NSURL fileURLWithPath:ticketPath]];
|
|
ingmarstein@2652
|
1127 |
struct LSLaunchURLSpec spec = {
|
|
ingmarstein@2652
|
1128 |
.appURL = myURL,
|
|
ingmarstein@2652
|
1129 |
.itemURLs = (CFArrayRef)URLsToOpen,
|
|
ingmarstein@2652
|
1130 |
.passThruParams = NULL,
|
|
ingmarstein@2652
|
1131 |
.launchFlags = kLSLaunchDontAddToRecents | kLSLaunchDontSwitch | kLSLaunchAsync,
|
|
ingmarstein@2652
|
1132 |
.asyncRefCon = NULL,
|
|
ingmarstein@2652
|
1133 |
};
|
|
ingmarstein@2652
|
1134 |
OSStatus err = LSOpenFromURLSpec(&spec, /*outLaunchedURL*/ NULL);
|
|
ingmarstein@2652
|
1135 |
if (err != noErr)
|
|
ingmarstein@2652
|
1136 |
NSLog(@"The registration ticket for %@ could not be opened (LSOpenFromURLSpec returned %li). Pathname for the ticket file: %@", appName, (long)err, ticketPath);
|
|
ingmarstein@2652
|
1137 |
CFRelease(myURL);
|
|
ingmarstein@2652
|
1138 |
} else if ([GrowlApplicationTicket isKnownTicketVersion:ticket]) {
|
|
ingmarstein@2652
|
1139 |
NSLog(@"%@ (located at %@) contains an invalid registration ticket - developer, please consult Growl developer documentation (http://growl.info/documentation/developer/)", appName, appPath);
|
|
ingmarstein@2652
|
1140 |
} else {
|
|
boredzo@3662
|
1141 |
NSNumber *versionNum = [ticket objectForKey:GROWL_TICKET_VERSION];
|
|
boredzo@3662
|
1142 |
if (versionNum)
|
|
boredzo@3662
|
1143 |
NSLog(@"%@ (located at %@) contains a ticket whose format version (%i) is unrecognised by this version (%@) of Growl", appName, appPath, [versionNum intValue], [self stringWithVersionDictionary:nil]);
|
|
boredzo@3662
|
1144 |
else
|
|
boredzo@3662
|
1145 |
NSLog(@"%@ (located at %@) contains a ticket with no format version number; Growl requires that a registration dictionary include a format version number, so that Growl knows whether it will understand the dictionary's format. This ticket will be ignored.", appName, appPath);
|
|
boredzo@1479
|
1146 |
}
|
|
ingmarstein@2652
|
1147 |
[ticket release];
|
|
boredzo@1415
|
1148 |
}
|
|
ingmarstein@2652
|
1149 |
CFRelease(ticketURL);
|
|
boredzo@1415
|
1150 |
}
|
|
boredzo@1415
|
1151 |
}
|
|
evands@3538
|
1152 |
|
|
evands@3538
|
1153 |
[pool release];
|
|
boredzo@1415
|
1154 |
}
|
|
boredzo@1415
|
1155 |
|
|
evands@3703
|
1156 |
#pragma mark Growl Application Bridge delegate
|
|
evands@3703
|
1157 |
/*!
|
|
evands@3703
|
1158 |
* @brief Returns the application name Growl will use
|
|
evands@3703
|
1159 |
*/
|
|
evands@3703
|
1160 |
- (NSString *)applicationNameForGrowl
|
|
evands@3703
|
1161 |
{
|
|
evands@3703
|
1162 |
return @"Growl";
|
|
evands@3703
|
1163 |
}
|
|
evands@3703
|
1164 |
|
|
evands@3703
|
1165 |
- (NSDictionary *)registrationDictionaryForGrowl
|
|
evands@3703
|
1166 |
{
|
|
evands@3703
|
1167 |
NSDictionary *descriptions = [NSDictionary dictionaryWithObjectsAndKeys:
|
|
evands@3703
|
1168 |
NSLocalizedString(@"A Growl update is available", nil), UPDATE_AVAILABLE_NOTIFICATION,
|
|
evands@3703
|
1169 |
NSLocalizedString(@"You are now considered idle by Growl", nil), USER_WENT_IDLE_NOTIFICATION,
|
|
evands@3703
|
1170 |
NSLocalizedString(@"You are no longer considered idle by Growl", nil), USER_RETURNED_NOTIFICATION,
|
|
evands@3703
|
1171 |
nil];
|
|
evands@3703
|
1172 |
|
|
evands@3703
|
1173 |
NSDictionary *humanReadableNames = [NSDictionary dictionaryWithObjectsAndKeys:
|
|
evands@3703
|
1174 |
NSLocalizedString(@"Growl update available", nil), UPDATE_AVAILABLE_NOTIFICATION,
|
|
evands@3703
|
1175 |
NSLocalizedString(@"User went idle", nil), USER_WENT_IDLE_NOTIFICATION,
|
|
evands@3703
|
1176 |
NSLocalizedString(@"User returned", nil), USER_RETURNED_NOTIFICATION,
|
|
evands@3703
|
1177 |
nil];
|
|
evands@3703
|
1178 |
|
|
evands@3703
|
1179 |
NSDictionary *growlReg = [NSDictionary dictionaryWithObjectsAndKeys:
|
|
evands@3703
|
1180 |
[NSArray arrayWithObjects:UPDATE_AVAILABLE_NOTIFICATION, USER_WENT_IDLE_NOTIFICATION, USER_RETURNED_NOTIFICATION, nil], GROWL_NOTIFICATIONS_ALL,
|
|
evands@3703
|
1181 |
[NSArray arrayWithObject:UPDATE_AVAILABLE_NOTIFICATION], GROWL_NOTIFICATIONS_DEFAULT,
|
|
evands@3703
|
1182 |
humanReadableNames, GROWL_NOTIFICATIONS_HUMAN_READABLE_NAMES,
|
|
evands@3703
|
1183 |
descriptions, GROWL_NOTIFICATIONS_DESCRIPTIONS,
|
|
evands@3703
|
1184 |
nil];
|
|
evands@3703
|
1185 |
|
|
evands@3703
|
1186 |
return growlReg;
|
|
evands@3703
|
1187 |
}
|
|
evands@3703
|
1188 |
|
|
evands@3703
|
1189 |
- (NSImage *)applicationIconDataForGrowl
|
|
evands@3703
|
1190 |
{
|
|
evands@3703
|
1191 |
return [NSImage imageNamed:@"growl-icon"];
|
|
evands@3703
|
1192 |
}
|
|
evands@3703
|
1193 |
|
|
evands@3703
|
1194 |
- (void)growlNotificationWasClicked:(id)clickContext
|
|
evands@3703
|
1195 |
{
|
|
evands@3703
|
1196 |
if (clickContext && [clickContext isKindOfClass:[NSString class]]) {
|
|
evands@3703
|
1197 |
NSURL *downloadURL = [NSURL URLWithString:clickContext];
|
|
evands@3703
|
1198 |
[[NSWorkspace sharedWorkspace] openURL:downloadURL];
|
|
evands@3703
|
1199 |
}
|
|
evands@3703
|
1200 |
}
|
|
evands@3703
|
1201 |
|
|
boredzo@1385
|
1202 |
@end
|
|
boredzo@1385
|
1203 |
|
|
boredzo@1385
|
1204 |
#pragma mark -
|
|
boredzo@1385
|
1205 |
|
|
bgannin@3450
|
1206 |
@implementation GrowlApplicationController (PRIVATE)
|
|
boredzo@1385
|
1207 |
|
|
boredzo@3071
|
1208 |
#pragma mark Click feedback from displays
|
|
boredzo@3071
|
1209 |
|
|
ingmarstein@3169
|
1210 |
/*click feedback comes here first. GAB picks up the DN and calls our
|
|
ingmarstein@3169
|
1211 |
* -growlNotificationWasClicked:/-growlNotificationTimedOut: with it if it's a
|
|
boredzo@3071
|
1212 |
* GHA notification.
|
|
boredzo@3071
|
1213 |
*/
|
|
boredzo@3069
|
1214 |
|
|
boredzo@1385
|
1215 |
- (void) notificationClicked:(NSNotification *)notification {
|
|
boredzo@1385
|
1216 |
NSString *appName, *growlNotificationClickedName;
|
|
ingmarstein@1997
|
1217 |
NSString *suffix;
|
|
ingmarstein@1958
|
1218 |
NSDictionary *clickInfo;
|
|
boredzo@1385
|
1219 |
NSDictionary *userInfo;
|
|
boredzo@1385
|
1220 |
|
|
ingmarstein@1958
|
1221 |
userInfo = [notification userInfo];
|
|
ingmarstein@1958
|
1222 |
|
|
boredzo@1385
|
1223 |
//Build the application-specific notification name
|
|
boredzo@1385
|
1224 |
appName = [notification object];
|
|
ingmarstein@2641
|
1225 |
if (getBooleanForKey(userInfo, @"ClickHandlerEnabled")) {
|
|
ingmarstein@1997
|
1226 |
suffix = GROWL_NOTIFICATION_CLICKED;
|
|
ingmarstein@1958
|
1227 |
} else {
|
|
ingmarstein@1958
|
1228 |
/*
|
|
ingmarstein@1958
|
1229 |
* send GROWL_NOTIFICATION_TIMED_OUT instead, so that an application is
|
|
ingmarstein@1958
|
1230 |
* guaranteed to receive feedback for every notification.
|
|
ingmarstein@1958
|
1231 |
*/
|
|
ingmarstein@1997
|
1232 |
suffix = GROWL_NOTIFICATION_TIMED_OUT;
|
|
ingmarstein@1997
|
1233 |
}
|
|
boredzo@3064
|
1234 |
NSNumber *pid = [userInfo objectForKey:GROWL_APP_PID];
|
|
ingmarstein@2672
|
1235 |
if (pid)
|
|
ingmarstein@1997
|
1236 |
growlNotificationClickedName = [[NSString alloc] initWithFormat:@"%@-%@-%@",
|
|
ingmarstein@1997
|
1237 |
appName, pid, suffix];
|
|
ingmarstein@2672
|
1238 |
else
|
|
ingmarstein@1997
|
1239 |
growlNotificationClickedName = [[NSString alloc] initWithFormat:@"%@%@",
|
|
ingmarstein@1997
|
1240 |
appName, suffix];
|
|
ingmarstein@1997
|
1241 |
clickInfo = [[NSDictionary alloc] initWithObjectsAndKeys:
|
|
boredzo@3064
|
1242 |
[userInfo objectForKey:GROWL_KEY_CLICKED_CONTEXT], GROWL_KEY_CLICKED_CONTEXT,
|
|
ingmarstein@1997
|
1243 |
nil];
|
|
ingmarstein@1997
|
1244 |
|
|
boredzo@3062
|
1245 |
[[NSDistributedNotificationCenter defaultCenter] postNotificationName:growlNotificationClickedName
|
|
boredzo@3062
|
1246 |
object:nil
|
|
boredzo@3062
|
1247 |
userInfo:clickInfo
|
|
boredzo@3062
|
1248 |
deliverImmediately:YES];
|
|
ingmarstein@1637
|
1249 |
|
|
ingmarstein@1958
|
1250 |
[clickInfo release];
|
|
ingmarstein@1997
|
1251 |
[growlNotificationClickedName release];
|
|
boredzo@1385
|
1252 |
}
|
|
boredzo@1385
|
1253 |
|
|
ingmarstein@1893
|
1254 |
- (void) notificationTimedOut:(NSNotification *)notification {
|
|
ingmarstein@1893
|
1255 |
NSString *appName, *growlNotificationTimedOutName;
|
|
ingmarstein@1997
|
1256 |
NSDictionary *clickInfo;
|
|
ingmarstein@1893
|
1257 |
NSDictionary *userInfo;
|
|
ingmarstein@1893
|
1258 |
|
|
ingmarstein@1997
|
1259 |
userInfo = [notification userInfo];
|
|
ingmarstein@1997
|
1260 |
|
|
ingmarstein@1893
|
1261 |
//Build the application-specific notification name
|
|
ingmarstein@1893
|
1262 |
appName = [notification object];
|
|
ingmarstein@1997
|
1263 |
NSNumber *pid = [userInfo objectForKey:GROWL_APP_PID];
|
|
ingmarstein@2672
|
1264 |
if (pid)
|
|
ingmarstein@1997
|
1265 |
growlNotificationTimedOutName = [[NSString alloc] initWithFormat:@"%@-%@-%@",
|
|
ingmarstein@1997
|
1266 |
appName, pid, GROWL_NOTIFICATION_TIMED_OUT];
|
|
ingmarstein@2672
|
1267 |
else
|
|
ingmarstein@1997
|
1268 |
growlNotificationTimedOutName = [[NSString alloc] initWithFormat:@"%@%@",
|
|
ingmarstein@1997
|
1269 |
appName, GROWL_NOTIFICATION_TIMED_OUT];
|
|
ingmarstein@1997
|
1270 |
clickInfo = [[NSDictionary alloc] initWithObjectsAndKeys:
|
|
ingmarstein@1997
|
1271 |
[userInfo objectForKey:GROWL_KEY_CLICKED_CONTEXT], GROWL_KEY_CLICKED_CONTEXT,
|
|
ingmarstein@1997
|
1272 |
nil];
|
|
ingmarstein@1893
|
1273 |
|
|
boredzo@3062
|
1274 |
[[NSDistributedNotificationCenter defaultCenter] postNotificationName:growlNotificationTimedOutName
|
|
boredzo@3062
|
1275 |
object:nil
|
|
boredzo@3062
|
1276 |
userInfo:clickInfo
|
|
boredzo@3062
|
1277 |
deliverImmediately:YES];
|
|
ingmarstein@2721
|
1278 |
|
|
ingmarstein@1997
|
1279 |
[clickInfo release];
|
|
ingmarstein@1997
|
1280 |
[growlNotificationTimedOutName release];
|
|
ingmarstein@1893
|
1281 |
}
|
|
ingmarstein@1893
|
1282 |
|
|
boredzo@1385
|
1283 |
@end
|
|
boredzo@4051
|
1284 |
|
|
boredzo@4051
|
1285 |
static OSStatus soundCompletionCallbackProc(SystemSoundActionID actionID, void *refcon) {
|
|
boredzo@4051
|
1286 |
#pragma unused(refcon)
|
|
boredzo@4051
|
1287 |
|
|
boredzo@4051
|
1288 |
SystemSoundRemoveCompletionRoutine(actionID);
|
|
boredzo@4051
|
1289 |
|
|
boredzo@4051
|
1290 |
return SystemSoundRemoveActionID(actionID);
|
|
boredzo@4051
|
1291 |
}
|