Bug 421551: Update Growl framework to 1.1.4. rs=sdwilsh

This commit is contained in:
Dave Townsend 2008-08-29 18:05:02 +01:00
parent babe951bb6
commit e62d0391bf
22 changed files with 1678 additions and 669 deletions

View File

@ -3,8 +3,9 @@
// Growl
//
// Created by Mac-arena the Bored Zo on Wed Jun 18 2004.
// Copyright 2005 The Growl Project.
// Copyright 2005-2006 The Growl Project.
//
// This file is under the BSD License, refer to License.txt for details
#include <Carbon/Carbon.h>
#include <unistd.h>
@ -13,14 +14,102 @@
#include <netdb.h>
#include "CFGrowlAdditions.h"
static CFStringRef _CFURLAliasDataKey = CFSTR("_CFURLAliasData");
static CFStringRef _CFURLStringKey = CFSTR("_CFURLString");
static CFStringRef _CFURLStringTypeKey = CFSTR("_CFURLStringType");
#ifndef MIN
# define MIN(a,b) ((a) < (b) ? (a) : (b))
#endif
//see GrowlApplicationBridge-Carbon.c for rationale of using NSLog.
extern void NSLog(CFStringRef format, ...);
extern Boolean CFStringGetFileSystemRepresentation() __attribute__((weak_import));
extern CFIndex CFStringGetMaximumSizeOfFileSystemRepresentation(CFStringRef string) __attribute__((weak_import));
CFStringRef copyCurrentProcessName(void) {
char *createFileSystemRepresentationOfString(CFStringRef str) {
char *buffer;
#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_4
/* CFStringGetFileSystemRepresentation will cause a link error despite the weak_import attribute above on 10.5 when compiling with 10.2 compatibility using gcc 3.3.
* PPC will therefore always use the 10.3 and below method of creating a file system representation.
*/
if (CFStringGetFileSystemRepresentation) {
CFIndex size = CFStringGetMaximumSizeOfFileSystemRepresentation(str);
buffer = malloc(size);
CFStringGetFileSystemRepresentation(str, buffer, size);
} else
#endif
{
buffer = malloc(512);
CFURLRef url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, str, kCFURLPOSIXPathStyle, false);
if (!CFURLGetFileSystemRepresentation(url, false, (UInt8 *)buffer, 512)) {
free(buffer);
buffer = NULL;
}
CFRelease(url);
}
return buffer;
}
STRING_TYPE createStringWithDate(CFDateRef date) {
CFLocaleRef locale = CFLocaleCopyCurrent();
CFDateFormatterRef dateFormatter = CFDateFormatterCreate(kCFAllocatorDefault,
locale,
kCFDateFormatterMediumStyle,
kCFDateFormatterMediumStyle);
CFRelease(locale);
CFStringRef dateString = CFDateFormatterCreateStringWithDate(kCFAllocatorDefault,
dateFormatter,
date);
CFRelease(dateFormatter);
return dateString;
}
STRING_TYPE createStringWithContentsOfFile(CFStringRef filename, CFStringEncoding encoding) {
CFStringRef str = NULL;
char *path = createFileSystemRepresentationOfString(filename);
if (path) {
FILE *fp = fopen(path, "rb");
if (fp) {
fseek(fp, 0, SEEK_END);
unsigned long size = ftell(fp);
fseek(fp, 0, SEEK_SET);
unsigned char *buffer = malloc(size);
if (buffer && fread(buffer, 1, size, fp) == size)
str = CFStringCreateWithBytes(kCFAllocatorDefault, buffer, size, encoding, true);
fclose(fp);
}
free(path);
}
return str;
}
STRING_TYPE createStringWithStringAndCharacterAndString(STRING_TYPE str0, UniChar ch, STRING_TYPE str1) {
CFStringRef cfstr0 = (CFStringRef)str0;
CFStringRef cfstr1 = (CFStringRef)str1;
CFIndex len0 = (cfstr0 ? CFStringGetLength(cfstr0) : 0);
CFIndex len1 = (cfstr1 ? CFStringGetLength(cfstr1) : 0);
unsigned length = (len0 + (ch != 0xffff) + len1);
UniChar *buf = malloc(sizeof(UniChar) * length);
unsigned i = 0U;
if (cfstr0) {
CFStringGetCharacters(cfstr0, CFRangeMake(0, len0), buf);
i += len0;
}
if (ch != 0xffff)
buf[i++] = ch;
if (cfstr1)
CFStringGetCharacters(cfstr1, CFRangeMake(0, len1), &buf[i]);
return CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, buf, length, /*contentsDeallocator*/ kCFAllocatorMalloc);
}
char *copyCString(STRING_TYPE str, CFStringEncoding encoding) {
CFIndex size = CFStringGetMaximumSizeForEncoding(CFStringGetLength(str), encoding) + 1;
char *buffer = calloc(size, 1);
CFStringGetCString(str, buffer, size, encoding);
return buffer;
}
STRING_TYPE copyCurrentProcessName(void) {
ProcessSerialNumber PSN = { 0, kCurrentProcess };
CFStringRef name = NULL;
OSStatus err = CopyProcessName(&PSN, &name);
@ -31,7 +120,7 @@ CFStringRef copyCurrentProcessName(void) {
return name;
}
CFURLRef copyCurrentProcessURL(void) {
URL_TYPE copyCurrentProcessURL(void) {
ProcessSerialNumber psn = { 0, kCurrentProcess };
FSRef fsref;
CFURLRef URL = NULL;
@ -43,14 +132,14 @@ CFURLRef copyCurrentProcessURL(void) {
}
return URL;
}
CFStringRef copyCurrentProcessPath(void) {
STRING_TYPE copyCurrentProcessPath(void) {
CFURLRef URL = copyCurrentProcessURL();
CFStringRef path = CFURLCopyFileSystemPath(URL, kCFURLPOSIXPathStyle);
CFRelease(URL);
return path;
}
CFURLRef copyTemporaryFolderURL(void) {
URL_TYPE copyTemporaryFolderURL(void) {
FSRef ref;
CFURLRef url = NULL;
@ -62,7 +151,7 @@ CFURLRef copyTemporaryFolderURL(void) {
return url;
}
CFStringRef copyTemporaryFolderPath(void) {
STRING_TYPE copyTemporaryFolderPath(void) {
CFStringRef path = NULL;
CFURLRef url = copyTemporaryFolderURL();
@ -74,74 +163,37 @@ CFStringRef copyTemporaryFolderPath(void) {
return path;
}
CFDictionaryRef createDockDescriptionForURL(CFURLRef url) {
if (!url) {
NSLog(CFSTR("%@"), CFSTR("in copyDockDescriptionForURL in CFGrowlAdditions: Cannot copy Dock description for a NULL URL"));
return NULL;
}
DATA_TYPE readFile(const char *filename)
{
CFDataRef data;
// read the file into a CFDataRef
FILE *fp = fopen(filename, "r");
if (fp) {
fseek(fp, 0, SEEK_END);
long dataLength = ftell(fp);
fseek(fp, 0, SEEK_SET);
unsigned char *fileData = malloc(dataLength);
fread(fileData, 1, dataLength, fp);
fclose(fp);
data = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, fileData, dataLength, kCFAllocatorMalloc);
} else
data = NULL;
//return NULL for non-file: URLs.
CFStringRef scheme = CFURLCopyScheme(url);
Boolean isFileURL = (CFStringCompare(scheme, CFSTR("file"), kCFCompareCaseInsensitive) == kCFCompareEqualTo);
CFRelease(scheme);
if (!isFileURL)
return NULL;
CFDictionaryRef dict = NULL;
CFStringRef path = NULL;
CFDataRef aliasData = NULL;
FSRef fsref;
if (CFURLGetFSRef(url, &fsref)) {
AliasHandle alias = NULL;
OSStatus err = FSNewAlias(/*fromFile*/ NULL, &fsref, &alias);
if (err != noErr) {
NSLog(CFSTR("in copyDockDescriptionForURL in CFGrowlAdditions: FSNewAlias for %@ returned %li"), url, (long)err);
} else {
HLock((Handle)alias);
err = FSCopyAliasInfo(alias, /*targetName*/ NULL, /*volumeName*/ NULL, (CFStringRef *)&path, /*whichInfo*/ NULL, /*info*/ NULL);
if (err != noErr) {
NSLog(CFSTR("in copyDockDescriptionForURL in CFGrowlAdditions: FSCopyAliasInfo for %@ returned %li"), url, (long)err);
}
aliasData = CFDataCreate(kCFAllocatorDefault, (UInt8 *)*alias, GetHandleSize((Handle)alias));
HUnlock((Handle)alias);
DisposeHandle((Handle)alias);
}
}
if (!path) {
path = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle);
}
if (path || aliasData) {
CFMutableDictionaryRef temp = CFDictionaryCreateMutable(kCFAllocatorDefault, /*capacity*/ 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
if (path) {
CFDictionarySetValue(temp, _CFURLStringKey, path);
CFRelease(path);
int pathStyle = kCFURLPOSIXPathStyle;
CFNumberRef pathStyleNum = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &pathStyle);
CFDictionarySetValue(temp, _CFURLStringTypeKey, pathStyleNum);
CFRelease(pathStyleNum);
}
if (aliasData) {
CFDictionarySetValue(temp, _CFURLAliasDataKey, aliasData);
CFRelease(aliasData);
}
dict = temp;
}
return dict;
return data;
}
URL_TYPE copyURLForApplication(STRING_TYPE appName)
{
CFURLRef appURL = NULL;
OSStatus err = LSFindApplicationForInfo(/*inCreator*/ kLSUnknownCreator,
/*inBundleID*/ NULL,
/*inName*/ appName,
/*outAppRef*/ NULL,
/*outAppURL*/ &appURL);
return (err == noErr) ? appURL : NULL;
}
CFStringRef createStringWithAddressData(CFDataRef aAddressData) {
STRING_TYPE createStringWithAddressData(DATA_TYPE aAddressData) {
struct sockaddr *socketAddress = (struct sockaddr *)CFDataGetBytePtr(aAddressData);
// IPv6 Addresses are "FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF"
// at max, which is 40 bytes (0-terminated)
@ -167,7 +219,7 @@ CFStringRef createStringWithAddressData(CFDataRef aAddressData) {
return addressAsString;
}
CFStringRef createHostNameForAddressData(CFDataRef aAddressData) {
STRING_TYPE createHostNameForAddressData(DATA_TYPE aAddressData) {
char hostname[NI_MAXHOST];
struct sockaddr *socketAddress = (struct sockaddr *)CFDataGetBytePtr(aAddressData);
if (getnameinfo(socketAddress, CFDataGetLength(aAddressData),
@ -179,7 +231,7 @@ CFStringRef createHostNameForAddressData(CFDataRef aAddressData) {
return CFStringCreateWithCString(kCFAllocatorDefault, hostname, kCFStringEncodingASCII);
}
CFDataRef copyIconDataForPath(CFStringRef path) {
DATA_TYPE copyIconDataForPath(STRING_TYPE path) {
CFDataRef data = NULL;
//false is probably safest, and is harmless when the object really is a directory.
@ -192,7 +244,8 @@ CFDataRef copyIconDataForPath(CFStringRef path) {
return data;
}
CFDataRef copyIconDataForURL(CFURLRef URL) {
DATA_TYPE copyIconDataForURL(URL_TYPE URL)
{
CFDataRef data = NULL;
if (URL) {
@ -227,7 +280,8 @@ CFDataRef copyIconDataForURL(CFURLRef URL) {
return data;
}
URL_TYPE createURLByMakingDirectoryAtURLWithName(URL_TYPE parent, STRING_TYPE name) {
URL_TYPE createURLByMakingDirectoryAtURLWithName(URL_TYPE parent, STRING_TYPE name)
{
CFURLRef newDirectory = NULL;
CFAllocatorRef allocator = parent ? CFGetAllocator(parent) : name ? CFGetAllocator(name) : kCFAllocatorDefault;
@ -264,8 +318,7 @@ URL_TYPE createURLByMakingDirectoryAtURLWithName(URL_TYPE parent, STRING_TYPE na
FSRef newDirectoryRef;
struct HFSUniStr255 nameUnicode;
CFIndex nameLength = CFStringGetLength(name);
CFRange range = { 0, (nameLength < USHRT_MAX ? nameLength : USHRT_MAX) };
CFRange range = { 0, MIN(CFStringGetLength(name), USHRT_MAX) };
CFStringGetCharacters(name, range, nameUnicode.unicode);
nameUnicode.length = range.length;
@ -278,7 +331,7 @@ URL_TYPE createURLByMakingDirectoryAtURLWithName(URL_TYPE parent, STRING_TYPE na
.textEncodingHint = kTextEncodingUnknown,
.newRef = &newDirectoryRef,
};
OSStatus err = PBCreateDirectoryUnicodeSync(&refPB);
if (err == dupFNErr) {
//dupFNErr == file (or folder) exists already. this is fine.
@ -475,7 +528,7 @@ static OSStatus GrowlCopyObjectSync(const FSRef *fileRef, const FSRef *destRef,
},
.outForkName = &forkName,
};
do {
err = PBIterateForksSync(&forkPB);
NSLog(CFSTR("PBIterateForksSync returned %li"), (long)err);
@ -492,7 +545,8 @@ static OSStatus GrowlCopyObjectSync(const FSRef *fileRef, const FSRef *destRef,
return err;
}
CFURLRef createURLByCopyingFileFromURLToDirectoryURL(CFURLRef file, CFURLRef dest) {
URL_TYPE createURLByCopyingFileFromURLToDirectoryURL(URL_TYPE file, URL_TYPE dest)
{
CFURLRef destFileURL = NULL;
FSRef fileRef, destRef, destFileRef;
@ -528,7 +582,8 @@ CFURLRef createURLByCopyingFileFromURLToDirectoryURL(CFURLRef file, CFURLRef des
return destFileURL;
}
CFPropertyListRef createPropertyListFromURL(CFURLRef file, u_int32_t mutability, CFPropertyListFormat *outFormat, CFStringRef *outErrorString) {
PLIST_TYPE createPropertyListFromURL(URL_TYPE file, u_int32_t mutability, CFPropertyListFormat *outFormat, STRING_TYPE *outErrorString)
{
CFPropertyListRef plist = NULL;
if (!file)

View File

@ -3,24 +3,27 @@
// Growl
//
// Created by Mac-arena the Bored Zo on Wed Jun 18 2004.
// Copyright 2005 The Growl Project.
// Copyright 2005-2006 The Growl Project.
//
// This file is under the BSD License, refer to License.txt for details
#ifdef __OBJC__
# define DATA_TYPE NSData *
# define DICTIONARY_TYPE NSDictionary *
# define STRING_TYPE NSString *
# define ARRAY_TYPE NSArray *
# define URL_TYPE NSURL *
# define PLIST_TYPE NSObject *
#else
# define DATA_TYPE CFDataRef
# define DICTIONARY_TYPE CFDictionaryRef
# define STRING_TYPE CFStringRef
# define ARRAY_TYPE CFArrayRef
# define URL_TYPE CFURLRef
# define PLIST_TYPE CFPropertyListRef
#endif
#ifndef HAVE_CFGROWLADDITIONS_H
#define HAVE_CFGROWLADDITIONS_H
#include "CFGrowlDefines.h"
//see GrowlApplicationBridge-Carbon.c for rationale of using NSLog.
extern void NSLog(STRING_TYPE format, ...);
char *createFileSystemRepresentationOfString(STRING_TYPE str);
STRING_TYPE createStringWithDate(DATE_TYPE date);
STRING_TYPE createStringWithContentsOfFile(STRING_TYPE filename, CFStringEncoding encoding);
//you can leave out any of these three components. to leave out the character, pass 0xffff.
STRING_TYPE createStringWithStringAndCharacterAndString(STRING_TYPE str0, UniChar ch, STRING_TYPE str1);
char *copyCString(STRING_TYPE str, CFStringEncoding encoding);
STRING_TYPE copyCurrentProcessName(void);
URL_TYPE copyCurrentProcessURL(void);
@ -32,7 +35,8 @@ STRING_TYPE copyTemporaryFolderPath(void);
STRING_TYPE createStringWithAddressData(DATA_TYPE aAddressData);
STRING_TYPE createHostNameForAddressData(DATA_TYPE aAddressData);
DICTIONARY_TYPE createDockDescriptionForURL(URL_TYPE url);
DATA_TYPE readFile(const char *filename);
URL_TYPE copyURLForApplication(STRING_TYPE appName);
/* @function copyIconDataForPath
* @param path The POSIX path to the file or folder whose icon you want.
@ -78,3 +82,5 @@ URL_TYPE createURLByCopyingFileFromURLToDirectoryURL(URL_TYPE file, URL_TYPE des
* @result The property list. You are responsible for releasing this object.
*/
PLIST_TYPE createPropertyListFromURL(URL_TYPE file, u_int32_t mutability, CFPropertyListFormat *outFormat, STRING_TYPE *outErrorString);
#endif

View File

@ -0,0 +1,38 @@
//
// CFURLDefines.h
// Growl
//
// Created by Ingmar Stein on Fri Sep 16 2005.
// Copyright 2005-2006 The Growl Project. All rights reserved.
//
// This file is under the BSD License, refer to License.txt for details
#ifndef HAVE_CFGROWLDEFINES_H
#define HAVE_CFGROWLDEFINES_H
#ifdef __OBJC__
# define DATA_TYPE NSData *
# define DATE_TYPE NSDate *
# define DICTIONARY_TYPE NSDictionary *
# define MUTABLE_DICTIONARY_TYPE NSMutableDictionary *
# define STRING_TYPE NSString *
# define ARRAY_TYPE NSArray *
# define URL_TYPE NSURL *
# define PLIST_TYPE NSObject *
# define OBJECT_TYPE id
# define BOOL_TYPE BOOL
#else
# include <CoreFoundation/CoreFoundation.h>
# define DATA_TYPE CFDataRef
# define DATE_TYPE CFDateRef
# define DICTIONARY_TYPE CFDictionaryRef
# define MUTABLE_DICTIONARY_TYPE CFMutableDictionaryRef
# define STRING_TYPE CFStringRef
# define ARRAY_TYPE CFArrayRef
# define URL_TYPE CFURLRef
# define PLIST_TYPE CFPropertyListRef
# define OBJECT_TYPE CFTypeRef
# define BOOL_TYPE Boolean
#endif
#endif

View File

@ -0,0 +1,24 @@
//
// CFMutableDictionaryAdditions.c
// Growl
//
// Created by Ingmar Stein on 29.05.05.
// Copyright 2005-2006 The Growl Project. All rights reserved.
//
// This file is under the BSD License, refer to License.txt for details
#include "CFMutableDictionaryAdditions.h"
void setObjectForKey(CFMutableDictionaryRef dict, const void *key, CFTypeRef value) {
CFDictionarySetValue(dict, key, value);
}
void setIntegerForKey(CFMutableDictionaryRef dict, const void *key, int value) {
CFNumberRef num = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &value);
CFDictionarySetValue(dict, key, num);
CFRelease(num);
}
void setBooleanForKey(CFMutableDictionaryRef dict, const void *key, Boolean value) {
CFDictionarySetValue(dict, key, value ? kCFBooleanTrue : kCFBooleanFalse);
}

View File

@ -0,0 +1,19 @@
//
// CFMutableDictionaryAdditions.h
// Growl
//
// Created by Ingmar Stein on 29.05.05.
// Copyright 2005-2006 The Growl Project. All rights reserved.
//
// This file is under the BSD License, refer to License.txt for details
#ifndef HAVE_CFMUTABLEDICTIONARYADDITIONS_H
#define HAVE_CFMUTABLEDICTIONARYADDITIONS_H
#include "CFGrowlDefines.h"
void setObjectForKey(MUTABLE_DICTIONARY_TYPE dict, const void *key, OBJECT_TYPE value);
void setIntegerForKey(MUTABLE_DICTIONARY_TYPE dict, const void *key, int value);
void setBooleanForKey(MUTABLE_DICTIONARY_TYPE dict, const void *key, BOOL_TYPE value);
#endif

View File

@ -0,0 +1,155 @@
//
// CFURLAdditions.c
// Growl
//
// Created by Karl Adam on Fri May 28 2004.
// Copyright 2004-2006 The Growl Project. All rights reserved.
//
// This file is under the BSD License, refer to License.txt for details
#include "CFURLAdditions.h"
#include "CFGrowlAdditions.h"
#include "CFMutableDictionaryAdditions.h"
#include <Carbon/Carbon.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#define _CFURLAliasDataKey CFSTR("_CFURLAliasData")
#define _CFURLStringKey CFSTR("_CFURLString")
#define _CFURLStringTypeKey CFSTR("_CFURLStringType")
//'alias' as in the Alias Manager.
URL_TYPE createFileURLWithAliasData(DATA_TYPE aliasData) {
if (!aliasData) {
NSLog(CFSTR("WARNING: createFileURLWithAliasData called with NULL aliasData"));
return NULL;
}
CFURLRef url = NULL;
AliasHandle alias = NULL;
OSStatus err = PtrToHand(CFDataGetBytePtr(aliasData), (Handle *)&alias, CFDataGetLength(aliasData));
if (err != noErr) {
NSLog(CFSTR("in createFileURLWithAliasData: Could not allocate an alias handle from %u bytes of alias data (data follows) because PtrToHand returned %li\n%@"), CFDataGetLength(aliasData), aliasData, (long)err);
} else {
CFStringRef path = NULL;
/*
* FSResolveAlias mounts disk images or network shares to resolve
* aliases, thus we resort to FSCopyAliasInfo.
*/
err = FSCopyAliasInfo(alias,
/* targetName */ NULL,
/* volumeName */ NULL,
&path,
/* whichInfo */ NULL,
/* info */ NULL);
DisposeHandle((Handle)alias);
if (err != noErr) {
if (err != fnfErr) //ignore file-not-found; it's harmless
NSLog(CFSTR("in createFileURLWithAliasData: Could not resolve alias (alias data follows) because FSResolveAlias returned %li - will try path\n%@"), (long)err, aliasData);
} else if (path) {
url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, path, kCFURLPOSIXPathStyle, true);
} else {
NSLog(CFSTR("in createFileURLWithAliasData: FSCopyAliasInfo returned a NULL path"));
}
CFRelease(path);
}
return url;
}
DATA_TYPE createAliasDataWithURL(URL_TYPE theURL) {
//return NULL for non-file: URLs.
CFStringRef scheme = CFURLCopyScheme(theURL);
CFComparisonResult isFileURL = CFStringCompare(scheme, CFSTR("file"), kCFCompareCaseInsensitive);
CFRelease(scheme);
if (isFileURL != kCFCompareEqualTo)
return NULL;
CFDataRef aliasData = NULL;
FSRef fsref;
if (CFURLGetFSRef(theURL, &fsref)) {
AliasHandle alias = NULL;
OSStatus err = FSNewAlias(/*fromFile*/ NULL, &fsref, &alias);
if (err != noErr) {
NSLog(CFSTR("in createAliasDataForURL: FSNewAlias for %@ returned %li"), theURL, (long)err);
} else {
HLock((Handle)alias);
aliasData = CFDataCreate(kCFAllocatorDefault, (const UInt8 *)*alias, GetHandleSize((Handle)alias));
HUnlock((Handle)alias);
DisposeHandle((Handle)alias);
}
}
return aliasData;
}
//these are the type of external representations used by Dock.app.
URL_TYPE createFileURLWithDockDescription(DICTIONARY_TYPE dict) {
CFURLRef url = NULL;
CFStringRef path = CFDictionaryGetValue(dict, _CFURLStringKey);
CFDataRef aliasData = CFDictionaryGetValue(dict, _CFURLAliasDataKey);
if (aliasData)
url = createFileURLWithAliasData(aliasData);
if (!url) {
if (path) {
CFNumberRef pathStyleNum = CFDictionaryGetValue(dict, _CFURLStringTypeKey);
CFURLPathStyle pathStyle;
if (pathStyleNum)
CFNumberGetValue(pathStyleNum, kCFNumberIntType, &pathStyle);
else
pathStyleNum = kCFURLPOSIXPathStyle;
char *filename = createFileSystemRepresentationOfString(path);
int fd = open(filename, O_RDONLY, 0);
free(filename);
if (fd != -1) {
struct stat sb;
fstat(fd, &sb);
close(fd);
url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, path, pathStyle, /*isDirectory*/ (sb.st_mode & S_IFDIR));
}
}
}
return url;
}
DICTIONARY_TYPE createDockDescriptionWithURL(URL_TYPE theURL) {
CFMutableDictionaryRef dict;
if (!theURL) {
NSLog(CFSTR("%@"), CFSTR("in createDockDescriptionWithURL: Cannot copy Dock description for a NULL URL"));
return NULL;
}
CFStringRef path = CFURLCopyFileSystemPath(theURL, kCFURLPOSIXPathStyle);
CFDataRef aliasData = createAliasDataWithURL(theURL);
if (path || aliasData) {
dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 3, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
if (path) {
CFDictionarySetValue(dict, _CFURLStringKey, path);
CFRelease(path);
setIntegerForKey(dict, _CFURLStringTypeKey, kCFURLPOSIXPathStyle);
}
if (aliasData) {
CFDictionarySetValue(dict, _CFURLAliasDataKey, aliasData);
CFRelease(aliasData);
}
} else {
dict = NULL;
}
return dict;
}

View File

@ -0,0 +1,25 @@
//
// CFURLAdditions.h
// Growl
//
// Created by Karl Adam on Fri May 28 2004.
// Copyright 2004-2006 The Growl Project. All rights reserved.
//
// This file is under the BSD License, refer to License.txt for details
#ifndef HAVE_CFURLADDITIONS_H
#define HAVE_CFURLADDITIONS_H
#include <CoreFoundation/CoreFoundation.h>
#include "CFGrowlDefines.h"
//'alias' as in the Alias Manager.
URL_TYPE createFileURLWithAliasData(DATA_TYPE aliasData);
DATA_TYPE createAliasDataWithURL(URL_TYPE theURL);
//these are the type of external representations used by Dock.app.
URL_TYPE createFileURLWithDockDescription(DICTIONARY_TYPE dict);
//createDockDescriptionWithURL returns NULL for non-file: URLs.
DICTIONARY_TYPE createDockDescriptionWithURL(URL_TYPE theURL);
#endif

View File

@ -0,0 +1,71 @@
//
// GrowlAbstractSingletonObject.h
// GBUtilities
//
// Renamed from GBAbstractSingletonObject to GrowlAbstractSingletonObject.
// Created by Ofri Wolfus on 15/08/05.
// Copyright 2005-2006 The Growl Project. All rights reserved.
//
#import <Foundation/Foundation.h>
/*!
* @class GrowlAbstractSingletonObject
* @brief An Abstract Singleton Object
*
* This is an abstract object for object that should have only one instnace
* that is never released (singleton object).
* This class is thread safe.
*/
@interface GrowlAbstractSingletonObject : NSObject {
BOOL _isInitialized;
}
/*!
* @brief Returns the shared instance of this class.
*/
+ (id) sharedInstance;
/*!
* @brief Releases and deallocates all the singletons that are subclasses of this object.
*
* Once +destroyAllSingletons has been called, no more singletons can be created
* and every call to [SomeSingletonSubclass sharedInstance] will return nil.
* Also note that a call to this method will destroy GBAbstractSingletonObject and all it's subclasses.
* Even though that you generally can't release a singleton object, it's dealloc message WILL be called
* when it's beeing destroyed.
*
* USE THIS METHOD WITH GREAT CAUTION!!!
*/
+ (void) destroyAllSingletons;
@end
/*!
* @category GrowlSingletonObjectInit
* @brief A private category for subclasses only.
*
* Only subclasses should override/call methods in the category.
*/
@interface GrowlAbstractSingletonObject (GrowlAbstractSingletonObjectInit)
/*!
* @brief An init method for your singleton object.
*
* Implement this in your subclass to init your shared object.
* You should call [super initSingleton] and return your initialized object.
* Never call this method directly! It'll be automatically called when needed.
*/
- (id) initSingleton;
/*!
* @brief Finish and clean up whatever your singleton does.
*
* This will be called before the singleton will be destroyed.
* You should put whatever you would put in the -dealloc method here instead
* and then call [super destroy].
*/
- (void) destroy;
@end

View File

@ -3,7 +3,7 @@
// Growl
//
// Created by Evan Schoenberg on Wed Jun 16 2004.
// Copyright 2004-2005 The Growl Project. All rights reserved.
// Copyright 2004-2006 The Growl Project. All rights reserved.
//
/*!
@ -17,23 +17,12 @@
#define __GrowlApplicationBridge_h__
#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
#import "GrowlDefines.h"
//Forward declarations
@protocol GrowlApplicationBridgeDelegate;
/*!
* @defined GROWL_PREFPANE_BUNDLE_IDENTIFIER
* @discussion The bundle identifier for the Growl prefpane.
*/
#define GROWL_PREFPANE_BUNDLE_IDENTIFIER @"com.growl.prefpanel"
/*!
* @defined GROWL_PREFPANE_NAME
* @discussion The file name of the Growl prefpane.
*/
#define GROWL_PREFPANE_NAME @"Growl.prefPane"
//Internal notification when the user chooses not to install (to avoid continuing to cache notifications awaiting installation)
#define GROWL_USER_CHOSE_NOT_TO_INSTALL_NOTIFICATION @"User chose not to install"
@ -176,6 +165,40 @@
clickContext:(id)clickContext
identifier:(NSString *)identifier;
/*!
* @method notifyWithTitle:description:notificationName:iconData:priority:isSticky:clickContext:identifier:
* @abstract Send a Growl notification.
* @discussion This is the preferred means for sending a Growl notification.
* The notification name and at least one of the title and description are
* required (all three are preferred). All other parameters may be
* <code>nil</code> (or 0 or NO as appropriate) to accept default values.
*
* If using the Growl-WithInstaller framework, if Growl is not installed the
* user will be prompted to install Growl. If the user cancels, this method
* will have no effect until the next application session, at which time when
* it is called the user will be prompted again. The user is also given the
* option to not be prompted again. If the user does choose to install Growl,
* the requested notification will be displayed once Growl is installed and
* running.
*
* @param title The title of the notification displayed to the user.
* @param description The full description of the notification displayed to the user.
* @param notifName The internal name of the notification. Should be human-readable, as it will be displayed in the Growl preference pane.
* @param iconData <code>NSData</code> object to show with the notification as its icon. If <code>nil</code>, the application's icon will be used instead.
* @param priority The priority of the notification. The default value is 0; positive values are higher priority and negative values are lower priority. Not all Growl displays support priority.
* @param isSticky If YES, the notification will remain on screen until clicked. Not all Growl displays support sticky notifications.
* @param clickContext A context passed back to the Growl delegate if it implements -(void)growlNotificationWasClicked: and the notification is clicked. Not all display plugins support clicking. The clickContext must be plist-encodable (completely of <code>NSString</code>, <code>NSArray</code>, <code>NSNumber</code>, <code>NSDictionary</code>, and <code>NSData</code> types).
* @param identifier An identifier for this notification. Notifications with equal identifiers are coalesced.
*/
+ (void) notifyWithTitle:(NSString *)title
description:(NSString *)description
notificationName:(NSString *)notifName
iconData:(NSData *)iconData
priority:(signed int)priority
isSticky:(BOOL)isSticky
clickContext:(id)clickContext
identifier:(NSString *)identifier;
/*! @method notifyWithDictionary:
* @abstract Notifies using a userInfo dictionary suitable for passing to
* <code>NSDistributedNotificationCenter</code>.
@ -371,6 +394,21 @@
*/
+ (NSDictionary *) registrationDictionaryByFillingInDictionary:(NSDictionary *)regDict restrictToKeys:(NSSet *)keys;
/*! @brief Tries to fill in missing keys in a notification dictionary.
* @param notifDict The dictionary to fill in.
* @return The dictionary with the keys filled in. This will be a separate instance from \a notifDict.
* @discussion This function examines the \a notifDict for missing keys, and
* tries to get them from the last known registration dictionary. As of 1.1,
* the keys that it will look for are:
*
* \li <code>GROWL_APP_NAME</code>
* \li <code>GROWL_APP_ICON</code>
*
* @since Growl.framework 1.1
*/
+ (NSDictionary *) notificationDictionaryByFillingInDictionary:(NSDictionary *)regDict;
+ (NSDictionary *) frameworkInfoDictionary;
@end
//------------------------------------------------------------------------------
@ -417,10 +455,13 @@
* <code>+[GrowlApplicationBridge
* notifyWithTitle:description:notificationName:iconData:priority:isSticky:clickContext:]</code> calls.
*
* The dictionary should have 2 key object pairs:
* The dictionary should have the required key object pairs:
* key: GROWL_NOTIFICATIONS_ALL object: <code>NSArray</code> of <code>NSString</code> objects
* key: GROWL_NOTIFICATIONS_DEFAULT object: <code>NSArray</code> of <code>NSString</code> objects
*
* The dictionary may have the following key object pairs:
* key: GROWL_NOTIFICATIONS_HUMAN_READABLE_NAMES object: <code>NSDictionary</code> of key: notification name object: human-readable notification name
*
* You do not need to implement this method if you have an auto-discoverable
* plist file in your app bundle. (XXX refer to more information on that)
*
@ -447,6 +488,18 @@
*/
- (NSString *) applicationNameForGrowl;
/*!
* @method applicationIconForGrowl
* @abstract Return the <code>NSImage</code> to treat as the application icon.
* @discussion The delegate may optionally return an <code>NSImage</code>
* object to use as the application icon. If this method is not implemented,
* {{{-applicationIconDataForGrowl}}} is tried. If that method is not
* implemented, the application's own icon is used. Neither method is
* generally needed.
* @result The <code>NSImage</code> to treat as the application icon.
*/
- (NSImage *) applicationIconForGrowl;
/*!
* @method applicationIconDataForGrowl
* @abstract Return the <code>NSData</code> to treat as the application icon.
@ -454,6 +507,7 @@
* object to use as the application icon; if this is not implemented, the
* application's own icon is used. This is not generally needed.
* @result The <code>NSData</code> to treat as the application icon.
* @deprecated In version 1.1, in favor of {{{-applicationIconForGrowl}}}.
*/
- (NSData *) applicationIconDataForGrowl;
@ -461,9 +515,8 @@
* @method growlIsReady
* @abstract Informs the delegate that Growl has launched.
* @discussion Informs the delegate that Growl (specifically, the
* GrowlHelperApp) was launched successfully or was already running. The
* application can take actions with the knowledge that Growl is installed and
* functional.
* GrowlHelperApp) was launched successfully. The application can take actions
* with the knowledge that Growl is installed and functional.
*/
- (void) growlIsReady;

View File

@ -3,7 +3,7 @@
// Growl
//
// Created by Evan Schoenberg on Wed Jun 16 2004.
// Copyright 2004-2005 The Growl Project. All rights reserved.
// Copyright 2004-2006 The Growl Project. All rights reserved.
//
#import "GrowlApplicationBridge.h"
@ -11,16 +11,37 @@
#import "GrowlInstallationPrompt.h"
#import "GrowlVersionUtilities.h"
#endif
#import "NSURLAdditions.h"
#import "CFGrowlAdditions.h"
#include "CFGrowlAdditions.h"
#include "CFURLAdditions.h"
#include "CFMutableDictionaryAdditions.h"
#import "GrowlDefinesInternal.h"
#import "GrowlPathUtil.h"
#import "GrowlPathUtilities.h"
#import "GrowlPathway.h"
#import <ApplicationServices/ApplicationServices.h>
#define PREFERENCE_PANES_SUBFOLDER_OF_LIBRARY @"PreferencePanes"
#define PREFERENCE_PANE_EXTENSION @"prefPane"
/*!
* The 10.3+ exception handling can only work if -fobjc-exceptions is enabled
*/
#if 0
#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_3
# define TRY @try {
# define ENDTRY }
# define CATCH @catch(NSException *localException) {
# define ENDCATCH }
#else
# define TRY NS_DURING
# define ENDTRY
# define CATCH NS_HANDLER
# define ENDCATCH NS_ENDHANDLER
#endif
#else
# define TRY NS_DURING
# define ENDTRY
# define CATCH NS_HANDLER
# define ENDCATCH NS_ENDHANDLER
#endif
@interface GrowlApplicationBridge (PRIVATE)
/*!
@ -66,13 +87,19 @@
*/
+ (NSData *) _applicationIconDataForGrowlSearchingRegistrationDictionary:(NSDictionary *)regDict;
/*! @method growlProxy
* @abstract Obtain (creating a connection if needed) a proxy to the Growl Helper Application
*/
+ (NSProxy<GrowlNotificationProtocol> *) growlProxy;
@end
static NSDictionary *cachedRegistrationDictionary = nil;
static NSString *appName = nil;
static NSData *appIconData = nil;
static id delegate = nil;
static BOOL growlLaunched = NO;
static NSProxy<GrowlNotificationProtocol> *growlProxy = nil;
#ifdef GROWL_WITH_INSTALLER
static NSMutableArray *queuedGrowlNotifications = nil;
@ -92,20 +119,27 @@ static BOOL registerWhenGrowlIsReady = NO;
+ (void) setGrowlDelegate:(NSObject<GrowlApplicationBridgeDelegate> *)inDelegate {
NSDistributedNotificationCenter *NSDNC = [NSDistributedNotificationCenter defaultCenter];
[delegate autorelease];
delegate = [inDelegate retain];
if (inDelegate != delegate) {
[delegate release];
delegate = [inDelegate retain];
}
NSDictionary *regDict = [self bestRegistrationDictionary];
[cachedRegistrationDictionary release];
cachedRegistrationDictionary = [[self bestRegistrationDictionary] retain];
//Cache the appName from the delegate or the process name
[appName autorelease];
appName = [[self _applicationNameForGrowlSearchingRegistrationDictionary:regDict] retain];
if (!appName)
appName = [[self _applicationNameForGrowlSearchingRegistrationDictionary:cachedRegistrationDictionary] retain];
if (!appName) {
NSLog(@"%@", @"GrowlApplicationBridge: Cannot register because the application name was not supplied and could not be determined");
return;
}
//Cache the appIconData from the delegate if it responds to the applicationIconDataForGrowl selector, or the application if not
/* Cache the appIconData from the delegate if it responds to the
* applicationIconDataForGrowl selector, or the application if not
*/
[appIconData autorelease];
appIconData = [[self _applicationIconDataForGrowlSearchingRegistrationDictionary:regDict] retain];
appIconData = [[self _applicationIconDataForGrowlSearchingRegistrationDictionary:cachedRegistrationDictionary] retain];
//Add the observer for GROWL_IS_READY which will be triggered later if all goes well
[NSDNC addObserver:self
@ -113,35 +147,36 @@ static BOOL registerWhenGrowlIsReady = NO;
name:GROWL_IS_READY
object:nil];
//Watch for notification clicks if our delegate responds to the growlNotificationWasClicked: selector
//Notifications will come in on a unique notification name based on our app name and GROWL_NOTIFICATION_CLICKED
/* Watch for notification clicks if our delegate responds to the
* growlNotificationWasClicked: selector. Notifications will come in on a
* unique notification name based on our app name, pid and
* GROWL_NOTIFICATION_CLICKED.
*/
int pid = [[NSProcessInfo processInfo] processIdentifier];
NSString *growlNotificationClickedName = [[NSString alloc] initWithFormat:@"%@-%d-%@",
appName, pid, GROWL_NOTIFICATION_CLICKED];
if ([delegate respondsToSelector:@selector(growlNotificationWasClicked:)]) {
if ([delegate respondsToSelector:@selector(growlNotificationWasClicked:)])
[NSDNC addObserver:self
selector:@selector(_growlNotificationWasClicked:)
selector:@selector(growlNotificationWasClicked:)
name:growlNotificationClickedName
object:nil];
} else {
else
[NSDNC removeObserver:self
name:growlNotificationClickedName
object:nil];
}
[growlNotificationClickedName release];
NSString *growlNotificationTimedOutName = [[NSString alloc] initWithFormat:@"%@-%d-%@",
appName, pid, GROWL_NOTIFICATION_TIMED_OUT];
if ([delegate respondsToSelector:@selector(growlNotificationTimedOut:)]) {
if ([delegate respondsToSelector:@selector(growlNotificationTimedOut:)])
[NSDNC addObserver:self
selector:@selector(_growlNotificationTimedOut:)
selector:@selector(growlNotificationTimedOut:)
name:growlNotificationTimedOutName
object:nil];
} else {
else
[NSDNC removeObserver:self
name:growlNotificationTimedOutName
object:nil];
}
[growlNotificationTimedOutName release];
#ifdef GROWL_WITH_INSTALLER
@ -149,7 +184,7 @@ static BOOL registerWhenGrowlIsReady = NO;
userChoseNotToInstallGrowl = [[NSUserDefaults standardUserDefaults] boolForKey:@"Growl Installation:Do Not Prompt Again"];
#endif
growlLaunched = [self _launchGrowlIfInstalledWithRegistrationDictionary:regDict];
growlLaunched = [self _launchGrowlIfInstalledWithRegistrationDictionary:cachedRegistrationDictionary];
}
+ (NSObject<GrowlApplicationBridgeDelegate> *) growlDelegate {
@ -176,10 +211,10 @@ static BOOL registerWhenGrowlIsReady = NO;
identifier:nil];
}
/*Send a notification to Growl for display.
*title, description, and notifName are required.
*All other id parameters may be nil to accept defaults.
*priority is 0 by default; isSticky is NO by default.
/* Send a notification to Growl for display.
* title, description, and notifName are required.
* All other id parameters may be nil to accept defaults.
* priority is 0 by default; isSticky is NO by default.
*/
+ (void) notifyWithTitle:(NSString *)title
description:(NSString *)description
@ -193,75 +228,61 @@ static BOOL registerWhenGrowlIsReady = NO;
NSParameterAssert(notifName); //Notification name is required.
NSParameterAssert(title || description); //At least one of title or description is required.
NSDictionary *regDict = [self bestRegistrationDictionary];
if (!appName)
appName = [[self _applicationNameForGrowlSearchingRegistrationDictionary:regDict] retain];
if (!appIconData)
appIconData = [[self _applicationIconDataForGrowlSearchingRegistrationDictionary:regDict] retain];
NSNumber *pid = [[NSNumber alloc] initWithInt:[[NSProcessInfo processInfo] processIdentifier]];
// Build our noteDict from all passed parameters
NSMutableDictionary *noteDict = [[NSMutableDictionary alloc] initWithObjectsAndKeys:
appName, GROWL_APP_NAME,
pid, GROWL_APP_PID,
notifName, GROWL_NOTIFICATION_NAME,
appIconData, GROWL_NOTIFICATION_APP_ICON,
nil];
[pid release];
if (title) [noteDict setObject:title forKey:GROWL_NOTIFICATION_TITLE];
if (description) [noteDict setObject:description forKey:GROWL_NOTIFICATION_DESCRIPTION];
if (iconData) [noteDict setObject:iconData forKey:GROWL_NOTIFICATION_ICON];
if (clickContext) [noteDict setObject:clickContext forKey:GROWL_NOTIFICATION_CLICK_CONTEXT];
if (priority) {
NSNumber *value = [[NSNumber alloc] initWithInt:priority];
[noteDict setObject:value forKey:GROWL_NOTIFICATION_PRIORITY];
[value release];
}
if (isSticky) {
NSNumber *value = [[NSNumber alloc] initWithBool:isSticky];
[noteDict setObject:value forKey:GROWL_NOTIFICATION_STICKY];
[value release];
}
if (identifier) [noteDict setObject:identifier forKey:GROWL_NOTIFICATION_IDENTIFIER];
if (title) setObjectForKey(noteDict, GROWL_NOTIFICATION_TITLE, title);
if (description) setObjectForKey(noteDict, GROWL_NOTIFICATION_DESCRIPTION, description);
if (iconData) setObjectForKey(noteDict, GROWL_NOTIFICATION_ICON, iconData);
if (clickContext) setObjectForKey(noteDict, GROWL_NOTIFICATION_CLICK_CONTEXT, clickContext);
if (priority) setIntegerForKey(noteDict, GROWL_NOTIFICATION_PRIORITY, priority);
if (isSticky) setBooleanForKey(noteDict, GROWL_NOTIFICATION_STICKY, isSticky);
if (identifier) setObjectForKey(noteDict, GROWL_NOTIFICATION_IDENTIFIER, identifier);
[self notifyWithDictionary:noteDict];
[noteDict release];
}
+ (void) notifyWithDictionary:(NSDictionary *)userInfo {
//clean up things that need to be cleaned up.
NSMutableDictionary *mUserInfo = [userInfo mutableCopy];
Class NSImageClass = [NSImage class];
//notification icon.
NSImage *icon = [mUserInfo objectForKey:GROWL_NOTIFICATION_ICON];
if (icon && [icon isKindOfClass:NSImageClass])
[mUserInfo setObject:[icon TIFFRepresentation] forKey:GROWL_NOTIFICATION_ICON];
//per-notification application icon.
icon = [mUserInfo objectForKey:GROWL_NOTIFICATION_APP_ICON];
if (icon && [icon isKindOfClass:NSImageClass])
[mUserInfo setObject:[icon TIFFRepresentation] forKey:GROWL_NOTIFICATION_APP_ICON];
userInfo = [mUserInfo autorelease];
//post it.
if (growlLaunched) {
NSConnection *connection = [NSConnection connectionWithRegisteredName:@"GrowlApplicationBridgePathway" host:nil];
if (connection) {
if (growlLaunched) {
NSProxy<GrowlNotificationProtocol> *currentGrowlProxy = [self growlProxy];
//Make sure we have everything that we need (that we can retrieve from the registration dictionary).
userInfo = [self notificationDictionaryByFillingInDictionary:userInfo];
if (currentGrowlProxy) {
//Post to Growl via GrowlApplicationBridgePathway
NS_DURING
NSDistantObject *theProxy = [connection rootProxy];
[theProxy setProtocolForProxy:@protocol(GrowlNotificationProtocol)];
id<GrowlNotificationProtocol> growlProxy = (id)theProxy;
[growlProxy postNotificationWithDictionary:userInfo];
NS_HANDLER
TRY
[currentGrowlProxy postNotificationWithDictionary:userInfo];
ENDTRY
CATCH
NSLog(@"GrowlApplicationBridge: exception while sending notification: %@", localException);
NS_ENDHANDLER
ENDCATCH
} else {
//Post to Growl via NSDistributedNotificationCenter
//NSLog(@"GrowlApplicationBridge: could not find local GrowlApplicationBridgePathway, falling back to NSDistributedNotificationCenter");
//DNC needs a plist. this means we must pass data, not an NSImage.
Class NSImageClass = [NSImage class];
NSImage *icon = [userInfo objectForKey:GROWL_NOTIFICATION_ICON];
NSImage *appIcon = [userInfo objectForKey:GROWL_NOTIFICATION_APP_ICON];
BOOL iconIsImage = icon && [icon isKindOfClass:NSImageClass];
BOOL appIconIsImage = appIcon && [appIcon isKindOfClass:NSImageClass];
if (iconIsImage || appIconIsImage) {
NSMutableDictionary *mUserInfo = [userInfo mutableCopy];
//notification icon.
if (iconIsImage)
[mUserInfo setObject:[icon TIFFRepresentation] forKey:GROWL_NOTIFICATION_ICON];
//per-notification application icon.
if (appIconIsImage)
[mUserInfo setObject:[appIcon TIFFRepresentation] forKey:GROWL_NOTIFICATION_APP_ICON];
userInfo = [mUserInfo autorelease];
}
//Post to Growl via NSDistributedNotificationCenter
[[NSDistributedNotificationCenter defaultCenter] postNotificationName:GROWL_NOTIFICATION
object:nil
userInfo:userInfo
@ -273,12 +294,8 @@ static BOOL registerWhenGrowlIsReady = NO;
* it, store this notification for posting
*/
if (!userChoseNotToInstallGrowl) {
//in case the dictionary is mutable, make a copy.
userInfo = [userInfo copy];
if (!queuedGrowlNotifications) {
if (!queuedGrowlNotifications)
queuedGrowlNotifications = [[NSMutableArray alloc] init];
}
[queuedGrowlNotifications addObject:userInfo];
//if we have not already asked the user to install Growl, do it now
@ -286,7 +303,6 @@ static BOOL registerWhenGrowlIsReady = NO;
[GrowlInstallationPrompt showInstallationPrompt];
promptedToInstallGrowl = YES;
}
[userInfo release];
}
#endif
}
@ -295,7 +311,7 @@ static BOOL registerWhenGrowlIsReady = NO;
#pragma mark -
+ (BOOL) isGrowlInstalled {
return ([GrowlPathUtil growlPrefPaneBundle] != nil);
return ([GrowlPathUtilities growlPrefPaneBundle] != nil);
}
+ (BOOL) isGrowlRunning {
@ -303,19 +319,30 @@ static BOOL registerWhenGrowlIsReady = NO;
ProcessSerialNumber PSN = { kNoProcess, kNoProcess };
while (GetNextProcess(&PSN) == noErr) {
NSDictionary *infoDict = (NSDictionary *)ProcessInformationCopyDictionary(&PSN, kProcessDictionaryIncludeAllInformationMask);
CFDictionaryRef infoDict = ProcessInformationCopyDictionary(&PSN, kProcessDictionaryIncludeAllInformationMask);
CFStringRef bundleId = CFDictionaryGetValue(infoDict, kCFBundleIdentifierKey);
if ([[infoDict objectForKey:(NSString *)kCFBundleIdentifierKey] isEqualToString:@"com.Growl.GrowlHelperApp"]) {
if (bundleId && CFStringCompare(bundleId, CFSTR("com.Growl.GrowlHelperApp"), 0) == kCFCompareEqualTo) {
growlIsRunning = YES;
[infoDict release];
CFRelease(infoDict);
break;
}
[infoDict release];
CFRelease(infoDict);
}
return growlIsRunning;
}
+ (void) displayInstallationPromptIfNeeded {
#ifdef GROWL_WITH_INSTALLER
//if we have not already asked the user to install Growl, do it now
if (!promptedToInstallGrowl) {
[GrowlInstallationPrompt showInstallationPrompt];
promptedToInstallGrowl = YES;
}
#endif
}
#pragma mark -
+ (BOOL) registerWithDictionary:(NSDictionary *)regDict {
@ -323,6 +350,10 @@ static BOOL registerWhenGrowlIsReady = NO;
regDict = [self registrationDictionaryByFillingInDictionary:regDict];
else
regDict = [self bestRegistrationDictionary];
[cachedRegistrationDictionary release];
cachedRegistrationDictionary = [regDict retain];
return [self _launchGrowlIfInstalledWithRegistrationDictionary:regDict];
}
@ -365,16 +396,13 @@ static BOOL registerWhenGrowlIsReady = NO;
+ (NSDictionary *) bestRegistrationDictionary {
NSDictionary *registrationDictionary = [self registrationDictionaryFromDelegate];
if (!registrationDictionary)
registrationDictionary = [self registrationDictionaryFromBundle:nil];
if (!registrationDictionary) {
NSLog(@"GrowlApplicationBridge: The Growl delegate did not supply a registration dictionary, and the app bundle at %@ does not have one. Please tell this application's developer.", [[NSBundle mainBundle] bundlePath]);
registrationDictionary = [self registrationDictionaryFromBundle:nil];
if (!registrationDictionary)
NSLog(@"GrowlApplicationBridge: The Growl delegate did not supply a registration dictionary, and the app bundle at %@ does not have one. Please tell this application's developer.", [[NSBundle mainBundle] bundlePath]);
}
registrationDictionary = [self registrationDictionaryByFillingInDictionary:registrationDictionary];
return registrationDictionary;
return [self registrationDictionaryByFillingInDictionary:registrationDictionary];
}
#pragma mark -
@ -400,16 +428,10 @@ static BOOL registerWhenGrowlIsReady = NO;
if ((!keys) || [keys containsObject:GROWL_APP_ICON]) {
if (![mRegDict objectForKey:GROWL_APP_ICON]) {
if (!appIconData) {
appIconData = [self _applicationIconDataForGrowlSearchingRegistrationDictionary:regDict];
if(appIconData && [appIconData isKindOfClass:[NSImage class]])
appIconData = [(NSImage *)appIconData TIFFRepresentation];
appIconData = [appIconData retain];
}
if (appIconData) {
[mRegDict setObject:appIconData
forKey:GROWL_APP_ICON];
}
if (!appIconData)
appIconData = [[self _applicationIconDataForGrowlSearchingRegistrationDictionary:regDict] retain];
if (appIconData)
[mRegDict setObject:appIconData forKey:GROWL_APP_ICON];
}
}
@ -417,11 +439,11 @@ static BOOL registerWhenGrowlIsReady = NO;
if (![mRegDict objectForKey:GROWL_APP_LOCATION]) {
NSURL *myURL = copyCurrentProcessURL();
if (myURL) {
NSDictionary *file_data = [myURL dockDescription];
NSDictionary *file_data = createDockDescriptionWithURL(myURL);
if (file_data) {
NSDictionary *location = [[NSDictionary alloc] initWithObjectsAndKeys:file_data, @"file-data", nil];
[mRegDict setObject:location
forKey:GROWL_APP_LOCATION];
[file_data release];
[mRegDict setObject:location forKey:GROWL_APP_LOCATION];
[location release];
} else {
[mRegDict removeObjectForKey:GROWL_APP_LOCATION];
@ -434,16 +456,60 @@ static BOOL registerWhenGrowlIsReady = NO;
if ((!keys) || [keys containsObject:GROWL_NOTIFICATIONS_DEFAULT]) {
if (![mRegDict objectForKey:GROWL_NOTIFICATIONS_DEFAULT]) {
NSArray *all = [mRegDict objectForKey:GROWL_NOTIFICATIONS_ALL];
if (all) {
[mRegDict setObject:all
forKey:GROWL_NOTIFICATIONS_DEFAULT];
}
if (all)
[mRegDict setObject:all forKey:GROWL_NOTIFICATIONS_DEFAULT];
}
}
NSDictionary *result = [NSDictionary dictionaryWithDictionary:mRegDict];
[mRegDict release];
return result;
if ((!keys) || [keys containsObject:GROWL_APP_ID])
if (![mRegDict objectForKey:GROWL_APP_ID])
[mRegDict setObject:(NSString *)CFBundleGetIdentifier(CFBundleGetMainBundle()) forKey:GROWL_APP_ID];
return [mRegDict autorelease];
}
+ (NSDictionary *) notificationDictionaryByFillingInDictionary:(NSDictionary *)notifDict {
NSMutableDictionary *mNotifDict = [notifDict mutableCopy];
if (![mNotifDict objectForKey:GROWL_APP_NAME]) {
if (!appName)
appName = [[self _applicationNameForGrowlSearchingRegistrationDictionary:cachedRegistrationDictionary] retain];
if (appName) {
[mNotifDict setObject:appName
forKey:GROWL_APP_NAME];
}
}
if (![mNotifDict objectForKey:GROWL_APP_ICON]) {
if (!appIconData)
appIconData = [[self _applicationIconDataForGrowlSearchingRegistrationDictionary:cachedRegistrationDictionary] retain];
if (appIconData) {
[mNotifDict setObject:appIconData
forKey:GROWL_APP_ICON];
}
}
//Only include the PID when there's a click context. We do this because NSDNC imposes a 15-MiB limit on the serialized notification, and we wouldn't want to overrun it because of a 4-byte PID.
if ([mNotifDict objectForKey:GROWL_NOTIFICATION_CLICK_CONTEXT] && ![mNotifDict objectForKey:GROWL_APP_PID]) {
NSNumber *pidNum = [[NSNumber alloc] initWithInt:[[NSProcessInfo processInfo] processIdentifier]];
[mNotifDict setObject:pidNum
forKey:GROWL_APP_PID];
[pidNum release];
}
return [mNotifDict autorelease];
}
+ (NSDictionary *) frameworkInfoDictionary {
#ifdef GROWL_WITH_INSTALLER
return (NSDictionary *)CFBundleGetInfoDictionary(CFBundleGetBundleWithIdentifier(CFSTR("com.growl.growlwithinstallerframework")));
#else
return (NSDictionary *)CFBundleGetInfoDictionary(CFBundleGetBundleWithIdentifier(CFSTR("com.growl.growlframework")));
#endif
}
#pragma mark -
@ -455,23 +521,31 @@ static BOOL registerWhenGrowlIsReady = NO;
if (delegate && [delegate respondsToSelector:@selector(applicationNameForGrowl)])
applicationNameForGrowl = [delegate applicationNameForGrowl];
if (!applicationNameForGrowl)
if (!applicationNameForGrowl) {
applicationNameForGrowl = [regDict objectForKey:GROWL_APP_NAME];
if (!applicationNameForGrowl)
applicationNameForGrowl = [[NSProcessInfo processInfo] processName];
if (!applicationNameForGrowl)
applicationNameForGrowl = [[NSProcessInfo processInfo] processName];
}
return applicationNameForGrowl;
}
+ (NSData *) _applicationIconDataForGrowlSearchingRegistrationDictionary:(NSDictionary *)regDict {
NSData *iconData = nil;
if (delegate && [delegate respondsToSelector:@selector(applicationIconDataForGrowl)])
iconData = [delegate applicationIconDataForGrowl];
if (delegate) {
if ([delegate respondsToSelector:@selector(applicationIconForGrowl)])
iconData = (NSData *)[delegate applicationIconForGrowl];
else if ([delegate respondsToSelector:@selector(applicationIconDataForGrowl)])
iconData = [delegate applicationIconDataForGrowl];
}
if (!iconData)
iconData = [regDict objectForKey:GROWL_APP_ICON];
if (iconData && [iconData isKindOfClass:[NSImage class]])
iconData = [(NSImage *)iconData TIFFRepresentation];
if (!iconData) {
NSURL *URL = copyCurrentProcessURL();
iconData = [copyIconDataForURL(URL) autorelease];
@ -485,19 +559,65 @@ static BOOL registerWhenGrowlIsReady = NO;
* called manually, and the calling observer should only be registered if the
* delegate responds to growlNotificationWasClicked:.
*/
+ (void) _growlNotificationWasClicked:(NSNotification *)notification {
[delegate performSelector:@selector(growlNotificationWasClicked:)
withObject:[[notification userInfo] objectForKey:GROWL_KEY_CLICKED_CONTEXT]];
+ (void) growlNotificationWasClicked:(NSNotification *)notification {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[delegate growlNotificationWasClicked:
[[notification userInfo] objectForKey:GROWL_KEY_CLICKED_CONTEXT]];
[pool release];
}
+ (void) _growlNotificationTimedOut:(NSNotification *)notification {
[delegate performSelector:@selector(growlNotificationTimedOut:)
withObject:[[notification userInfo] objectForKey:GROWL_KEY_CLICKED_CONTEXT]];
+ (void) growlNotificationTimedOut:(NSNotification *)notification {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[delegate growlNotificationTimedOut:
[[notification userInfo] objectForKey:GROWL_KEY_CLICKED_CONTEXT]];
[pool release];
}
#pragma mark -
//When a connection dies, release our reference to its proxy
+ (void) connectionDidDie:(NSNotification *)notification {
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSConnectionDidDieNotification
object:[notification object]];
[growlProxy release]; growlProxy = nil;
}
+ (NSProxy<GrowlNotificationProtocol> *) growlProxy {
if (!growlProxy) {
NSConnection *connection = [NSConnection connectionWithRegisteredName:@"GrowlApplicationBridgePathway" host:nil];
if (connection) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(connectionDidDie:)
name:NSConnectionDidDieNotification
object:connection];
TRY
{
NSDistantObject *theProxy = [connection rootProxy];
if ([theProxy respondsToSelector:@selector(registerApplicationWithDictionary:)]) {
[theProxy setProtocolForProxy:@protocol(GrowlNotificationProtocol)];
growlProxy = [(NSProxy<GrowlNotificationProtocol> *)theProxy retain];
} else {
NSLog(@"Received a fake GrowlApplicationBridgePathway object. Some other application is interfering with Growl, or something went horribly wrong. Please file a bug report.");
growlProxy = nil;
}
}
ENDTRY
CATCH
{
NSLog(@"GrowlApplicationBridge: exception while sending notification: %@", localException);
growlProxy = nil;
}
ENDCATCH
}
}
return growlProxy;
}
+ (void) _growlIsReady:(NSNotification *)notification {
#pragma unused(notification)
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
//Growl has now launched; we may get here with (growlLaunched == NO) when the user first installs
growlLaunched = YES;
@ -508,13 +628,13 @@ static BOOL registerWhenGrowlIsReady = NO;
//Post a notification locally
[[NSNotificationCenter defaultCenter] postNotificationName:GROWL_IS_READY
object:nil];
object:nil
userInfo:nil];
//Stop observing for GROWL_IS_READY
NSDistributedNotificationCenter *distCenter = [NSDistributedNotificationCenter defaultCenter];
[distCenter removeObserver:self
name:GROWL_IS_READY
object:nil];
[[NSDistributedNotificationCenter defaultCenter] removeObserver:self
name:GROWL_IS_READY
object:nil];
//register (fixes #102: this is necessary if we got here by Growl having just been installed)
if (registerWhenGrowlIsReady) {
@ -527,30 +647,31 @@ static BOOL registerWhenGrowlIsReady = NO;
NSEnumerator *enumerator = [queuedGrowlNotifications objectEnumerator];
NSDictionary *noteDict;
while ((noteDict = [enumerator nextObject])) {
NSConnection *connection = [NSConnection connectionWithRegisteredName:@"GrowlApplicationBridgePathway" host:nil];
if (connection) {
//Configure the growl proxy if it isn't currently configured
NSProxy<GrowlNotificationProtocol> *currentGrowlProxy = [self growlProxy];
while ((noteDict = [enumerator nextObject])) {
if (currentGrowlProxy) {
//Post to Growl via GrowlApplicationBridgePathway
NS_DURING
NSDistantObject *theProxy = [connection rootProxy];
[theProxy setProtocolForProxy:@protocol(GrowlNotificationProtocol)];
id<GrowlNotificationProtocol> growlProxy = (id)theProxy;
[growlProxy postNotificationWithDictionary:noteDict];
[currentGrowlProxy postNotificationWithDictionary:noteDict];
NS_HANDLER
NSLog(@"GrowlApplicationBridge: exception while sending notification: %@", localException);
NS_ENDHANDLER
} else {
//Post to Growl via NSDistributedNotificationCenter
//NSLog(@"GrowlApplicationBridge: could not find local GrowlApplicationBridgePathway, falling back to NSDistributedNotificationCenter");
[distCenter postNotificationName:GROWL_NOTIFICATION
object:nil
userInfo:noteDict
deliverImmediately:NO];
NSLog(@"GrowlApplicationBridge: could not find local GrowlApplicationBridgePathway, falling back to NSDistributedNotificationCenter");
[[NSDistributedNotificationCenter defaultCenter] postNotificationName:GROWL_NOTIFICATION
object:NULL
userInfo:noteDict
deliverImmediately:FALSE];
}
}
[queuedGrowlNotifications release]; queuedGrowlNotifications = nil;
#endif
[pool release];
}
#ifdef GROWL_WITH_INSTALLER
@ -572,10 +693,14 @@ static BOOL registerWhenGrowlIsReady = NO;
NSString *packagedVersion, *installedVersion;
BOOL upgradeIsAvailable;
ourGrowlPrefPaneInfoPath = [[NSBundle bundleForClass:[self class]] pathForResource:@"GrowlPrefPaneInfo"
ofType:@"plist"];
ourGrowlPrefPaneInfoPath = [[NSBundle bundleWithIdentifier:@"com.growl.growlwithinstallerframework"] pathForResource:@"GrowlPrefPaneInfo"
ofType:@"plist"];
NSObject *infoPropertyList = createPropertyListFromURL([NSURL fileURLWithPath:ourGrowlPrefPaneInfoPath],
kCFPropertyListImmutable,
/* outFormat */ NULL, /* outErrorString */ NULL);
NSDictionary *infoDict = ([infoPropertyList isKindOfClass:[NSDictionary class]] ? (NSDictionary *)infoPropertyList : nil);
NSDictionary *infoDict = [[NSDictionary alloc] initWithContentsOfFile:ourGrowlPrefPaneInfoPath];
packagedVersion = [infoDict objectForKey:(NSString *)kCFBundleVersionKey];
infoDictionary = [growlPrefPaneBundle infoDictionary];
@ -603,7 +728,7 @@ static BOOL registerWhenGrowlIsReady = NO;
NSBundle *growlPrefPaneBundle;
BOOL success = NO;
growlPrefPaneBundle = [GrowlPathUtil growlPrefPaneBundle];
growlPrefPaneBundle = [GrowlPathUtilities growlPrefPaneBundle];
if (growlPrefPaneBundle) {
NSString *growlHelperAppPath = [growlPrefPaneBundle pathForResource:@"GrowlHelperApp"
@ -629,16 +754,18 @@ static BOOL registerWhenGrowlIsReady = NO;
NSString *regDictPath;
//Obtain a truly unique file name
regDictFileName = [[[[self _applicationNameForGrowlSearchingRegistrationDictionary:regDict] stringByAppendingString:@"-"] stringByAppendingString:[[NSProcessInfo processInfo] globallyUniqueString]] stringByAppendingPathExtension:GROWL_REG_DICT_EXTENSION];
if ([regDictFileName length] > NAME_MAX) {
CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault);
CFStringRef uuidString = CFUUIDCreateString(kCFAllocatorDefault, uuid);
CFRelease(uuid);
regDictFileName = [[NSString stringWithFormat:@"%@-%u-%@", [self _applicationNameForGrowlSearchingRegistrationDictionary:regDict], getpid(), (NSString *)uuidString] stringByAppendingPathExtension:GROWL_REG_DICT_EXTENSION];
CFRelease(uuidString);
if ([regDictFileName length] > NAME_MAX)
regDictFileName = [[regDictFileName substringToIndex:(NAME_MAX - [GROWL_REG_DICT_EXTENSION length])] stringByAppendingPathExtension:GROWL_REG_DICT_EXTENSION];
}
//make sure it's within pathname length constraints
regDictPath = [NSTemporaryDirectory() stringByAppendingPathComponent:regDictFileName];
if ([regDictPath length] > PATH_MAX) {
if ([regDictPath length] > PATH_MAX)
regDictPath = [[regDictPath substringToIndex:(PATH_MAX - [GROWL_REG_DICT_EXTENSION length])] stringByAppendingPathExtension:GROWL_REG_DICT_EXTENSION];
}
//Write the registration dictionary out to the temporary directory
NSData *plistData;
@ -647,7 +774,8 @@ static BOOL registerWhenGrowlIsReady = NO;
format:NSPropertyListBinaryFormat_v1_0
errorDescription:&error];
if (plistData) {
success = [plistData writeToFile:regDictPath atomically:NO];
if (![plistData writeToFile:regDictPath atomically:NO])
NSLog(@"GrowlApplicationBridge: Error writing registration dictionary at %@", regDictPath);
} else {
NSLog(@"GrowlApplicationBridge: Error writing registration dictionary at %@: %@", regDictPath, error);
NSLog(@"GrowlApplicationBridge: Registration dictionary follows\n%@", regDict);
@ -655,9 +783,8 @@ static BOOL registerWhenGrowlIsReady = NO;
}
regStatus = FSPathMakeRef((UInt8 *)[regDictPath fileSystemRepresentation], &regItemRef, NULL);
if (regStatus == noErr) {
if (regStatus == noErr)
passRegDict = YES;
}
}
spec.appRef = &appRef;

View File

@ -7,10 +7,10 @@
#ifdef __OBJC__
#define XSTR(x) (@x)
#define STRING NSString *
#define STRING_TYPE NSString *
#else
#define XSTR CFSTR
#define STRING CFStringRef
#define STRING_TYPE CFStringRef
#endif
/*! @header GrowlDefines.h
@ -48,6 +48,14 @@
* "SurfWriter Lite" are not.
*/
#define GROWL_APP_NAME XSTR("ApplicationName")
/*! @defined GROWL_APP_ID
* @abstract The bundle identifier of your application.
* @discussion The bundle identifier of your application. This key should
* be unique for your application while there may be several applications
* with the same GROWL_APP_NAME.
* This key is optional.
*/
#define GROWL_APP_ID XSTR("ApplicationId")
/*! @defined GROWL_APP_ICON
* @abstract The image data for your application's icon.
* @discussion Image data representing your application's icon. This may be
@ -74,6 +82,26 @@
* notification names.
*/
#define GROWL_NOTIFICATIONS_ALL XSTR("AllNotifications")
/*! @defined GROWL_NOTIFICATIONS_HUMAN_READABLE_DESCRIPTIONS
* @abstract A dictionary of human-readable names for your notifications.
* @discussion By default, the Growl UI will display notifications by the names given in GROWL_NOTIFICATIONS_ALL
* which correspond to the GROWL_NOTIFICATION_NAME. This dictionary specifies the human-readable name to display.
* The keys of the dictionary are GROWL_NOTIFICATION_NAME strings; the objects are the human-readable versions.
* For any GROWL_NOTIFICATION_NAME not specific in this dictionary, the GROWL_NOTIFICATION_NAME will be displayed.
*
* This key is optional.
*/
#define GROWL_NOTIFICATIONS_HUMAN_READABLE_NAMES XSTR("HumanReadableNames")
/*! @defined GROWL_NOTIFICATIONS_DESCRIPTIONS
* @abstract A dictionary of descriptions of _when_ each notification occurs
* @discussion This is an NSDictionary whose keys are GROWL_NOTIFICATION_NAME strings and whose objects are
* descriptions of _when_ each notification occurs, such as "You received a new mail message" or
* "A file finished downloading".
*
* This key is optional.
*/
#define GROWL_NOTIFICATIONS_DESCRIPTIONS XSTR("NotificationDescriptions")
/*! @defined GROWL_TICKET_VERSION
* @abstract The version of your registration ticket.
* @discussion Include this key in a ticket plist file that you put in your
@ -97,9 +125,10 @@
/*! @defined GROWL_NOTIFICATION_NAME
* @abstract The name of the notification.
* @discussion The name of the notification. This should be human-readable, as
* it's shown in the prefpane, in the list of notifications your application
* supports. */
* @discussion The name of the notification. Note that if you do not define
* GROWL_NOTIFICATIONS_HUMAN_READABLE_NAMES when registering your ticket originally this name
* will the one displayed within the Growl preference pane and should be human-readable.
*/
#define GROWL_NOTIFICATION_NAME XSTR("NotificationName")
/*! @defined GROWL_NOTIFICATION_TITLE
* @abstract The title to display in the notification.
@ -186,6 +215,15 @@
*/
#define GROWL_APP_PID XSTR("ApplicationPID")
/*! @defined GROWL_NOTIFICATION_PROGRESS
* @abstract If this key is set, it should contain a double value wrapped
* in a NSNumber which describes some sort of progress (from 0.0 to 100.0).
* If this is key is not set, no progress bar is shown.
*
* Optional. Not supported by all display plugins.
*/
#define GROWL_NOTIFICATION_PROGRESS XSTR("NotificationProgress")
// Notifications
#pragma mark Notifications
@ -304,4 +342,7 @@
*/
#define GROWL_REG_DICT_EXTENSION XSTR("growlRegDict")
#define GROWL_POSITION_PREFERENCE_KEY @"GrowlSelectedPosition"
#endif //ndef _GROWLDEFINES_H

View File

@ -10,6 +10,8 @@
#define _GROWL_GROWLDEFINESINTERNAL_H
#include <CoreFoundation/CoreFoundation.h>
#include <sys/types.h>
#include <unistd.h>
#ifdef __OBJC__
#define XSTR(x) (@x)
@ -154,11 +156,17 @@ struct GrowlNetworkNotification {
* @field sticky the sticky flag.
*/
struct GrowlNetworkNotificationFlags {
#ifdef __BIG_ENDIAN__
unsigned reserved: 12;
signed priority: 3;
unsigned sticky: 1;
#else
unsigned sticky: 1;
signed priority: 3;
unsigned reserved: 12;
#endif
} ATTRIBUTE_PACKED flags; //size = 16 (12 + 3 + 1)
/* In addition to being unsigned, the notification name length
* is in network byte order.
*/
@ -212,16 +220,40 @@ struct GrowlNetworkNotification {
*/
#define GROWL_APP_LOCATION XSTR("AppLocation")
#endif //ndef _GROWL_GROWLDEFINESINTERNAL_H
/*! @defined GROWL_REMOTE_ADDRESS
* @abstract The address of the host who sent this notification/registration.
* @discussion Contains an NSData with the address of the remote host who
* sent this notification/registration.
*/
#define GROWL_REMOTE_ADDRESS XSTR("RemoteAddress")
/*!
* @defined GROWL_PREFPANE_BUNDLE_IDENTIFIER
* @discussion The bundle identifier for the Growl preference pane.
*/
#define GROWL_PREFPANE_BUNDLE_IDENTIFIER XSTR("com.growl.prefpanel")
/*!
* @defined GROWL_HELPERAPP_BUNDLE_IDENTIFIER
* @discussion The bundle identifier for the Growl background application (GrowlHelperApp).
*/
#define GROWL_HELPERAPP_BUNDLE_IDENTIFIER XSTR("com.Growl.GrowlHelperApp")
/*!
* @defined GROWL_PREFPANE_NAME
* @discussion The file name of the Growl preference pane.
*/
#define GROWL_PREFPANE_NAME XSTR("Growl.prefPane")
#define PREFERENCE_PANES_SUBFOLDER_OF_LIBRARY XSTR("PreferencePanes")
#define PREFERENCE_PANE_EXTENSION XSTR("prefPane")
//plug-in bundle filename extensions
#define GROWL_PLUGIN_EXTENSION XSTR("growlPlugin")
#define GROWL_PATHWAY_EXTENSION XSTR("growlPathway")
#define GROWL_VIEW_EXTENSION XSTR("growlView")
#define GROWL_STYLE_EXTENSION XSTR("growlStyle")
/* --- These following macros are intended for plug-ins --- */
/*Since anything that needs the include guards won't be using these macros, we
* don't need the include guards here.
*/
#ifdef __OBJC__
/*! @function SYNCHRONIZE_GROWL_PREFS
* @abstract Synchronizes Growl prefs so they're up-to-date.
* @discussion This macro is intended for use by GrowlHelperApp and by
@ -236,14 +268,16 @@ struct GrowlNetworkNotification {
*/
#define UPDATE_GROWL_PREFS() do { \
SYNCHRONIZE_GROWL_PREFS(); \
NSNumber *pid = [[NSNumber alloc] initWithInt:[[NSProcessInfo processInfo] processIdentifier]];\
NSDictionary *userInfo = [[NSDictionary alloc] initWithObjectsAndKeys:\
pid, @"pid",\
nil];\
[pid release];\
[[NSDistributedNotificationCenter defaultCenter]\
postNotificationName:@"GrowlPreferencesChanged" object:@"GrowlUserDefaults" userInfo:userInfo];\
[userInfo release];\
CFStringRef _key = CFSTR("pid"); \
int pid = getpid(); \
CFNumberRef _value = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &pid); \
CFDictionaryRef userInfo = CFDictionaryCreate(kCFAllocatorDefault, (const void **)&_key, (const void **)&_value, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); \
CFRelease(_value); \
CFNotificationCenterPostNotification(CFNotificationCenterGetDistributedCenter(), \
CFSTR("GrowlPreferencesChanged"), \
CFSTR("GrowlUserDefaults"), \
userInfo, false); \
CFRelease(userInfo); \
} while(0)
/*! @function READ_GROWL_PREF_VALUE
@ -317,13 +351,7 @@ struct GrowlNetworkNotification {
* @param domain The bundle ID of the plug-in.
*/
#define WRITE_GROWL_PREF_BOOL(key, value, domain) do {\
CFBooleanRef boolValue; \
if (value) {\
boolValue = kCFBooleanTrue; \
} else {\
boolValue = kCFBooleanFalse; \
}\
WRITE_GROWL_PREF_VALUE(key, boolValue, domain); } while(0)
WRITE_GROWL_PREF_VALUE(key, value ? kCFBooleanTrue : kCFBooleanFalse, domain); } while(0)
/*! @function READ_GROWL_PREF_INT
* @abstract Reads the given integer from the plug-in's preferences.
@ -383,4 +411,20 @@ struct GrowlNetworkNotification {
WRITE_GROWL_PREF_VALUE(key, floatValue, domain); \
CFRelease(floatValue); } while(0)
#endif /* __OBJC__ */
/*! @defined GROWL_CLOSE_ALL_NOTIFICATIONS
* @abstract Notification to close all Growl notifications
* @discussion Should be posted to the default notification center when a close widget is option+clicked.
* All notifications should close in response.
*/
#define GROWL_CLOSE_ALL_NOTIFICATIONS XSTR("GrowlCloseAllNotifications")
#pragma mark Small utilities
/*!
* @defined FLOAT_EQ(x,y)
* @abstract Compares two floats.
*/
#define FLOAT_EQ(x,y) (((y - FLT_EPSILON) < x) && (x < (y + FLT_EPSILON)))
#endif //ndef _GROWL_GROWLDEFINESINTERNAL_H

View File

@ -1,30 +0,0 @@
//
// GrowlPathUtil.h
// Growl
//
// Created by Ingmar Stein on 17.04.05.
// Copyright 2005 The Growl Project. All rights reserved.
//
// This file is under the BSD License, refer to License.txt for details
#import <Cocoa/Cocoa.h>
@interface GrowlPathUtil : NSObject {
}
/*!
* @method growlPrefPaneBundle
* @abstract Returns the bundle containing Growl's PrefPane.
* @discussion Searches all installed PrefPanes for the Growl PrefPane.
* @result Returns an NSBundle if Growl's PrefPane is installed, nil otherwise
*/
+ (NSBundle *) growlPrefPaneBundle;
+ (NSBundle *) helperAppBundle;
+ (NSString *) growlSupportDir;
//the current screenshots directory: $HOME/Library/Application\ Support/Growl/Screenshots
+ (NSString *) screenshotsDirectory;
//returns e.g. @"Screenshot 1". you append your own pathname extension; it is guaranteed not to exist.
+ (NSString *) nextScreenshotName;
@end

View File

@ -1,171 +0,0 @@
//
// GrowlPathUtil.m
// Growl
//
// Created by Ingmar Stein on 17.04.05.
// Copyright 2005 The Growl Project. All rights reserved.
//
// This file is under the BSD License, refer to License.txt for details
#import "GrowlPathUtil.h"
#define HelperAppBundleIdentifier @"com.Growl.GrowlHelperApp"
#define GROWL_PREFPANE_BUNDLE_IDENTIFIER @"com.growl.prefpanel"
#define GROWL_PREFPANE_NAME @"Growl.prefPane"
#define PREFERENCE_PANES_SUBFOLDER_OF_LIBRARY @"PreferencePanes"
#define PREFERENCE_PANE_EXTENSION @"prefPane"
static NSBundle *helperAppBundle;
static NSBundle *prefPaneBundle;
@implementation GrowlPathUtil
+ (NSBundle *) growlPrefPaneBundle {
NSArray *librarySearchPaths;
NSString *path;
NSString *bundleIdentifier;
NSEnumerator *searchPathEnumerator;
NSBundle *bundle;
if (prefPaneBundle) {
return prefPaneBundle;
}
static const unsigned bundleIDComparisonFlags = NSCaseInsensitiveSearch | NSBackwardsSearch;
//Find Library directories in all domains except /System (as of Panther, that's ~/Library, /Library, and /Network/Library)
librarySearchPaths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSAllDomainsMask & ~NSSystemDomainMask, YES);
/*First up, we'll have a look for Growl.prefPane, and if it exists, check
* whether it is our prefPane.
*This is much faster than having to enumerate all preference panes, and
* can drop a significant amount of time off this code.
*/
searchPathEnumerator = [librarySearchPaths objectEnumerator];
while ((path = [searchPathEnumerator nextObject])) {
path = [path stringByAppendingPathComponent:PREFERENCE_PANES_SUBFOLDER_OF_LIBRARY];
path = [path stringByAppendingPathComponent:GROWL_PREFPANE_NAME];
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
bundle = [NSBundle bundleWithPath:path];
if (bundle) {
bundleIdentifier = [bundle bundleIdentifier];
if (bundleIdentifier && ([bundleIdentifier compare:GROWL_PREFPANE_BUNDLE_IDENTIFIER options:bundleIDComparisonFlags] == NSOrderedSame)) {
prefPaneBundle = bundle;
return prefPaneBundle;
}
}
}
}
/*Enumerate all installed preference panes, looking for the Growl prefpane
* bundle identifier and stopping when we find it.
*Note that we check the bundle identifier because we should not insist
* that the user not rename his preference pane files, although most users
* of course will not. If the user wants to mutilate the Info.plist file
* inside the bundle, he/she deserves to not have a working Growl
* installation.
*/
searchPathEnumerator = [librarySearchPaths objectEnumerator];
while ((path = [searchPathEnumerator nextObject])) {
NSString *bundlePath;
NSDirectoryEnumerator *bundleEnum;
path = [path stringByAppendingPathComponent:PREFERENCE_PANES_SUBFOLDER_OF_LIBRARY];
bundleEnum = [[NSFileManager defaultManager] enumeratorAtPath:path];
while ((bundlePath = [bundleEnum nextObject])) {
if ([[bundlePath pathExtension] isEqualToString:PREFERENCE_PANE_EXTENSION]) {
bundle = [NSBundle bundleWithPath:[path stringByAppendingPathComponent:bundlePath]];
if (bundle) {
bundleIdentifier = [bundle bundleIdentifier];
if (bundleIdentifier && ([bundleIdentifier compare:GROWL_PREFPANE_BUNDLE_IDENTIFIER options:bundleIDComparisonFlags] == NSOrderedSame)) {
prefPaneBundle = bundle;
return prefPaneBundle;
}
}
[bundleEnum skipDescendents];
}
}
}
return nil;
}
#pragma mark -
#pragma mark Important file-system objects
+ (NSBundle *) helperAppBundle {
if (!helperAppBundle) {
NSBundle *bundle = [NSBundle mainBundle];
if ([[bundle bundleIdentifier] isEqualToString:HelperAppBundleIdentifier]) {
//we are running in GHA.
helperAppBundle = bundle;
} else {
//look in the prefpane bundle.
bundle = [NSBundle bundleForClass:[GrowlPathUtil class]];
if (![[bundle bundleIdentifier] isEqualToString:GROWL_PREFPANE_BUNDLE_IDENTIFIER]) {
bundle = [GrowlPathUtil growlPrefPaneBundle];
}
NSString *helperAppPath = [bundle pathForResource:@"GrowlHelperApp" ofType:@"app"];
helperAppBundle = [NSBundle bundleWithPath:helperAppPath];
}
}
return helperAppBundle;
}
+ (NSString *) growlSupportDir {
NSString *supportDir;
NSArray *searchPath = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, /* expandTilde */ YES);
supportDir = [searchPath objectAtIndex:0U];
supportDir = [supportDir stringByAppendingPathComponent:@"Application Support/Growl"];
return supportDir;
}
#pragma mark -
+ (NSString *) screenshotsDirectory {
NSString *path = [NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/Growl/Screenshots"];
[[NSFileManager defaultManager] createDirectoryAtPath:path
attributes:nil];
return path;
}
+ (NSString *) nextScreenshotName {
NSFileManager *mgr = [NSFileManager defaultManager];
NSString *directory = [GrowlPathUtil screenshotsDirectory];
NSString *filename = nil;
NSArray *origContents = [mgr directoryContentsAtPath:directory];
NSMutableSet *directoryContents = [[NSMutableSet alloc] initWithCapacity:[origContents count]];
NSEnumerator *filesEnum = [origContents objectEnumerator];
NSString *existingFilename;
while ((existingFilename = [filesEnum nextObject])) {
existingFilename = [directory stringByAppendingPathComponent:[existingFilename stringByDeletingPathExtension]];
[directoryContents addObject:existingFilename];
}
unsigned long i;
for (i = 1UL; i < ULONG_MAX; ++i) {
[filename release];
filename = [[NSString alloc] initWithFormat:@"Screenshot %lu", i];
NSString *path = [directory stringByAppendingPathComponent:filename];
if (![directoryContents containsObject:path]) {
break;
}
}
[directoryContents release];
return [filename autorelease];
}
@end

View File

@ -0,0 +1,171 @@
//
// GrowlPathUtilities.h
// Growl
//
// Created by Ingmar Stein on 17.04.05.
// Copyright 2005-2006 The Growl Project. All rights reserved.
//
// This file is under the BSD License, refer to License.txt for details
/*we can't typedef the enum, because then NSSearchPathDirectory constants
* cannot be used in GrowlSearchPathDirectory arguments/variables without a
* compiler warning (because NSSearchPathDirectory and GrowlSearchPathDirectory
* would then be different types).
*/
typedef int GrowlSearchPathDirectory;
enum {
//Library/Application\ Support/Growl
GrowlSupportDirectory = 0x10000,
//all other directory constants refer to subdirectories of Growl Support.
GrowlScreenshotsDirectory,
GrowlTicketsDirectory,
GrowlPluginsDirectory,
};
typedef NSSearchPathDomainMask GrowlSearchPathDomainMask; //consistency
@interface GrowlPathUtilities : NSObject {
}
#pragma mark Bundles
/*! @method growlPrefPaneBundle
* @abstract Returns the Growl preference pane bundle.
* @discussion First, attempts to retrieve the bundle for a running
* GrowlHelperApp process using <code>runningHelperAppBundle</code>, and if
* that was successful, returns the .prefpane bundle that contains it (if any).
* Then, if that failed, searches all installed preference panes for the Growl
* preference pane.
* @result The <code>NSBundle</code> for the Growl preference pane if it is
* installed; <code>nil</code> otherwise.
*/
+ (NSBundle *) growlPrefPaneBundle;
/*! @method helperAppBundle
* @abstract Returns the GrowlHelperApp bundle.
* @discussion First, attempts to retrieve the bundle for a running
* GrowlHelperApp process using <code>runningHelperAppBundle</code>, and
* returns that if it was successful.
* Then, if it wasn't, searches for a Growl preference pane, and, if one is
* installed, returns the GrowlHelperApp bundle inside it.
* @result The <code>NSBundle</code> for GrowlHelperApp if it is present;
* <code>nil</code> otherwise.
*/
+ (NSBundle *) helperAppBundle;
/*! @method runningHelperAppBundle
* @abstract Returns the bundle for the running GrowlHelperApp process.
* @discussion If GrowlHelperApp is running, returns an NSBundle for the .app
* bundle it was loaded from.
* If GrowlHelperApp is not running, returns <code>nil</code>.
* @result The <code>NSBundle</code> for GrowlHelperApp if it is running;
* <code>nil</code> otherwise.
*/
+ (NSBundle *) runningHelperAppBundle;
#pragma mark Directories
/*! @method searchPathForDirectory:inDomains:mustBeWritable:
* @abstract Returns an array of absolute paths to a given directory.
* @discussion This method returns an array of all the directories of a given
* type that exist (and, if <code>flag</code> is <code>YES</code>, are
* writable). If no directories match this criteria, a valid (but empty)
* array is returned.
*
* Unlike the <code>NSSearchPathForDirectoriesInDomains</code> function in
* Foundation, this method does not allow you to specify whether tildes are
* expanded: they will always be expanded.
* @result An array of zero or more absolute paths.
*/
+ (NSArray *) searchPathForDirectory:(GrowlSearchPathDirectory) directory inDomains:(GrowlSearchPathDomainMask) domainMask mustBeWritable:(BOOL)flag;
/*! @method searchPathForDirectory:inDomains:
* @abstract Returns an array of absolute paths to a given directory.
* @discussion This method returns an array of all the directories of a given
* type that exist. They need not be writable.
*
* Unlike the <code>NSSearchPathForDirectoriesInDomains</code> function in
* Foundation, this method does not allow you to specify whether tildes are
* expanded: they will always be expanded.
* @result An array of zero or more absolute paths.
*/
+ (NSArray *) searchPathForDirectory:(GrowlSearchPathDirectory) directory inDomains:(GrowlSearchPathDomainMask) domainMask;
/*! @method growlSupportDirectory
* @abstract Returns the path for Growl's folder inside Application Support.
* @discussion This method creates the folder if it does not already exist.
* @result The path to Growl's support directory.
*/
+ (NSString *) growlSupportDirectory;
/*! @method screenshotsDirectory
* @abstract Returns the directory where screenshots are to be stored.
* @discussion The default location of this directory is
* $HOME/Library/Application\ Support/Growl/Screenshots. This method creates
* the folder if it does not already exist.
* @result The absolute path to the screenshot directory.
*/
+ (NSString *) screenshotsDirectory;
/*! @method ticketsDirectory
* @abstract Returns the directory where tickets are to be saved.
* @discussion The default location of this directory is
* $HOME/Library/Application\ Support/Growl/Tickets. This method creates
* the folder if it does not already exist.
* @result The absolute path to the ticket directory.
*/
+ (NSString *) ticketsDirectory;
#pragma mark Screenshot names
/*! @method nextScreenshotName
* @abstract Returns the name you should use for the next screenshot.
* @discussion Names returned by this method are currently in the format
* 'Screenshot N', where N starts at 1 and continues indefinitely. Note the
* lack of a filename extension: you append it yourself.
*
* The name returned by this method is guaranteed to not exist with any
* filename extension. This is intentional: it would be confusing for the
* user if the fifth screenshot were assigned the name 'Screenshot 1' simply
* because the previous four screenshots had a different filename extension.
*
* Calling this method is the same as calling
* <code>nextScreenshotNameInDirectory:</code> with a directory of
* <code>nil</code>.
* @result A valid, non-existing, serial filename for a screenshot.
*/
+ (NSString *) nextScreenshotName;
/*! @method nextScreenshotNameInDirectory:
* @abstract Returns the name you should use for the next screenshot in a directory.
* @discussion Names returned by this method are currently in the format
* 'Screenshot N', where N starts at 1 and continues indefinitely. Note the
* lack of a filename extension: you append it yourself. Note also that the
* directory is not included as a prefix on the result.
*
* The name returned by this method is guaranteed to not exist in the given
* directory with any filename extension. This is intentional: it would be
* confusing for the user if the fifth screenshot were assigned the name
* 'Screenshot 1' simply because the previous four screenshots had a
* different filename extension.
* @result A valid, non-existing, serial filename for a screenshot.
*/
+ (NSString *) nextScreenshotNameInDirectory:(NSString *) dirName;
#pragma mark Tickets
/*! @method defaultSavePathForTicketWithApplicationName:
* @abstract Returns an absolute path that can be used for saving a ticket.
* @discussion When called with an application name, ".ticket" is appended to
* it, and the result is appended to the absolute path to the ticket
* directory. When called with <code>nil</code>, the ticket directory itself
* is returned.
*
* For the purpose of this method, 'the ticket directory' refers to the first
* writable directory returned by
* <code>+searchPathForDirectory:inDomains:</code>. If there is no writable
* directory, this method returns <code>nil</code>.
* @param The application name for the ticket, or <code>nil</code>.
* @result The absolute path to a ticket file, or the ticket directory where a
* ticket file can be saved.
*/
+ (NSString *) defaultSavePathForTicketWithApplicationName:(NSString *) appName;
@end

View File

@ -0,0 +1,381 @@
//
// GrowlPathUtil.m
// Growl
//
// Created by Ingmar Stein on 17.04.05.
// Copyright 2005-2006 The Growl Project. All rights reserved.
//
// This file is under the BSD License, refer to License.txt for details
#import <Cocoa/Cocoa.h>
#import "GrowlPathUtilities.h"
#import "GrowlPreferencesController.h"
#import "GrowlTicketController.h"
#import "GrowlDefinesInternal.h"
static NSBundle *helperAppBundle;
static NSBundle *prefPaneBundle;
#define NAME_OF_SCREENSHOTS_DIRECTORY @"Screenshots"
#define NAME_OF_TICKETS_DIRECTORY @"Tickets"
#define NAME_OF_PLUGINS_DIRECTORY @"Plugins"
@implementation GrowlPathUtilities
#pragma mark Bundles
//Searches the process list (as yielded by GetNextProcess) for a process with the given bundle identifier.
//Returns the oldest matching process.
+ (NSBundle *) bundleForProcessWithBundleIdentifier:(NSString *)identifier
{
restart:;
OSStatus err;
NSBundle *bundle = nil;
struct ProcessSerialNumber psn = { 0, 0 };
UInt32 oldestProcessLaunchDate = UINT_MAX;
while ((err = GetNextProcess(&psn)) == noErr) {
struct ProcessInfoRec info = { .processInfoLength = sizeof(struct ProcessInfoRec) };
err = GetProcessInformation(&psn, &info);
if (err == noErr) {
//Compare the launch dates first, since it's cheaper than comparing bundle IDs.
if (info.processLaunchDate < oldestProcessLaunchDate) {
//This one is older (fewer ticks since startup), so this is our current prospect to be the result.
NSDictionary *dict = (NSDictionary *)ProcessInformationCopyDictionary(&psn, kProcessDictionaryIncludeAllInformationMask);
if (dict) {
pid_t pid = 0;
GetProcessPID(&psn, &pid);
if ([[dict objectForKey:(NSString *)kCFBundleIdentifierKey] isEqualToString:identifier]) {
NSString *bundlePath = [dict objectForKey:@"BundlePath"];
if (bundlePath) {
bundle = [NSBundle bundleWithPath:bundlePath];
oldestProcessLaunchDate = info.processLaunchDate;
}
}
[dict release];
} else {
//ProcessInformationCopyDictionary returning NULL probably means that the process disappeared out from under us (i.e., exited) in between GetProcessInformation and ProcessInformationCopyDictionary. Start over.
goto restart;
}
}
} else {
if (err != noErr) {
//Unexpected failure of GetProcessInformation (Process Manager got confused?). Assume severe breakage and bail.
NSLog(@"Couldn't get information about process %lu,%lu: GetProcessInformation returned %i/%s", psn.highLongOfPSN, psn.lowLongOfPSN, err, GetMacOSStatusCommentString(err));
err = noErr; //So our NSLog for GetNextProcess doesn't complain. (I wish I had Python's while..else block.)
break;
} else {
//Process disappeared out from under us (i.e., exited) in between GetNextProcess and GetProcessInformation. Start over.
goto restart;
}
}
}
if (err != procNotFound) {
NSLog(@"%s: GetNextProcess returned %i/%s", __PRETTY_FUNCTION__, err, GetMacOSStatusCommentString(err));
}
return bundle;
}
//Obtains the bundle for the active GrowlHelperApp process. Returns nil if there is no such process.
+ (NSBundle *) runningHelperAppBundle {
return [self bundleForProcessWithBundleIdentifier:GROWL_HELPERAPP_BUNDLE_IDENTIFIER];
}
+ (NSBundle *) growlPrefPaneBundle {
NSArray *librarySearchPaths;
NSString *path;
NSString *bundleIdentifier;
NSEnumerator *searchPathEnumerator;
NSBundle *bundle;
if (prefPaneBundle)
return prefPaneBundle;
prefPaneBundle = [NSBundle bundleWithIdentifier:GROWL_PREFPANE_BUNDLE_IDENTIFIER];
if (prefPaneBundle)
return prefPaneBundle;
//If GHA is running, the prefpane bundle is the bundle that contains it.
NSBundle *runningHelperAppBundle = [self runningHelperAppBundle];
NSString *runningHelperAppBundlePath = [runningHelperAppBundle bundlePath];
//GHA in Growl.prefPane/Contents/Resources/
NSString *possiblePrefPaneBundlePath1 = [runningHelperAppBundlePath stringByDeletingLastPathComponent];
//GHA in Growl.prefPane/ (hypothetical)
NSString *possiblePrefPaneBundlePath2 = [[possiblePrefPaneBundlePath1 stringByDeletingLastPathComponent] stringByDeletingLastPathComponent];
if ([[[possiblePrefPaneBundlePath1 pathExtension] lowercaseString] isEqualToString:@"prefpane"]) {
prefPaneBundle = [NSBundle bundleWithPath:possiblePrefPaneBundlePath1];
if (prefPaneBundle)
return prefPaneBundle;
}
if ([[[possiblePrefPaneBundlePath2 pathExtension] lowercaseString] isEqualToString:@"prefpane"]) {
prefPaneBundle = [NSBundle bundleWithPath:possiblePrefPaneBundlePath2];
if (prefPaneBundle)
return prefPaneBundle;
}
static const unsigned bundleIDComparisonFlags = NSCaseInsensitiveSearch | NSBackwardsSearch;
NSFileManager *fileManager = [NSFileManager defaultManager];
//Find Library directories in all domains except /System (as of Panther, that's ~/Library, /Library, and /Network/Library)
librarySearchPaths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSAllDomainsMask & ~NSSystemDomainMask, YES);
/*First up, we'll look for Growl.prefPane, and if it exists, check whether
* it is our prefPane.
*This is much faster than having to enumerate all preference panes, and
* can drop a significant amount of time off this code.
*/
searchPathEnumerator = [librarySearchPaths objectEnumerator];
while ((path = [searchPathEnumerator nextObject])) {
path = [path stringByAppendingPathComponent:PREFERENCE_PANES_SUBFOLDER_OF_LIBRARY];
path = [path stringByAppendingPathComponent:GROWL_PREFPANE_NAME];
if ([fileManager fileExistsAtPath:path]) {
bundle = [NSBundle bundleWithPath:path];
if (bundle) {
bundleIdentifier = [bundle bundleIdentifier];
if (bundleIdentifier && ([bundleIdentifier compare:GROWL_PREFPANE_BUNDLE_IDENTIFIER options:bundleIDComparisonFlags] == NSOrderedSame)) {
prefPaneBundle = bundle;
return prefPaneBundle;
}
}
}
}
/*Enumerate all installed preference panes, looking for the Growl prefpane
* bundle identifier and stopping when we find it.
*Note that we check the bundle identifier because we should not insist
* that the user not rename his preference pane files, although most users
* of course will not. If the user wants to mutilate the Info.plist file
* inside the bundle, he/she deserves to not have a working Growl
* installation.
*/
searchPathEnumerator = [librarySearchPaths objectEnumerator];
while ((path = [searchPathEnumerator nextObject])) {
NSString *bundlePath;
NSDirectoryEnumerator *bundleEnum;
path = [path stringByAppendingPathComponent:PREFERENCE_PANES_SUBFOLDER_OF_LIBRARY];
bundleEnum = [fileManager enumeratorAtPath:path];
while ((bundlePath = [bundleEnum nextObject])) {
if ([[bundlePath pathExtension] isEqualToString:PREFERENCE_PANE_EXTENSION]) {
bundle = [NSBundle bundleWithPath:[path stringByAppendingPathComponent:bundlePath]];
if (bundle) {
bundleIdentifier = [bundle bundleIdentifier];
if (bundleIdentifier && ([bundleIdentifier compare:GROWL_PREFPANE_BUNDLE_IDENTIFIER options:bundleIDComparisonFlags] == NSOrderedSame)) {
prefPaneBundle = bundle;
return prefPaneBundle;
}
}
[bundleEnum skipDescendents];
}
}
}
return nil;
}
+ (NSBundle *) helperAppBundle {
if (!helperAppBundle) {
helperAppBundle = [self runningHelperAppBundle];
if (!helperAppBundle) {
//look in the prefpane bundle.
NSBundle *bundle = [GrowlPathUtilities growlPrefPaneBundle];
NSString *helperAppPath = [bundle pathForResource:@"GrowlHelperApp" ofType:@"app"];
helperAppBundle = [NSBundle bundleWithPath:helperAppPath];
}
}
return helperAppBundle;
}
#pragma mark -
#pragma mark Directories
+ (NSArray *) searchPathForDirectory:(GrowlSearchPathDirectory) directory inDomains:(GrowlSearchPathDomainMask) domainMask mustBeWritable:(BOOL)flag {
if (directory < GrowlSupportDirectory) {
NSArray *searchPath = NSSearchPathForDirectoriesInDomains(directory, domainMask, /*expandTilde*/ YES);
if (!flag)
return searchPath;
else {
//flag is not NO: exclude non-writable directories.
NSMutableArray *result = [NSMutableArray arrayWithCapacity:[searchPath count]];
NSFileManager *mgr = [NSFileManager defaultManager];
NSEnumerator *searchPathEnum = [searchPath objectEnumerator];
NSString *dir;
while ((dir = [searchPathEnum nextObject])) {
if ([mgr isWritableFileAtPath:dir])
[result addObject:dir];
}
return result;
}
} else {
//determine what to append to each Application Support folder.
NSString *subpath = nil;
switch (directory) {
case GrowlSupportDirectory:
//do nothing.
break;
case GrowlScreenshotsDirectory:
subpath = NAME_OF_SCREENSHOTS_DIRECTORY;
break;
case GrowlTicketsDirectory:
subpath = NAME_OF_TICKETS_DIRECTORY;
break;
case GrowlPluginsDirectory:
subpath = NAME_OF_PLUGINS_DIRECTORY;
break;
default:
NSLog(@"ERROR: GrowlPathUtil was asked for directory 0x%x, but it doesn't know what directory that is. Please tell the Growl developers.", directory);
return nil;
}
if (subpath)
subpath = [@"Application Support/Growl" stringByAppendingPathComponent:subpath];
else
subpath = @"Application Support/Growl";
/*get the search path, and append the subpath to all the items therein.
*exclude results that don't exist.
*/
NSFileManager *mgr = [NSFileManager defaultManager];
BOOL isDir = NO;
NSArray *searchPath = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, domainMask, /*expandTilde*/ YES);
NSMutableArray *mSearchPath = [NSMutableArray arrayWithCapacity:[searchPath count]];
NSEnumerator *searchPathEnum = [searchPath objectEnumerator];
NSString *path;
while ((path = [searchPathEnum nextObject])) {
path = [path stringByAppendingPathComponent:subpath];
if ([mgr fileExistsAtPath:path isDirectory:&isDir] && isDir)
[mSearchPath addObject:path];
}
return mSearchPath;
}
}
+ (NSArray *) searchPathForDirectory:(GrowlSearchPathDirectory) directory inDomains:(GrowlSearchPathDomainMask) domainMask {
//NO to emulate the default NSSearchPathForDirectoriesInDomains behaviour.
return [self searchPathForDirectory:directory inDomains:domainMask mustBeWritable:NO];
}
+ (NSString *) growlSupportDirectory {
NSArray *searchPath = [self searchPathForDirectory:GrowlSupportDirectory inDomains:NSUserDomainMask mustBeWritable:YES];
if ([searchPath count])
return [searchPath objectAtIndex:0U];
else {
NSString *path = nil;
//if this doesn't return any writable directories, path will still be nil.
searchPath = [self searchPathForDirectory:NSLibraryDirectory inDomains:NSAllDomainsMask mustBeWritable:YES];
if ([searchPath count]) {
path = [[searchPath objectAtIndex:0U] stringByAppendingPathComponent:@"Application Support/Growl"];
//try to create it. if that doesn't work, don't return it. return nil instead.
if (![[NSFileManager defaultManager] createDirectoryAtPath:path attributes:nil])
path = nil;
}
return path;
}
}
+ (NSString *) screenshotsDirectory {
NSArray *searchPath = [self searchPathForDirectory:GrowlScreenshotsDirectory inDomains:NSAllDomainsMask mustBeWritable:YES];
if ([searchPath count])
return [searchPath objectAtIndex:0U];
else {
NSString *path = nil;
//if this doesn't return any writable directories, path will still be nil.
path = [self growlSupportDirectory];
if (path) {
path = [path stringByAppendingPathComponent:NAME_OF_SCREENSHOTS_DIRECTORY];
//try to create it. if that doesn't work, don't return it. return nil instead.
if (![[NSFileManager defaultManager] createDirectoryAtPath:path attributes:nil])
path = nil;
}
return path;
}
}
+ (NSString *) ticketsDirectory {
NSArray *searchPath = [self searchPathForDirectory:GrowlTicketsDirectory inDomains:NSAllDomainsMask mustBeWritable:YES];
if ([searchPath count])
return [searchPath objectAtIndex:0U];
else {
NSString *path = nil;
//if this doesn't return any writable directories, path will still be nil.
path = [self growlSupportDirectory];
if (path) {
path = [path stringByAppendingPathComponent:NAME_OF_TICKETS_DIRECTORY];
//try to create it. if that doesn't work, don't return it. return nil instead.
if (![[NSFileManager defaultManager] createDirectoryAtPath:path attributes:nil])
path = nil;
}
return path;
}
}
#pragma mark -
#pragma mark Screenshot names
+ (NSString *) nextScreenshotName {
return [self nextScreenshotNameInDirectory:nil];
}
+ (NSString *) nextScreenshotNameInDirectory:(NSString *) directory {
NSFileManager *mgr = [NSFileManager defaultManager];
if (!directory)
directory = [GrowlPathUtilities screenshotsDirectory];
//build a set of all the files in the directory, without their filename extensions.
NSArray *origContents = [mgr directoryContentsAtPath:directory];
NSMutableSet *directoryContents = [[NSMutableSet alloc] initWithCapacity:[origContents count]];
NSEnumerator *filesEnum = [origContents objectEnumerator];
NSString *existingFilename;
while ((existingFilename = [filesEnum nextObject]))
[directoryContents addObject:[existingFilename stringByDeletingPathExtension]];
//look for a filename that doesn't exist (with any extension) in the directory.
NSString *filename = nil;
unsigned long long i;
for (i = 1ULL; i < ULLONG_MAX; ++i) {
[filename release];
filename = [[NSString alloc] initWithFormat:@"Screenshot %llu", i];
if (![directoryContents containsObject:filename])
break;
}
[directoryContents release];
return [filename autorelease];
}
#pragma mark -
#pragma mark Tickets
+ (NSString *) defaultSavePathForTicketWithApplicationName:(NSString *) appName {
return [[self ticketsDirectory] stringByAppendingPathComponent:[appName stringByAppendingPathExtension:GROWL_PATHEXTENSION_TICKET]];
}
@end

View File

@ -3,7 +3,7 @@
// Growl
//
// Created by Ingmar Stein on 15.11.04.
// Copyright 2004-2005 The Growl Project. All rights reserved.
// Copyright 2004-2006 The Growl Project. All rights reserved.
//
// This file is under the BSD License, refer to License.txt for details
@ -15,10 +15,9 @@
- (bycopy NSString *) growlVersion;
@end
@class GrowlApplicationController;
@interface GrowlPathway : NSObject <GrowlNotificationProtocol> {
}
- (void) registerApplicationWithDictionary:(NSDictionary *)dict;
- (void) postNotificationWithDictionary:(NSDictionary *)dict;
@end

View File

@ -0,0 +1,126 @@
//
// GrowlPreferencesController.h
// Growl
//
// Created by Nelson Elhage on 8/24/04.
// Renamed from GrowlPreferences.h by Mac-arena the Bored Zo on 2005-06-27.
// Copyright 2004-2006 The Growl Project. All rights reserved.
//
// This file is under the BSD License, refer to License.txt for details
#ifndef GROWL_PREFERENCES_CONTROLLER_H
#define GROWL_PREFERENCES_CONTROLLER_H
#ifdef __OBJC__
#define XSTR(x) (@x)
#else
#define XSTR CFSTR
#endif
#define GrowlPreferencesChanged XSTR("GrowlPreferencesChanged")
#define GrowlPreview XSTR("GrowlPreview")
#define GrowlDisplayPluginKey XSTR("GrowlDisplayPluginName")
#define GrowlUserDefaultsKey XSTR("GrowlUserDefaults")
#define GrowlStartServerKey XSTR("GrowlStartServer")
#define GrowlRemoteRegistrationKey XSTR("GrowlRemoteRegistration")
#define GrowlEnableForwardKey XSTR("GrowlEnableForward")
#define GrowlForwardDestinationsKey XSTR("GrowlForwardDestinations")
#define GrowlUDPPortKey XSTR("GrowlUDPPort")
#define GrowlTCPPortKey XSTR("GrowlTCPPort")
#define GrowlUpdateCheckKey XSTR("GrowlUpdateCheck")
#define LastUpdateCheckKey XSTR("LastUpdateCheck")
#define GrowlLoggingEnabledKey XSTR("GrowlLoggingEnabled")
#define GrowlLogTypeKey XSTR("GrowlLogType")
#define GrowlCustomHistKey1 XSTR("Custom log history 1")
#define GrowlCustomHistKey2 XSTR("Custom log history 2")
#define GrowlCustomHistKey3 XSTR("Custom log history 3")
#define GrowlMenuExtraKey XSTR("GrowlMenuExtra")
#define GrowlSquelchModeKey XSTR("GrowlSquelchMode")
#define LastKnownVersionKey XSTR("LastKnownVersion")
#define GrowlStickyWhenAwayKey XSTR("StickyWhenAway")
#define GrowlStickyIdleThresholdKey XSTR("IdleThreshold")
CFTypeRef GrowlPreferencesController_objectForKey(CFTypeRef key);
int GrowlPreferencesController_integerForKey(CFTypeRef key);
Boolean GrowlPreferencesController_boolForKey(CFTypeRef key);
#ifdef __OBJC__
#import "GrowlAbstractSingletonObject.h"
@interface GrowlPreferencesController : GrowlAbstractSingletonObject {
}
+ (GrowlPreferencesController *) sharedController;
- (void) registerDefaults:(NSDictionary *)inDefaults;
- (id) objectForKey:(NSString *)key;
- (void) setObject:(id)object forKey:(NSString *)key;
- (BOOL) boolForKey:(NSString*) key;
- (void) setBool:(BOOL)value forKey:(NSString *)key;
- (int) integerForKey:(NSString *)key;
- (void) setInteger:(int)value forKey:(NSString *)key;
- (void) synchronize;
- (BOOL) shouldStartGrowlAtLogin;
- (void) setShouldStartGrowlAtLogin:(BOOL)flag;
- (void) setStartAtLogin:(NSString *)path enabled:(BOOL)flag;
- (BOOL) isRunning:(NSString *)theBundleIdentifier;
- (BOOL) isGrowlRunning;
- (void) setGrowlRunning:(BOOL)flag noMatterWhat:(BOOL)nmw;
- (void) launchGrowl:(BOOL)noMatterWhat;
- (void) terminateGrowl;
#pragma mark GrowlMenu running state
- (void) enableGrowlMenu;
- (void) disableGrowlMenu;
#pragma mark -
//Simplified accessors
#pragma mark UI
- (BOOL) isBackgroundUpdateCheckEnabled;
- (void) setIsBackgroundUpdateCheckEnabled:(BOOL)flag;
- (NSString *) defaultDisplayPluginName;
- (void) setDefaultDisplayPluginName:(NSString *)name;
- (BOOL) squelchMode;
- (void) setSquelchMode:(BOOL)flag;
- (BOOL) stickyWhenAway;
- (void) setStickyWhenAway:(BOOL)flag;
- (NSNumber*) idleThreshold;
- (void) setIdleThreshold:(NSNumber*)value;
#pragma mark GrowlMenu methods
- (BOOL) isGrowlMenuEnabled;
- (void) setGrowlMenuEnabled:(BOOL)state;
#pragma mark "Network" tab pane
- (BOOL) isGrowlServerEnabled;
- (void) setGrowlServerEnabled:(BOOL)enabled;
- (BOOL) isRemoteRegistrationAllowed;
- (void) setRemoteRegistrationAllowed:(BOOL)flag;
- (BOOL) isForwardingEnabled;
- (void) setForwardingEnabled:(BOOL)enabled;
- (NSString *) remotePassword;
- (void) setRemotePassword:(NSString *)value;
- (int) UDPPort;
- (void) setUDPPort:(int)value;
@end
#endif
#endif

View File

@ -0,0 +1,29 @@
//
// GrowlTicketController.h
// Growl
//
// Created by Mac-arena the Bored Zo on 2005-06-08.
// Copyright 2005-2006 Mac-arena the Bored Zo. All rights reserved.
//
#import "GrowlAbstractSingletonObject.h"
#define GROWL_PATHEXTENSION_TICKET @"growlTicket"
@class GrowlApplicationTicket;
@interface GrowlTicketController: GrowlAbstractSingletonObject
{
NSMutableDictionary *ticketsByApplicationName;
}
+ (id) sharedController;
- (NSDictionary *) allSavedTickets;
- (GrowlApplicationTicket *) ticketForApplicationName:(NSString *) appName;
- (void) addTicket:(GrowlApplicationTicket *) newTicket;
- (void) removeTicketForApplicationName:(NSString *)appName;
- (void) loadAllSavedTickets;
@end

View File

@ -48,12 +48,13 @@ FORCE_USE_PIC = 1
CMSRCS = \
GrowlApplicationBridge.m \
GrowlPathUtil.m \
NSURLAdditions.m \
GrowlPathUtilities.m \
$(NULL)
CSRCS = \
CFGrowlAdditions.c \
CFMutableDictionaryAdditions.c \
CFURLAdditions.c \
$(NULL)
include $(topsrcdir)/config/rules.mk

View File

@ -1,23 +0,0 @@
//
// NSURLAdditions.h
// Growl
//
// Created by Karl Adam on Fri May 28 2004.
// Copyright 2004-2005 The Growl Project. All rights reserved.
//
// This file is under the BSD License, refer to License.txt for details
#import <Foundation/Foundation.h>
@interface NSURL (GrowlAdditions)
//'alias' as in the Alias Manager.
+ (NSURL *) fileURLWithAliasData:(NSData *)aliasData;
- (NSData *) aliasData;
//these are the type of external representations used by Dock.app.
+ (NSURL *) fileURLWithDockDescription:(NSDictionary *)dict;
//-dockDescription returns nil for non-file: URLs.
- (NSDictionary *) dockDescription;
@end

View File

@ -1,132 +0,0 @@
//
// NSURLAdditions.m
// Growl
//
// Created by Karl Adam on Fri May 28 2004.
// Copyright 2004-2005 The Growl Project. All rights reserved.
//
// This file is under the BSD License, refer to License.txt for details
#import "NSURLAdditions.h"
#define _CFURLAliasDataKey @"_CFURLAliasData"
#define _CFURLStringKey @"_CFURLString"
#define _CFURLStringTypeKey @"_CFURLStringType"
@implementation NSURL (GrowlAdditions)
//'alias' as in the Alias Manager.
+ (NSURL *) fileURLWithAliasData:(NSData *)aliasData {
NSParameterAssert(aliasData != nil);
NSURL *url = nil;
AliasHandle alias = NULL;
OSStatus err = PtrToHand([aliasData bytes], (Handle *)&alias, [aliasData length]);
if (err != noErr) {
NSLog(@"in +[NSURL(GrowlAdditions) fileURLWithAliasData:]: Could not allocate an alias handle from %u bytes of alias data (data follows) because PtrToHand returned %li\n%@", [aliasData length], aliasData, (long)err);
} else {
NSString *path = nil;
/*
* FSResolveAlias mounts disk images or network shares to resolve
* aliases, thus we resort to FSCopyAliasInfo.
*/
err = FSCopyAliasInfo(alias,
/* targetName */ NULL,
/* volumeName */ NULL,
(CFStringRef *)&path,
/* whichInfo */ NULL,
/* info */ NULL);
if (err != noErr) {
if (err != fnfErr) { //ignore file-not-found; it's harmless
NSLog(@"in +[NSURL(GrowlAdditions) fileURLWithAliasData:]: Could not resolve alias (alias data follows) because FSResolveAlias returned %li - will try path\n%@", (long)err, aliasData);
}
} else if (path) {
url = [NSURL fileURLWithPath:path];
} else {
NSLog(@"in +[NSURL(GrowlAdditions) fileURLWithAliasData:]: FSCopyAliasInfo returned a nil path");
}
}
return url;
}
- (NSData *) aliasData {
//return nil for non-file: URLs.
if ([[self scheme] caseInsensitiveCompare:@"file"] != NSOrderedSame)
return nil;
NSData *aliasData = nil;
FSRef fsref;
if (CFURLGetFSRef((CFURLRef)self, &fsref)) {
AliasHandle alias = NULL;
OSStatus err = FSNewAlias(/*fromFile*/ NULL, &fsref, &alias);
if (err != noErr) {
NSLog(@"in -[NSURL(GrowlAdditions) dockDescription]: FSNewAlias for %@ returned %li", self, (long)err);
} else {
HLock((Handle)alias);
aliasData = [NSData dataWithBytes:*alias length:GetHandleSize((Handle)alias)];
HUnlock((Handle)alias);
DisposeHandle((Handle)alias);
}
}
return aliasData;
}
//these are the type of external representations used by Dock.app.
+ (NSURL *) fileURLWithDockDescription:(NSDictionary *)dict {
NSURL *URL = nil;
NSString *path = [dict objectForKey:_CFURLStringKey];
NSData *aliasData = [dict objectForKey:_CFURLAliasDataKey];
if (aliasData)
URL = [self fileURLWithAliasData:aliasData];
if (!URL) {
if (path) {
NSNumber *pathStyleNum = [dict objectForKey:_CFURLStringTypeKey];
CFURLPathStyle pathStyle = pathStyleNum ? [pathStyleNum intValue] : kCFURLPOSIXPathStyle;
BOOL isDir = YES;
BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDir];
if (exists) {
URL = [(NSURL *)CFURLCreateWithFileSystemPath(kCFAllocatorDefault, (CFStringRef)path, pathStyle, /*isDirectory*/ isDir) autorelease];
}
}
}
return URL;
}
- (NSDictionary *) dockDescription {
NSMutableDictionary *dict;
NSString *path = [self path];
NSData *aliasData = [self aliasData];
if (path || aliasData) {
dict = [NSMutableDictionary dictionaryWithCapacity:3U];
if (path) {
NSNumber *type = [[NSNumber alloc] initWithInt:kCFURLPOSIXPathStyle];
[dict setObject:path forKey:_CFURLStringKey];
[dict setObject:type forKey:_CFURLStringTypeKey];
[type release];
}
if (aliasData) {
[dict setObject:aliasData forKey:_CFURLAliasDataKey];
}
} else {
dict = nil;
}
return dict;
}
@end