Bug 362685 - Growl Integration for Mac OS X (nsIAlertsService). r=cbarrett, r=mano

This commit is contained in:
sdwilsh@shawnwilsher.com 2007-04-24 20:10:59 -07:00
parent bf1432026a
commit 7b577b4614
23 changed files with 3874 additions and 2 deletions

View File

@ -20,6 +20,7 @@
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
# Shawn Wilsher <me@shawnwilsher.com>
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
@ -48,5 +49,9 @@ ifneq (,$(filter $(MOZ_WIDGET_TOOLKIT),windows gtk gtk2))
DIRS += src
endif
ifneq (,$(filter mac cocoa, $(MOZ_WIDGET_TOOLKIT)))
DIRS += src/mac
endif
include $(topsrcdir)/config/rules.mk

View File

@ -0,0 +1,73 @@
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is Growl implementation of nsIAlertsService.
#
# The Initial Developer of the Original Code is
# Shawn Wilsher <me@shawnwilsher.com>.
# Portions created by the Initial Developer are Copyright (C) 2006-2007
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****
DEPTH = ../../../../..
topsrcdir = @top_srcdir@
srcdir = @srcdir@
VPATH = @srcdir@
include $(DEPTH)/config/autoconf.mk
MODULE = alerts
LIBRARY_NAME = alerts_s
FORCE_STATIC_LIB = 1
LIBXUL_LIBRARY = 1
DIRS = growl
REQUIRES = \
xpcom \
string \
necko \
xulapp \
$(NULL)
CMMSRCS = \
nsAlertsService.mm \
mozGrowlDelegate.mm \
nsAlertsImageLoadListener.mm \
$(NULL)
LOCAL_INCLUDES += \
-I$(srcdir)/growl/ \
-I$(topsrcdir)/toolkit/components/build/ \
$(NULL)
include $(topsrcdir)/config/rules.mk
EXTRA_DSO_LDOPTS += \
$(MOZ_COMPONENT_LIBS) \
$(NULL)

View File

@ -0,0 +1,571 @@
//
// CFGrowlAdditions.c
// Growl
//
// Created by Mac-arena the Bored Zo on Wed Jun 18 2004.
// Copyright 2005 The Growl Project.
//
#include <Carbon/Carbon.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include "CFGrowlAdditions.h"
static CFStringRef _CFURLAliasDataKey = CFSTR("_CFURLAliasData");
static CFStringRef _CFURLStringKey = CFSTR("_CFURLString");
static CFStringRef _CFURLStringTypeKey = CFSTR("_CFURLStringType");
//see GrowlApplicationBridge-Carbon.c for rationale of using NSLog.
extern void NSLog(CFStringRef format, ...);
CFStringRef copyCurrentProcessName(void) {
ProcessSerialNumber PSN = { 0, kCurrentProcess };
CFStringRef name = NULL;
OSStatus err = CopyProcessName(&PSN, &name);
if (err != noErr) {
NSLog(CFSTR("in copyCurrentProcessName in CFGrowlAdditions: Could not get process name because CopyProcessName returned %li"), (long)err);
name = NULL;
}
return name;
}
CFURLRef copyCurrentProcessURL(void) {
ProcessSerialNumber psn = { 0, kCurrentProcess };
FSRef fsref;
CFURLRef URL = NULL;
OSStatus err = GetProcessBundleLocation(&psn, &fsref);
if (err != noErr) {
NSLog(CFSTR("in copyCurrentProcessURL in CFGrowlAdditions: Could not get application location, because GetProcessBundleLocation returned %li\n"), (long)err);
} else {
URL = CFURLCreateFromFSRef(kCFAllocatorDefault, &fsref);
}
return URL;
}
CFStringRef copyCurrentProcessPath(void) {
CFURLRef URL = copyCurrentProcessURL();
CFStringRef path = CFURLCopyFileSystemPath(URL, kCFURLPOSIXPathStyle);
CFRelease(URL);
return path;
}
CFURLRef copyTemporaryFolderURL(void) {
FSRef ref;
CFURLRef url = NULL;
OSStatus err = FSFindFolder(kOnAppropriateDisk, kTemporaryFolderType, kCreateFolder, &ref);
if (err != noErr)
NSLog(CFSTR("in copyTemporaryFolderPath in CFGrowlAdditions: Could not locate temporary folder because FSFindFolder returned %li"), (long)err);
else
url = CFURLCreateFromFSRef(kCFAllocatorDefault, &ref);
return url;
}
CFStringRef copyTemporaryFolderPath(void) {
CFStringRef path = NULL;
CFURLRef url = copyTemporaryFolderURL();
if (url) {
path = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle);
CFRelease(url);
}
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;
}
//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;
}
CFStringRef createStringWithAddressData(CFDataRef 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)
// IPv4 Addresses are "255.255.255.255" at max which is smaller
char stringBuffer[40];
CFStringRef addressAsString = NULL;
if (socketAddress->sa_family == AF_INET) {
struct sockaddr_in *ipv4 = (struct sockaddr_in *)socketAddress;
if (inet_ntop(AF_INET, &(ipv4->sin_addr), stringBuffer, 40))
addressAsString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%s:%d"), stringBuffer, ipv4->sin_port);
else
addressAsString = CFSTR("IPv4 un-ntopable");
} else if (socketAddress->sa_family == AF_INET6) {
struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)socketAddress;
if (inet_ntop(AF_INET6, &(ipv6->sin6_addr), stringBuffer, 40))
// Suggested IPv6 format (see http://www.faqs.org/rfcs/rfc2732.html)
addressAsString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("[%s]:%d"), stringBuffer, ipv6->sin6_port);
else
addressAsString = CFSTR("IPv6 un-ntopable");
} else
addressAsString = CFSTR("neither IPv6 nor IPv4");
return addressAsString;
}
CFStringRef createHostNameForAddressData(CFDataRef aAddressData) {
char hostname[NI_MAXHOST];
struct sockaddr *socketAddress = (struct sockaddr *)CFDataGetBytePtr(aAddressData);
if (getnameinfo(socketAddress, CFDataGetLength(aAddressData),
hostname, sizeof(hostname),
/*serv*/ NULL, /*servlen*/ 0,
NI_NAMEREQD))
return NULL;
else
return CFStringCreateWithCString(kCFAllocatorDefault, hostname, kCFStringEncodingASCII);
}
CFDataRef copyIconDataForPath(CFStringRef path) {
CFDataRef data = NULL;
//false is probably safest, and is harmless when the object really is a directory.
CFURLRef URL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, path, kCFURLPOSIXPathStyle, /*isDirectory*/ false);
if (URL) {
data = copyIconDataForURL(URL);
CFRelease(URL);
}
return data;
}
CFDataRef copyIconDataForURL(CFURLRef URL) {
CFDataRef data = NULL;
if (URL) {
FSRef ref;
if (CFURLGetFSRef(URL, &ref)) {
IconRef icon = NULL;
SInt16 label_noOneCares;
OSStatus err = GetIconRefFromFileInfo(&ref,
/*inFileNameLength*/ 0U, /*inFileName*/ NULL,
kFSCatInfoNone, /*inCatalogInfo*/ NULL,
kIconServicesNoBadgeFlag | kIconServicesUpdateIfNeededFlag,
&icon,
&label_noOneCares);
if (err != noErr) {
NSLog(CFSTR("in copyIconDataForURL in CFGrowlAdditions: could not get icon for %@: GetIconRefFromFileInfo returned %li\n"), URL, (long)err);
} else {
IconFamilyHandle fam = NULL;
err = IconRefToIconFamily(icon, kSelectorAllAvailableData, &fam);
if (err != noErr) {
NSLog(CFSTR("in copyIconDataForURL in CFGrowlAdditions: could not get icon for %@: IconRefToIconFamily returned %li\n"), URL, (long)err);
} else {
HLock((Handle)fam);
data = CFDataCreate(kCFAllocatorDefault, (const UInt8 *)*(Handle)fam, GetHandleSize((Handle)fam));
HUnlock((Handle)fam);
DisposeHandle((Handle)fam);
}
ReleaseIconRef(icon);
}
}
}
return data;
}
URL_TYPE createURLByMakingDirectoryAtURLWithName(URL_TYPE parent, STRING_TYPE name) {
CFURLRef newDirectory = NULL;
CFAllocatorRef allocator = parent ? CFGetAllocator(parent) : name ? CFGetAllocator(name) : kCFAllocatorDefault;
if (parent) parent = CFRetain(parent);
else {
char *cwdBytes = alloca(PATH_MAX);
getcwd(cwdBytes, PATH_MAX);
parent = CFURLCreateFromFileSystemRepresentation(allocator, (const unsigned char *)cwdBytes, strlen(cwdBytes), /*isDirectory*/ true);
if (!name) {
newDirectory = parent;
goto end;
}
}
if (!parent)
NSLog(CFSTR("in createURLByMakingDirectoryAtURLWithName in CFGrowlAdditions: parent directory URL is NULL (please tell the Growl developers)\n"), parent);
else {
if (name)
name = CFRetain(name);
else {
name = CFURLCopyLastPathComponent(parent);
CFURLRef newParent = CFURLCreateCopyDeletingLastPathComponent(allocator, parent);
CFRelease(parent);
parent = newParent;
}
if (!name)
NSLog(CFSTR("in createURLByMakingDirectoryAtURLWithName in CFGrowlAdditions: name of directory to create is NULL (please tell the Growl developers)\n"), parent);
else {
FSRef parentRef;
if (!CFURLGetFSRef(parent, &parentRef))
NSLog(CFSTR("in createURLByMakingDirectoryAtURLWithName in CFGrowlAdditions: could not create FSRef for parent directory at %@ (please tell the Growl developers)\n"), parent);
else {
FSRef newDirectoryRef;
struct HFSUniStr255 nameUnicode;
CFRange range = { 0, MIN(CFStringGetLength(name), USHRT_MAX) };
CFStringGetCharacters(name, range, nameUnicode.unicode);
nameUnicode.length = range.length;
struct FSRefParam refPB = {
.ref = &parentRef,
.nameLength = nameUnicode.length,
.name = nameUnicode.unicode,
.whichInfo = kFSCatInfoNone,
.catInfo = NULL,
.textEncodingHint = kTextEncodingUnknown,
.newRef = &newDirectoryRef,
};
OSStatus err = PBCreateDirectoryUnicodeSync(&refPB);
if (err == dupFNErr) {
//dupFNErr == file (or folder) exists already. this is fine.
err = PBMakeFSRefUnicodeSync(&refPB);
}
if (err == noErr) {
NSLog(CFSTR("PBCreateDirectoryUnicodeSync or PBMakeFSRefUnicodeSync returned %li; calling CFURLCreateFromFSRef"), (long)err); //XXX
newDirectory = CFURLCreateFromFSRef(allocator, &newDirectoryRef);
NSLog(CFSTR("CFURLCreateFromFSRef returned %@"), newDirectory); //XXX
} else
NSLog(CFSTR("in createURLByMakingDirectoryAtURLWithName in CFGrowlAdditions: could not create directory '%@' in parent directory at %@: FSCreateDirectoryUnicode returned %li (please tell the Growl developers)"), name, parent, (long)err);
}
CFRelease(parent);
} //if (name)
CFRelease(name);
} //if (parent)
end:
return newDirectory;
}
#ifndef COPYFORK_BUFSIZE
# define COPYFORK_BUFSIZE 5242880U /*5 MiB*/
#endif
static OSStatus copyFork(const struct HFSUniStr255 *forkName, const FSRef *srcFile, const FSRef *destDir, const struct HFSUniStr255 *destName, FSRef *outDestFile) {
OSStatus err, closeErr;
struct FSForkIOParam srcPB = {
.ref = srcFile,
.forkNameLength = forkName->length,
.forkName = forkName->unicode,
.permissions = fsRdPerm,
};
unsigned char debuggingPathBuf[PATH_MAX] = "";
OSStatus debuggingPathErr;
err = PBOpenForkSync(&srcPB);
if (err != noErr) {
debuggingPathErr = FSRefMakePath(srcFile, debuggingPathBuf, PATH_MAX);
if (debuggingPathErr != noErr)
snprintf((char *)debuggingPathBuf, PATH_MAX, "(could not get path for source file: FSRefMakePath returned %li)", (long)debuggingPathErr);
NSLog(CFSTR("in copyFork in CFGrowlAdditions: PBOpenForkSync (source: %s) returned %li"), debuggingPathBuf, (long)err);
} else {
FSRef destFile;
/*the first thing to do is get the name of the destination file, if one
* wasn't provided.
*and while we're at it, we get the catalogue info as well.
*/
struct FSCatalogInfo catInfo;
struct FSRefParam refPB = {
.ref = srcFile,
.whichInfo = kFSCatInfoGettableInfo & kFSCatInfoSettableInfo,
.catInfo = &catInfo,
.spec = NULL,
.parentRef = NULL,
.outName = destName ? NULL : (struct HFSUniStr255 *)(destName = alloca(sizeof(struct HFSUniStr255))),
};
err = PBGetCatalogInfoSync(&refPB);
if (err != noErr) {
debuggingPathErr = FSRefMakePath(srcFile, debuggingPathBuf, PATH_MAX);
if (debuggingPathErr != noErr)
snprintf((char *)debuggingPathBuf, PATH_MAX, "(could not get path for source file: FSRefMakePath returned %li)", (long)debuggingPathErr);
NSLog(CFSTR("in copyFork in CFGrowlAdditions: PBGetCatalogInfoSync (source: %s) returned %li"), debuggingPathBuf, (long)err);
} else {
refPB.ref = destDir;
refPB.nameLength = destName->length;
refPB.name = destName->unicode;
refPB.textEncodingHint = kTextEncodingUnknown;
refPB.newRef = &destFile;
const char *functionName = "PBMakeFSRefUnicodeSync"; //for error-reporting message
err = PBMakeFSRefUnicodeSync(&refPB);
if ((err != noErr) && (err != fnfErr)) {
handleMakeFSRefError:
debuggingPathErr = FSRefMakePath(destDir, debuggingPathBuf, PATH_MAX);
if (debuggingPathErr != noErr)
snprintf((char *)debuggingPathBuf, PATH_MAX, "(could not get path for destination directory: FSRefMakePath returned %li)", (long)debuggingPathErr);
//get filename too
CFStringRef debuggingFilename = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault,
destName->unicode,
destName->length,
/*contentsDeallocator*/ kCFAllocatorNull);
if (!debuggingFilename)
debuggingFilename = CFStringCreateWithCStringNoCopy(kCFAllocatorDefault, "(could not get filename for destination file: CFStringCreateWithCharactersNoCopy returned NULL)", kCFStringEncodingASCII, /*contentsDeallocator*/ kCFAllocatorNull);
NSLog(CFSTR("in copyFork in CFGrowlAdditions: %s (destination: %s/%@) returned %li"), functionName, debuggingPathBuf, debuggingFilename, (long)err);
if (debuggingFilename) CFRelease(debuggingFilename);
} else {
//that file doesn't exist in that folder; create it.
err = PBCreateFileUnicodeSync(&refPB);
if (err == noErr) {
/*make sure the Finder knows about the new file.
*FNNotify returns a status code too, but this isn't an
* essential step, so we just ignore it.
*/
FNNotify(destDir, kFNDirectoryModifiedMessage, kNilOptions);
} else if (err == dupFNErr) {
/*dupFNErr: the file already exists.
*we can safely ignore this error.
*/
err = noErr;
} else {
functionName = "PBCreateFileUnicodeSync";
goto handleMakeFSRefError;
}
}
}
if (err == noErr) {
if (outDestFile)
memcpy(outDestFile, &destFile, sizeof(destFile));
struct FSForkIOParam destPB = {
.ref = &destFile,
.forkNameLength = forkName->length,
.forkName = forkName->unicode,
.permissions = fsWrPerm,
};
err = PBOpenForkSync(&destPB);
NSLog(CFSTR("in copyFork in CFGrowlAdditions: PBOpenForkSync (dest) returned %li"), (long)err);
if (err != noErr) {
debuggingPathErr = FSRefMakePath(&destFile, debuggingPathBuf, PATH_MAX);
if (debuggingPathErr != noErr)
snprintf((char *)debuggingPathBuf, PATH_MAX, "(could not get path for dest file: FSRefMakePath returned %li)", (long)debuggingPathErr);
NSLog(CFSTR("in copyFork in CFGrowlAdditions: PBOpenForkSync (destination: %s) returned %li"), debuggingPathBuf, (long)err);
} else {
void *buf = malloc(COPYFORK_BUFSIZE);
if (buf) {
srcPB.buffer = destPB.buffer = buf;
srcPB.requestCount = COPYFORK_BUFSIZE;
while (err == noErr) {
err = PBReadForkSync(&srcPB);
if (err == eofErr) {
err = noErr;
if (srcPB.actualCount == 0)
break;
}
if (err != noErr) {
debuggingPathErr = FSRefMakePath(&destFile, debuggingPathBuf, PATH_MAX);
if (debuggingPathErr != noErr)
snprintf((char *)debuggingPathBuf, PATH_MAX, "(could not get path for source file: FSRefMakePath returned %li)", (long)debuggingPathErr);
NSLog(CFSTR("in copyFork in CFGrowlAdditions: PBReadForkSync (source: %s) returned %li"), debuggingPathBuf, (long)err);
} else {
destPB.requestCount = srcPB.actualCount;
err = PBWriteForkSync(&destPB);
if (err != noErr) {
debuggingPathErr = FSRefMakePath(&destFile, debuggingPathBuf, PATH_MAX);
if (debuggingPathErr != noErr)
snprintf((char *)debuggingPathBuf, PATH_MAX, "(could not get path for dest file: FSRefMakePath returned %li)", (long)debuggingPathErr);
NSLog(CFSTR("in copyFork in CFGrowlAdditions: PBWriteForkSync (destination: %s) returned %li"), debuggingPathBuf, (long)err);
}
}
}
free(buf);
}
closeErr = PBCloseForkSync(&destPB);
if (closeErr != noErr) {
debuggingPathErr = FSRefMakePath(&destFile, debuggingPathBuf, PATH_MAX);
if (debuggingPathErr != noErr)
snprintf((char *)debuggingPathBuf, PATH_MAX, "(could not get path for dest file: FSRefMakePath returned %li)", (long)debuggingPathErr);
NSLog(CFSTR("in copyFork in CFGrowlAdditions: PBCloseForkSync (destination: %s) returned %li"), debuggingPathBuf, (long)err);
}
if (err == noErr) err = closeErr;
}
}
closeErr = PBCloseForkSync(&srcPB);
if (closeErr != noErr) {
debuggingPathErr = FSRefMakePath(&destFile, debuggingPathBuf, PATH_MAX);
if (debuggingPathErr != noErr)
snprintf((char *)debuggingPathBuf, PATH_MAX, "(could not get path for source file: FSRefMakePath returned %li)", (long)debuggingPathErr);
NSLog(CFSTR("in copyFork in CFGrowlAdditions: PBCloseForkSync (source: %s) returned %li"), debuggingPathBuf, (long)err);
}
if (err == noErr) err = closeErr;
}
return err;
}
static OSStatus GrowlCopyObjectSync(const FSRef *fileRef, const FSRef *destRef, FSRef *destFileRef) {
OSStatus err;
struct HFSUniStr255 forkName;
struct FSForkIOParam forkPB = {
.ref = fileRef,
.forkIterator = {
.initialize = 0L
},
.outForkName = &forkName,
};
do {
err = PBIterateForksSync(&forkPB);
NSLog(CFSTR("PBIterateForksSync returned %li"), (long)err);
if (err != noErr) {
if (err != errFSNoMoreItems)
NSLog(CFSTR("in GrowlCopyObjectSync in CFGrowlAdditions: PBIterateForksSync returned %li"), (long)err);
} else {
err = copyFork(&forkName, fileRef, destRef, /*destName*/ NULL, /*outDestFile*/ destFileRef);
//copyFork prints its own error messages
}
} while (err == noErr);
if (err == errFSNoMoreItems) err = noErr;
return err;
}
CFURLRef createURLByCopyingFileFromURLToDirectoryURL(CFURLRef file, CFURLRef dest) {
CFURLRef destFileURL = NULL;
FSRef fileRef, destRef, destFileRef;
Boolean gotFileRef = CFURLGetFSRef(file, &fileRef);
Boolean gotDestRef = CFURLGetFSRef(dest, &destRef);
if (!gotFileRef)
NSLog(CFSTR("in createURLByCopyingFileFromURLToDirectoryURL in CFGrowlAdditions: CFURLGetFSRef failed with source URL %@"), file);
else if (!gotDestRef)
NSLog(CFSTR("in createURLByCopyingFileFromURLToDirectoryURL in CFGrowlAdditions: CFURLGetFSRef failed with destination URL %@"), dest);
else {
OSStatus err;
/*
* 10.2 has a problem with weak symbols in frameworks so we use
* MAC_OS_X_VERSION_MIN_REQUIRED >= 10.3.
*/
#if defined(NSAppKitVersionNumber10_3) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4 && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_3
if (FSCopyObjectSync) {
err = FSCopyObjectSync(&fileRef, &destRef, /*destName*/ NULL, &destFileRef, kFSFileOperationOverwrite);
} else {
#endif
err = GrowlCopyObjectSync(&fileRef, &destRef, &destFileRef);
#if defined(NSAppKitVersionNumber10_3) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4 && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_3
}
#endif
if (err == noErr)
destFileURL = CFURLCreateFromFSRef(kCFAllocatorDefault, &destFileRef);
else
NSLog(CFSTR("in createURLByCopyingFileFromURLToDirectoryURL in CFGrowlAdditions: CopyObjectSync returned %li for source URL %@"), (long)err, file);
}
return destFileURL;
}
CFPropertyListRef createPropertyListFromURL(CFURLRef file, u_int32_t mutability, CFPropertyListFormat *outFormat, CFStringRef *outErrorString) {
CFPropertyListRef plist = NULL;
if (!file)
NSLog(CFSTR("in createPropertyListFromURL in CFGrowlAdditions: cannot read from a NULL URL"));
else {
CFReadStreamRef stream = CFReadStreamCreateWithFile(kCFAllocatorDefault, file);
if (!stream)
NSLog(CFSTR("in createPropertyListFromURL in CFGrowlAdditions: could not create stream for reading from URL %@"), file);
else {
if (!CFReadStreamOpen(stream))
NSLog(CFSTR("in createPropertyListFromURL in CFGrowlAdditions: could not open stream for reading from URL %@"), file);
else {
CFPropertyListFormat format;
CFStringRef errorString = NULL;
plist = CFPropertyListCreateFromStream(kCFAllocatorDefault,
stream,
/*streamLength*/ 0,
mutability,
&format,
&errorString);
if (!plist)
NSLog(CFSTR("in createPropertyListFromURL in CFGrowlAdditions: could not read property list from URL %@ (error string: %@)"), file, errorString);
if (outFormat) *outFormat = format;
if (errorString) {
if (outErrorString)
*outErrorString = errorString;
else
CFRelease(errorString);
}
CFReadStreamClose(stream);
}
CFRelease(stream);
}
}
return plist;
}

View File

@ -0,0 +1,80 @@
//
// CFGrowlAdditions.h
// Growl
//
// Created by Mac-arena the Bored Zo on Wed Jun 18 2004.
// Copyright 2005 The Growl Project.
//
#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
STRING_TYPE copyCurrentProcessName(void);
URL_TYPE copyCurrentProcessURL(void);
STRING_TYPE copyCurrentProcessPath(void);
URL_TYPE copyTemporaryFolderURL(void);
STRING_TYPE copyTemporaryFolderPath(void);
STRING_TYPE createStringWithAddressData(DATA_TYPE aAddressData);
STRING_TYPE createHostNameForAddressData(DATA_TYPE aAddressData);
DICTIONARY_TYPE createDockDescriptionForURL(URL_TYPE url);
/* @function copyIconDataForPath
* @param path The POSIX path to the file or folder whose icon you want.
* @result The icon data, in IconFamily format (same as used in the 'icns' resource and in .icns files). You are responsible for releasing this object.
*/
DATA_TYPE copyIconDataForPath(STRING_TYPE path);
/* @function copyIconDataForURL
* @param URL The URL to the file or folder whose icon you want.
* @result The icon data, in IconFamily format (same as used in the 'icns' resource and in .icns files). You are responsible for releasing this object.
*/
DATA_TYPE copyIconDataForURL(URL_TYPE URL);
/* @function createURLByMakingDirectoryAtURLWithName
* @abstract Create a directory.
* @discussion This function has a useful side effect: if you pass
* <code>NULL</code> for both parameters, this function will act basically as
* CFURL version of <code>getcwd</code>(3).
*
* Also, for CF clients: the allocator used to create the returned URL will
* be the allocator for the parent URL, the allocator for the name string, or
* the default allocator, in that order of preference.
* @param parent The directory in which to create the new directory. If this is <code>NULL</code>, the current working directory (as returned by <code>getcwd</code>(3)) will be used.
* @param name The name of the directory you want to create. If this is <code>NULL</code>, the directory specified by the URL will be created.
* @result The URL for the directory if it was successfully created (in which case, you are responsible for releasing this object); else, <code>NULL</code>.
*/
URL_TYPE createURLByMakingDirectoryAtURLWithName(URL_TYPE parent, STRING_TYPE name);
/* @function createURLByCopyingFileFromURLToDirectoryURL
* @param file The file to copy.
* @param dest The folder to copy it to.
* @result The copy. You are responsible for releasing this object.
*/
URL_TYPE createURLByCopyingFileFromURLToDirectoryURL(URL_TYPE file, URL_TYPE dest);
/* @function createPropertyListFromURL
* @abstract Reads a property list from the contents of an URL.
* @discussion Creates a property list from the data at an URL (for example, a
* file URL), and returns it.
* @param file The file to read.
* @param mutability A mutability-option constant indicating whether the property list (and possibly its contents) should be mutable.
* @param outFormat If the property list is read successfully, this will point to the format of the property list. You may pass NULL if you are not interested in this information. If the property list is not read successfully, the value at this pointer will be left unchanged.
* @param outErrorString If an error occurs, this will point to a string (which you are responsible for releasing) describing the error. You may pass NULL if you are not interested in this information. If no error occurs, the value at this pointer will be left unchanged.
* @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);

View File

@ -0,0 +1,556 @@
//
// GrowlApplicationBridge.h
// Growl
//
// Created by Evan Schoenberg on Wed Jun 16 2004.
// Copyright 2004-2005 The Growl Project. All rights reserved.
//
/*!
* @header GrowlApplicationBridge.h
* @abstract Defines the GrowlApplicationBridge class.
* @discussion This header defines the GrowlApplicationBridge class as well as
* the GROWL_PREFPANE_BUNDLE_IDENTIFIER constant.
*/
#ifndef __GrowlApplicationBridge_h__
#define __GrowlApplicationBridge_h__
#import <Foundation/Foundation.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"
//------------------------------------------------------------------------------
#pragma mark -
/*!
* @class GrowlApplicationBridge
* @abstract A class used to interface with Growl.
* @discussion This class provides a means to interface with Growl.
*
* Currently it provides a way to detect if Growl is installed and launch the
* GrowlHelperApp if it's not already running.
*/
@interface GrowlApplicationBridge : NSObject {
}
/*!
* @method isGrowlInstalled
* @abstract Detects whether Growl is installed.
* @discussion Determines if the Growl prefpane and its helper app are installed.
* @result Returns YES if Growl is installed, NO otherwise.
*/
+ (BOOL) isGrowlInstalled;
/*!
* @method isGrowlRunning
* @abstract Detects whether GrowlHelperApp is currently running.
* @discussion Cycles through the process list to find whether GrowlHelperApp is running and returns its findings.
* @result Returns YES if GrowlHelperApp is running, NO otherwise.
*/
+ (BOOL) isGrowlRunning;
#pragma mark -
/*!
* @method setGrowlDelegate:
* @abstract Set the object which will be responsible for providing and receiving Growl information.
* @discussion This must be called before using GrowlApplicationBridge.
*
* The methods in the GrowlApplicationBridgeDelegate protocol are required
* and return the basic information needed to register with Growl.
*
* The methods in the GrowlApplicationBridgeDelegate_InformalProtocol
* informal protocol are individually optional. They provide a greater
* degree of interaction between the application and growl such as informing
* the application when one of its Growl notifications is clicked by the user.
*
* The methods in the GrowlApplicationBridgeDelegate_Installation_InformalProtocol
* informal protocol are individually optional and are only applicable when
* using the Growl-WithInstaller.framework which allows for automated Growl
* installation.
*
* When this method is called, data will be collected from inDelegate, Growl
* will be launched if it is not already running, and the application will be
* registered with Growl.
*
* If using the Growl-WithInstaller framework, if Growl is already installed
* but this copy of the framework has an updated version of Growl, the user
* will be prompted to update automatically.
*
* @param inDelegate The delegate for the GrowlApplicationBridge. It must conform to the GrowlApplicationBridgeDelegate protocol.
*/
+ (void) setGrowlDelegate:(NSObject<GrowlApplicationBridgeDelegate> *)inDelegate;
/*!
* @method growlDelegate
* @abstract Return the object responsible for providing and receiving Growl information.
* @discussion See setGrowlDelegate: for details.
* @result The Growl delegate.
*/
+ (NSObject<GrowlApplicationBridgeDelegate> *) growlDelegate;
#pragma mark -
/*!
* @method notifyWithTitle:description:notificationName:iconData:priority:isSticky:clickContext:
* @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).
*/
+ (void) notifyWithTitle:(NSString *)title
description:(NSString *)description
notificationName:(NSString *)notifName
iconData:(NSData *)iconData
priority:(signed int)priority
isSticky:(BOOL)isSticky
clickContext:(id)clickContext;
/*!
* @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>.
* @param userInfo The dictionary to notify with.
* @discussion Before Growl 0.6, your application would have posted
* notifications using <code>NSDistributedNotificationCenter</code> by
* creating a userInfo dictionary with the notification data. This had the
* advantage of allowing you to add other data to the dictionary for programs
* besides Growl that might be listening.
*
* This method allows you to use such dictionaries without being restricted
* to using <code>NSDistributedNotificationCenter</code>. The keys for this dictionary
* can be found in GrowlDefines.h.
*/
+ (void) notifyWithDictionary:(NSDictionary *)userInfo;
#pragma mark -
/*! @method registerWithDictionary:
* @abstract Register your application with Growl without setting a delegate.
* @discussion When you call this method with a dictionary,
* GrowlApplicationBridge registers your application using that dictionary.
* If you pass <code>nil</code>, GrowlApplicationBridge will ask the delegate
* (if there is one) for a dictionary, and if that doesn't work, it will look
* in your application's bundle for an auto-discoverable plist.
* (XXX refer to more information on that)
*
* If you pass a dictionary to this method, it must include the
* <code>GROWL_APP_NAME</code> key, unless a delegate is set.
*
* This method is mainly an alternative to the delegate system introduced
* with Growl 0.6. Without a delegate, you cannot receive callbacks such as
* <code>-growlIsReady</code> (since they are sent to the delegate). You can,
* however, set a delegate after registering without one.
*
* This method was introduced in Growl.framework 0.7.
*/
+ (BOOL) registerWithDictionary:(NSDictionary *)regDict;
/*! @method reregisterGrowlNotifications
* @abstract Reregister the notifications for this application.
* @discussion This method does not normally need to be called. If your
* application changes what notifications it is registering with Growl, call
* this method to have the Growl delegate's
* <code>-registrationDictionaryForGrowl</code> method called again and the
* Growl registration information updated.
*
* This method is now implemented using <code>-registerWithDictionary:</code>.
*/
+ (void) reregisterGrowlNotifications;
#pragma mark -
/*! @method setWillRegisterWhenGrowlIsReady:
* @abstract Tells GrowlApplicationBridge to register with Growl when Growl
* launches (or not).
* @discussion When Growl has started listening for notifications, it posts a
* <code>GROWL_IS_READY</code> notification on the Distributed Notification
* Center. GrowlApplicationBridge listens for this notification, using it to
* perform various tasks (such as calling your delegate's
* <code>-growlIsReady</code> method, if it has one). If this method is
* called with <code>YES</code>, one of those tasks will be to reregister
* with Growl (in the manner of <code>-reregisterGrowlNotifications</code>).
*
* This attribute is automatically set back to <code>NO</code> (the default)
* after every <code>GROWL_IS_READY</code> notification.
* @param flag <code>YES</code> if you want GrowlApplicationBridge to register with
* Growl when next it is ready; <code>NO</code> if not.
*/
+ (void) setWillRegisterWhenGrowlIsReady:(BOOL)flag;
/*! @method willRegisterWhenGrowlIsReady
* @abstract Reports whether GrowlApplicationBridge will register with Growl
* when Growl next launches.
* @result <code>YES</code> if GrowlApplicationBridge will register with Growl
* when next it posts GROWL_IS_READY; <code>NO</code> if not.
*/
+ (BOOL) willRegisterWhenGrowlIsReady;
#pragma mark -
/*! @method registrationDictionaryFromDelegate
* @abstract Asks the delegate for a registration dictionary.
* @discussion If no delegate is set, or if the delegate's
* <code>-registrationDictionaryForGrowl</code> method returns
* <code>nil</code>, this method returns <code>nil</code>.
*
* This method does not attempt to clean up the dictionary in any way - for
* example, if it is missing the <code>GROWL_APP_NAME</code> key, the result
* will be missing it too. Use <code>+[GrowlApplicationBridge
* registrationDictionaryByFillingInDictionary:]</code> or
* <code>+[GrowlApplicationBridge
* registrationDictionaryByFillingInDictionary:restrictToKeys:]</code> to try
* to fill in missing keys.
*
* This method was introduced in Growl.framework 0.7.
* @result A registration dictionary.
*/
+ (NSDictionary *) registrationDictionaryFromDelegate;
/*! @method registrationDictionaryFromBundle:
* @abstract Looks in a bundle for a registration dictionary.
* @discussion This method looks in a bundle for an auto-discoverable
* registration dictionary file using <code>-[NSBundle
* pathForResource:ofType:]</code>. If it finds one, it loads the file using
* <code>+[NSDictionary dictionaryWithContentsOfFile:]</code> and returns the
* result.
*
* If you pass <code>nil</code> as the bundle, the main bundle is examined.
*
* This method does not attempt to clean up the dictionary in any way - for
* example, if it is missing the <code>GROWL_APP_NAME</code> key, the result
* will be missing it too. Use <code>+[GrowlApplicationBridge
* registrationDictionaryByFillingInDictionary:]</code> or
* <code>+[GrowlApplicationBridge
* registrationDictionaryByFillingInDictionary:restrictToKeys:]</code> to try
* to fill in missing keys.
*
* This method was introduced in Growl.framework 0.7.
* @result A registration dictionary.
*/
+ (NSDictionary *) registrationDictionaryFromBundle:(NSBundle *)bundle;
/*! @method bestRegistrationDictionary
* @abstract Obtains a registration dictionary, filled out to the best of
* GrowlApplicationBridge's knowledge.
* @discussion This method creates a registration dictionary as best
* GrowlApplicationBridge knows how.
*
* First, GrowlApplicationBridge contacts the Growl delegate (if there is
* one) and gets the registration dictionary from that. If no such dictionary
* was obtained, GrowlApplicationBridge looks in your application's main
* bundle for an auto-discoverable registration dictionary file. If that
* doesn't exist either, this method returns <code>nil</code>.
*
* Second, GrowlApplicationBridge calls
* <code>+registrationDictionaryByFillingInDictionary:</code> with whatever
* dictionary was obtained. The result of that method is the result of this
* method.
*
* GrowlApplicationBridge uses this method when you call
* <code>+setGrowlDelegate:</code>, or when you call
* <code>+registerWithDictionary:</code> with <code>nil</code>.
*
* This method was introduced in Growl.framework 0.7.
* @result A registration dictionary.
*/
+ (NSDictionary *) bestRegistrationDictionary;
#pragma mark -
/*! @method registrationDictionaryByFillingInDictionary:
* @abstract Tries to fill in missing keys in a registration dictionary.
* @discussion This method examines the passed-in dictionary for missing keys,
* and tries to work out correct values for them. As of 0.7, it uses:
*
* Key Value
* --- -----
* <code>GROWL_APP_NAME</code> <code>CFBundleExecutableName</code>
* <code>GROWL_APP_ICON</code> The icon of the application.
* <code>GROWL_APP_LOCATION</code> The location of the application.
* <code>GROWL_NOTIFICATIONS_DEFAULT</code> <code>GROWL_NOTIFICATIONS_ALL</code>
*
* Keys are only filled in if missing; if a key is present in the dictionary,
* its value will not be changed.
*
* This method was introduced in Growl.framework 0.7.
* @param regDict The dictionary to fill in.
* @result The dictionary with the keys filled in. This is an autoreleased
* copy of <code>regDict</code>.
*/
+ (NSDictionary *) registrationDictionaryByFillingInDictionary:(NSDictionary *)regDict;
/*! @method registrationDictionaryByFillingInDictionary:restrictToKeys:
* @abstract Tries to fill in missing keys in a registration dictionary.
* @discussion This method examines the passed-in dictionary for missing keys,
* and tries to work out correct values for them. As of 0.7, it uses:
*
* Key Value
* --- -----
* <code>GROWL_APP_NAME</code> <code>CFBundleExecutableName</code>
* <code>GROWL_APP_ICON</code> The icon of the application.
* <code>GROWL_APP_LOCATION</code> The location of the application.
* <code>GROWL_NOTIFICATIONS_DEFAULT</code> <code>GROWL_NOTIFICATIONS_ALL</code>
*
* Only those keys that are listed in <code>keys</code> will be filled in.
* Other missing keys are ignored. Also, keys are only filled in if missing;
* if a key is present in the dictionary, its value will not be changed.
*
* This method was introduced in Growl.framework 0.7.
* @param regDict The dictionary to fill in.
* @param keys The keys to fill in. If <code>nil</code>, any missing keys are filled in.
* @result The dictionary with the keys filled in. This is an autoreleased
* copy of <code>regDict</code>.
*/
+ (NSDictionary *) registrationDictionaryByFillingInDictionary:(NSDictionary *)regDict restrictToKeys:(NSSet *)keys;
@end
//------------------------------------------------------------------------------
#pragma mark -
/*!
* @protocol GrowlApplicationBridgeDelegate
* @abstract Required protocol for the Growl delegate.
* @discussion The methods in this protocol are required and are called
* automatically as needed by GrowlApplicationBridge. See
* <code>+[GrowlApplicationBridge setGrowlDelegate:]</code>.
* See also <code>GrowlApplicationBridgeDelegate_InformalProtocol</code>.
*/
@protocol GrowlApplicationBridgeDelegate
// -registrationDictionaryForGrowl has moved to the informal protocol as of 0.7.
@end
//------------------------------------------------------------------------------
#pragma mark -
/*!
* @category NSObject(GrowlApplicationBridgeDelegate_InformalProtocol)
* @abstract Methods which may be optionally implemented by the GrowlDelegate.
* @discussion The methods in this informal protocol will only be called if implemented by the delegate.
*/
@interface NSObject (GrowlApplicationBridgeDelegate_InformalProtocol)
/*!
* @method registrationDictionaryForGrowl
* @abstract Return the dictionary used to register this application with Growl.
* @discussion The returned dictionary gives Growl the complete list of
* notifications this application will ever send, and it also specifies which
* notifications should be enabled by default. Each is specified by an array
* of <code>NSString</code> objects.
*
* For most applications, these two arrays can be the same (if all sent
* notifications should be displayed by default).
*
* The <code>NSString</code> objects of these arrays will correspond to the
* <code>notificationName:</code> parameter passed in
* <code>+[GrowlApplicationBridge
* notifyWithTitle:description:notificationName:iconData:priority:isSticky:clickContext:]</code> calls.
*
* The dictionary should have 2 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
*
* 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)
*
* @result The <code>NSDictionary</code> to use for registration.
*/
- (NSDictionary *) registrationDictionaryForGrowl;
/*!
* @method applicationNameForGrowl
* @abstract Return the name of this application which will be used for Growl bookkeeping.
* @discussion This name is used both internally and in the Growl preferences.
*
* This should remain stable between different versions and incarnations of
* your application.
* For example, "SurfWriter" is a good app name, whereas "SurfWriter 2.0" and
* "SurfWriter Lite" are not.
*
* You do not need to implement this method if you are providing the
* application name elsewhere, meaning in an auto-discoverable plist file in
* your app bundle (XXX refer to more information on that) or in the result
* of -registrationDictionaryForGrowl.
*
* @result The name of the application using Growl.
*/
- (NSString *) applicationNameForGrowl;
/*!
* @method applicationIconDataForGrowl
* @abstract Return the <code>NSData</code> to treat as the application icon.
* @discussion The delegate may optionally return an <code>NSData</code>
* 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.
*/
- (NSData *) applicationIconDataForGrowl;
/*!
* @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.
*/
- (void) growlIsReady;
/*!
* @method growlNotificationWasClicked:
* @abstract Informs the delegate that a Growl notification was clicked.
* @discussion Informs the delegate that a Growl notification was clicked. It
* is only sent for notifications sent with a non-<code>nil</code>
* clickContext, so if you want to receive a message when a notification is
* clicked, clickContext must not be <code>nil</code> when calling
* <code>+[GrowlApplicationBridge notifyWithTitle: description:notificationName:iconData:priority:isSticky:clickContext:]</code>.
* @param clickContext The clickContext passed when displaying the notification originally via +[GrowlApplicationBridge notifyWithTitle:description:notificationName:iconData:priority:isSticky:clickContext:].
*/
- (void) growlNotificationWasClicked:(id)clickContext;
/*!
* @method growlNotificationTimedOut:
* @abstract Informs the delegate that a Growl notification timed out.
* @discussion Informs the delegate that a Growl notification timed out. It
* is only sent for notifications sent with a non-<code>nil</code>
* clickContext, so if you want to receive a message when a notification is
* clicked, clickContext must not be <code>nil</code> when calling
* <code>+[GrowlApplicationBridge notifyWithTitle: description:notificationName:iconData:priority:isSticky:clickContext:]</code>.
* @param clickContext The clickContext passed when displaying the notification originally via +[GrowlApplicationBridge notifyWithTitle:description:notificationName:iconData:priority:isSticky:clickContext:].
*/
- (void) growlNotificationTimedOut:(id)clickContext;
@end
#pragma mark -
/*!
* @category NSObject(GrowlApplicationBridgeDelegate_Installation_InformalProtocol)
* @abstract Methods which may be optionally implemented by the Growl delegate when used with Growl-WithInstaller.framework.
* @discussion The methods in this informal protocol will only be called if
* implemented by the delegate. They allow greater control of the information
* presented to the user when installing or upgrading Growl from within your
* application when using Growl-WithInstaller.framework.
*/
@interface NSObject (GrowlApplicationBridgeDelegate_Installation_InformalProtocol)
/*!
* @method growlInstallationWindowTitle
* @abstract Return the title of the installation window.
* @discussion If not implemented, Growl will use a default, localized title.
* @result An NSString object to use as the title.
*/
- (NSString *)growlInstallationWindowTitle;
/*!
* @method growlUpdateWindowTitle
* @abstract Return the title of the upgrade window.
* @discussion If not implemented, Growl will use a default, localized title.
* @result An NSString object to use as the title.
*/
- (NSString *)growlUpdateWindowTitle;
/*!
* @method growlInstallationInformation
* @abstract Return the information to display when installing.
* @discussion This information may be as long or short as desired (the window
* will be sized to fit it). It will be displayed to the user as an
* explanation of what Growl is and what it can do in your application. It
* should probably note that no download is required to install.
*
* If this is not implemented, Growl will use a default, localized explanation.
* @result An NSAttributedString object to display.
*/
- (NSAttributedString *)growlInstallationInformation;
/*!
* @method growlUpdateInformation
* @abstract Return the information to display when upgrading.
* @discussion This information may be as long or short as desired (the window
* will be sized to fit it). It will be displayed to the user as an
* explanation that an updated version of Growl is included in your
* application and no download is required.
*
* If this is not implemented, Growl will use a default, localized explanation.
* @result An NSAttributedString object to display.
*/
- (NSAttributedString *)growlUpdateInformation;
@end
//private
@interface GrowlApplicationBridge (GrowlInstallationPrompt_private)
+ (void) _userChoseNotToInstallGrowl;
@end
#endif /* __GrowlApplicationBridge_h__ */

View File

@ -0,0 +1,692 @@
//
// GrowlApplicationBridge.m
// Growl
//
// Created by Evan Schoenberg on Wed Jun 16 2004.
// Copyright 2004-2005 The Growl Project. All rights reserved.
//
#import "GrowlApplicationBridge.h"
#ifdef GROWL_WITH_INSTALLER
#import "GrowlInstallationPrompt.h"
#import "GrowlVersionUtilities.h"
#endif
#import "NSURLAdditions.h"
#import "CFGrowlAdditions.h"
#import "GrowlDefinesInternal.h"
#import "GrowlPathUtil.h"
#import "GrowlPathway.h"
#import <ApplicationServices/ApplicationServices.h>
#define PREFERENCE_PANES_SUBFOLDER_OF_LIBRARY @"PreferencePanes"
#define PREFERENCE_PANE_EXTENSION @"prefPane"
@interface GrowlApplicationBridge (PRIVATE)
/*!
* @method launchGrowlIfInstalled
* @abstract Launches GrowlHelperApp.
* @discussion Launches the GrowlHelperApp if it's not already running.
* GROWL_IS_READY will be posted to the distributed notification center
* once it is ready.
*
* Uses <code>+_launchGrowlIfInstalledWithRegistrationDictionary:</code>.
* @result Returns YES if GrowlHelperApp began launching or was already running, NO if Growl isn't installed
*/
+ (BOOL) launchGrowlIfInstalled;
/*!
* @method _launchGrowlIfInstalledWithRegistrationDictionary:
* @abstract Launches GrowlHelperApp and registers.
* @discussion Launches the GrowlHelperApp if it's not already running, and passes it a registration dictionary.
* If Growl is turned on in the Growl prefpane, GROWL_IS_READY will be posted
* to the distributed notification center when Growl is listening for
* notifications.
* @param regDict The dictionary with which to register.
* @result Returns YES if GrowlHelperApp began launching or was already running, NO if Growl isn't installed
*/
+ (BOOL) _launchGrowlIfInstalledWithRegistrationDictionary:(NSDictionary *)regDict;
#ifdef GROWL_WITH_INSTALLER
+ (void) _checkForPackagedUpdateForGrowlPrefPaneBundle:(NSBundle *)growlPrefPaneBundle;
#endif
/*! @method _applicationNameForGrowlSearchingRegistrationDictionary:
* @abstract Obtain the name of the current application.
* @param regDict The dictionary to search, or <code>nil</code> not to.
* @result The name of the current application.
* @discussion Does not call +bestRegistrationDictionary, and is therefore safe to call from it.
*/
+ (NSString *) _applicationNameForGrowlSearchingRegistrationDictionary:(NSDictionary *)regDict;
/*! @method _applicationNameForGrowlSearchingRegistrationDictionary:
* @abstract Obtain the icon of the current application.
* @param regDict The dictionary to search, or <code>nil</code> not to.
* @result The icon of the current application, in IconFamily format (same as is used in 'icns' resources and .icns files).
* @discussion Does not call +bestRegistrationDictionary, and is therefore safe to call from it.
*/
+ (NSData *) _applicationIconDataForGrowlSearchingRegistrationDictionary:(NSDictionary *)regDict;
@end
static NSString *appName = nil;
static NSData *appIconData = nil;
static id delegate = nil;
static BOOL growlLaunched = NO;
#ifdef GROWL_WITH_INSTALLER
static NSMutableArray *queuedGrowlNotifications = nil;
static BOOL userChoseNotToInstallGrowl = NO;
static BOOL promptedToInstallGrowl = NO;
static BOOL promptedToUpgradeGrowl = NO;
#endif
//used primarily by GIP, but could be useful elsewhere.
static BOOL registerWhenGrowlIsReady = NO;
#pragma mark -
@implementation GrowlApplicationBridge
+ (void) setGrowlDelegate:(NSObject<GrowlApplicationBridgeDelegate> *)inDelegate {
NSDistributedNotificationCenter *NSDNC = [NSDistributedNotificationCenter defaultCenter];
[delegate autorelease];
delegate = [inDelegate retain];
NSDictionary *regDict = [self bestRegistrationDictionary];
//Cache the appName from the delegate or the process name
[appName autorelease];
appName = [[self _applicationNameForGrowlSearchingRegistrationDictionary:regDict] retain];
if (!appName)
NSLog(@"%@", @"GrowlApplicationBridge: Cannot register because the application name was not supplied and could not be determined");
//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];
//Add the observer for GROWL_IS_READY which will be triggered later if all goes well
[NSDNC addObserver:self
selector:@selector(_growlIsReady:)
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
int pid = [[NSProcessInfo processInfo] processIdentifier];
NSString *growlNotificationClickedName = [[NSString alloc] initWithFormat:@"%@-%d-%@",
appName, pid, GROWL_NOTIFICATION_CLICKED];
if ([delegate respondsToSelector:@selector(growlNotificationWasClicked:)]) {
[NSDNC addObserver:self
selector:@selector(_growlNotificationWasClicked:)
name:growlNotificationClickedName
object:nil];
} 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:)]) {
[NSDNC addObserver:self
selector:@selector(_growlNotificationTimedOut:)
name:growlNotificationTimedOutName
object:nil];
} else {
[NSDNC removeObserver:self
name:growlNotificationTimedOutName
object:nil];
}
[growlNotificationTimedOutName release];
#ifdef GROWL_WITH_INSTALLER
//Determine if the user has previously told us not to ever request installation again
userChoseNotToInstallGrowl = [[NSUserDefaults standardUserDefaults] boolForKey:@"Growl Installation:Do Not Prompt Again"];
#endif
growlLaunched = [self _launchGrowlIfInstalledWithRegistrationDictionary:regDict];
}
+ (NSObject<GrowlApplicationBridgeDelegate> *) growlDelegate {
return delegate;
}
#pragma mark -
+ (void) notifyWithTitle:(NSString *)title
description:(NSString *)description
notificationName:(NSString *)notifName
iconData:(NSData *)iconData
priority:(int)priority
isSticky:(BOOL)isSticky
clickContext:(id)clickContext
{
[GrowlApplicationBridge notifyWithTitle:title
description:description
notificationName:notifName
iconData:iconData
priority:priority
isSticky:isSticky
clickContext:clickContext
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.
*/
+ (void) notifyWithTitle:(NSString *)title
description:(NSString *)description
notificationName:(NSString *)notifName
iconData:(NSData *)iconData
priority:(int)priority
isSticky:(BOOL)isSticky
clickContext:(id)clickContext
identifier:(NSString *)identifier
{
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];
[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) {
//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
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");
[[NSDistributedNotificationCenter defaultCenter] postNotificationName:GROWL_NOTIFICATION
object:nil
userInfo:userInfo
deliverImmediately:NO];
}
} else {
#ifdef GROWL_WITH_INSTALLER
/*if Growl launches, and the user hasn't already said NO to installing
* it, store this notification for posting
*/
if (!userChoseNotToInstallGrowl) {
//in case the dictionary is mutable, make a copy.
userInfo = [userInfo copy];
if (!queuedGrowlNotifications) {
queuedGrowlNotifications = [[NSMutableArray alloc] init];
}
[queuedGrowlNotifications addObject:userInfo];
//if we have not already asked the user to install Growl, do it now
if (!promptedToInstallGrowl) {
[GrowlInstallationPrompt showInstallationPrompt];
promptedToInstallGrowl = YES;
}
[userInfo release];
}
#endif
}
}
#pragma mark -
+ (BOOL) isGrowlInstalled {
return ([GrowlPathUtil growlPrefPaneBundle] != nil);
}
+ (BOOL) isGrowlRunning {
BOOL growlIsRunning = NO;
ProcessSerialNumber PSN = { kNoProcess, kNoProcess };
while (GetNextProcess(&PSN) == noErr) {
NSDictionary *infoDict = (NSDictionary *)ProcessInformationCopyDictionary(&PSN, kProcessDictionaryIncludeAllInformationMask);
if ([[infoDict objectForKey:(NSString *)kCFBundleIdentifierKey] isEqualToString:@"com.Growl.GrowlHelperApp"]) {
growlIsRunning = YES;
[infoDict release];
break;
}
[infoDict release];
}
return growlIsRunning;
}
#pragma mark -
+ (BOOL) registerWithDictionary:(NSDictionary *)regDict {
if (regDict)
regDict = [self registrationDictionaryByFillingInDictionary:regDict];
else
regDict = [self bestRegistrationDictionary];
return [self _launchGrowlIfInstalledWithRegistrationDictionary:regDict];
}
+ (void) reregisterGrowlNotifications {
[self registerWithDictionary:nil];
}
+ (void) setWillRegisterWhenGrowlIsReady:(BOOL)flag {
registerWhenGrowlIsReady = flag;
}
+ (BOOL) willRegisterWhenGrowlIsReady {
return registerWhenGrowlIsReady;
}
#pragma mark -
+ (NSDictionary *) registrationDictionaryFromDelegate {
NSDictionary *regDict = nil;
if (delegate && [delegate respondsToSelector:@selector(registrationDictionaryForGrowl)])
regDict = [delegate registrationDictionaryForGrowl];
return regDict;
}
+ (NSDictionary *) registrationDictionaryFromBundle:(NSBundle *)bundle {
if (!bundle) bundle = [NSBundle mainBundle];
NSDictionary *regDict = nil;
NSString *regDictPath = [bundle pathForResource:@"Growl Registration Ticket" ofType:GROWL_REG_DICT_EXTENSION];
if (regDictPath) {
regDict = [NSDictionary dictionaryWithContentsOfFile:regDictPath];
if (!regDict)
NSLog(@"GrowlApplicationBridge: The bundle at %@ contains a registration dictionary, but it is not a valid property list. Please tell this application's developer.", [bundle bundlePath]);
}
return regDict;
}
+ (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 registrationDictionaryByFillingInDictionary:registrationDictionary];
return registrationDictionary;
}
#pragma mark -
+ (NSDictionary *) registrationDictionaryByFillingInDictionary:(NSDictionary *)regDict {
return [self registrationDictionaryByFillingInDictionary:regDict restrictToKeys:nil];
}
+ (NSDictionary *) registrationDictionaryByFillingInDictionary:(NSDictionary *)regDict restrictToKeys:(NSSet *)keys {
if (!regDict) return nil;
NSMutableDictionary *mRegDict = [regDict mutableCopy];
if ((!keys) || [keys containsObject:GROWL_APP_NAME]) {
if (![mRegDict objectForKey:GROWL_APP_NAME]) {
if (!appName)
appName = [[self _applicationNameForGrowlSearchingRegistrationDictionary:regDict] retain];
[mRegDict setObject:appName
forKey:GROWL_APP_NAME];
}
}
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 ((!keys) || [keys containsObject:GROWL_APP_LOCATION]) {
if (![mRegDict objectForKey:GROWL_APP_LOCATION]) {
NSURL *myURL = copyCurrentProcessURL();
if (myURL) {
NSDictionary *file_data = [myURL dockDescription];
if (file_data) {
NSDictionary *location = [[NSDictionary alloc] initWithObjectsAndKeys:file_data, @"file-data", nil];
[mRegDict setObject:location
forKey:GROWL_APP_LOCATION];
[location release];
} else {
[mRegDict removeObjectForKey:GROWL_APP_LOCATION];
}
[myURL release];
}
}
}
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];
}
}
}
NSDictionary *result = [NSDictionary dictionaryWithDictionary:mRegDict];
[mRegDict release];
return result;
}
#pragma mark -
#pragma mark Private methods
+ (NSString *) _applicationNameForGrowlSearchingRegistrationDictionary:(NSDictionary *)regDict {
NSString *applicationNameForGrowl = nil;
if (delegate && [delegate respondsToSelector:@selector(applicationNameForGrowl)])
applicationNameForGrowl = [delegate applicationNameForGrowl];
if (!applicationNameForGrowl)
applicationNameForGrowl = [regDict objectForKey:GROWL_APP_NAME];
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 (!iconData)
iconData = [regDict objectForKey:GROWL_APP_ICON];
if (!iconData) {
NSURL *URL = copyCurrentProcessURL();
iconData = [copyIconDataForURL(URL) autorelease];
[URL release];
}
return iconData;
}
/*Selector called when a growl notification is clicked. This should never be
* 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) _growlNotificationTimedOut:(NSNotification *)notification {
[delegate performSelector:@selector(growlNotificationTimedOut:)
withObject:[[notification userInfo] objectForKey:GROWL_KEY_CLICKED_CONTEXT]];
}
#pragma mark -
+ (void) _growlIsReady:(NSNotification *)notification {
#pragma unused(notification)
//Growl has now launched; we may get here with (growlLaunched == NO) when the user first installs
growlLaunched = YES;
//Inform our delegate if it is interested
if ([delegate respondsToSelector:@selector(growlIsReady)])
[delegate growlIsReady];
//Post a notification locally
[[NSNotificationCenter defaultCenter] postNotificationName:GROWL_IS_READY
object:nil];
//Stop observing for GROWL_IS_READY
NSDistributedNotificationCenter *distCenter = [NSDistributedNotificationCenter defaultCenter];
[distCenter 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) {
[self reregisterGrowlNotifications];
registerWhenGrowlIsReady = NO;
}
#ifdef GROWL_WITH_INSTALLER
//Perform any queued notifications
NSEnumerator *enumerator = [queuedGrowlNotifications objectEnumerator];
NSDictionary *noteDict;
while ((noteDict = [enumerator nextObject])) {
NSConnection *connection = [NSConnection connectionWithRegisteredName:@"GrowlApplicationBridgePathway" host:nil];
if (connection) {
//Post to Growl via GrowlApplicationBridgePathway
NS_DURING
NSDistantObject *theProxy = [connection rootProxy];
[theProxy setProtocolForProxy:@protocol(GrowlNotificationProtocol)];
id<GrowlNotificationProtocol> growlProxy = (id)theProxy;
[growlProxy 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];
}
}
[queuedGrowlNotifications release]; queuedGrowlNotifications = nil;
#endif
}
#ifdef GROWL_WITH_INSTALLER
/*Sent to us by GrowlInstallationPrompt if the user clicks Cancel so we can
* avoid prompting again this session (or ever if they checked Don't Ask Again)
*/
+ (void) _userChoseNotToInstallGrowl {
//Note the user's action so we stop queueing notifications, etc.
userChoseNotToInstallGrowl = YES;
//Clear our queued notifications; we won't be needing them
[queuedGrowlNotifications release]; queuedGrowlNotifications = nil;
}
// Check against our current version number and ensure the installed Growl pane is the same or later
+ (void) _checkForPackagedUpdateForGrowlPrefPaneBundle:(NSBundle *)growlPrefPaneBundle {
NSString *ourGrowlPrefPaneInfoPath;
NSDictionary *infoDictionary;
NSString *packagedVersion, *installedVersion;
BOOL upgradeIsAvailable;
ourGrowlPrefPaneInfoPath = [[NSBundle bundleForClass:[self class]] pathForResource:@"GrowlPrefPaneInfo"
ofType:@"plist"];
NSDictionary *infoDict = [[NSDictionary alloc] initWithContentsOfFile:ourGrowlPrefPaneInfoPath];
packagedVersion = [infoDict objectForKey:(NSString *)kCFBundleVersionKey];
infoDictionary = [growlPrefPaneBundle infoDictionary];
installedVersion = [infoDictionary objectForKey:(NSString *)kCFBundleVersionKey];
//If the installed version is earlier than our packaged version, we can offer an upgrade.
upgradeIsAvailable = (compareVersionStringsTranslating1_0To0_5(packagedVersion, installedVersion) == kCFCompareGreaterThan);
if (upgradeIsAvailable && !promptedToUpgradeGrowl) {
NSString *lastDoNotPromptVersion = [[NSUserDefaults standardUserDefaults] objectForKey:@"Growl Update:Do Not Prompt Again:Last Version"];
if (!lastDoNotPromptVersion ||
(compareVersionStringsTranslating1_0To0_5(packagedVersion, lastDoNotPromptVersion) == kCFCompareGreaterThan))
{
[GrowlInstallationPrompt showUpdatePromptForVersion:packagedVersion];
promptedToUpgradeGrowl = YES;
}
}
[infoDict release];
}
#endif
#pragma mark -
+ (BOOL) _launchGrowlIfInstalledWithRegistrationDictionary:(NSDictionary *)regDict {
NSBundle *growlPrefPaneBundle;
BOOL success = NO;
growlPrefPaneBundle = [GrowlPathUtil growlPrefPaneBundle];
if (growlPrefPaneBundle) {
NSString *growlHelperAppPath = [growlPrefPaneBundle pathForResource:@"GrowlHelperApp"
ofType:@"app"];
#ifdef GROWL_WITH_INSTALLER
/* Check against our current version number and ensure the installed Growl pane is the same or later */
[self _checkForPackagedUpdateForGrowlPrefPaneBundle:growlPrefPaneBundle];
#endif
//Houston, we are go for launch.
if (growlHelperAppPath) {
//Let's launch in the background (unfortunately, requires Carbon on Jaguar)
LSLaunchFSRefSpec spec;
FSRef appRef;
OSStatus status = FSPathMakeRef((UInt8 *)[growlHelperAppPath fileSystemRepresentation], &appRef, NULL);
if (status == noErr) {
FSRef regItemRef;
BOOL passRegDict = NO;
if (regDict) {
OSStatus regStatus;
NSString *regDictFileName;
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) {
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) {
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;
NSString *error;
plistData = [NSPropertyListSerialization dataFromPropertyList:regDict
format:NSPropertyListBinaryFormat_v1_0
errorDescription:&error];
if (plistData) {
success = [plistData writeToFile:regDictPath atomically:NO];
} else {
NSLog(@"GrowlApplicationBridge: Error writing registration dictionary at %@: %@", regDictPath, error);
NSLog(@"GrowlApplicationBridge: Registration dictionary follows\n%@", regDict);
[error release];
}
regStatus = FSPathMakeRef((UInt8 *)[regDictPath fileSystemRepresentation], &regItemRef, NULL);
if (regStatus == noErr) {
passRegDict = YES;
}
}
spec.appRef = &appRef;
spec.numDocs = (passRegDict != NO);
spec.itemRefs = (passRegDict ? &regItemRef : NULL);
spec.passThruParams = NULL;
spec.launchFlags = kLSLaunchDontAddToRecents | kLSLaunchDontSwitch | kLSLaunchNoParams | kLSLaunchAsync;
spec.asyncRefCon = NULL;
status = LSOpenFromRefSpec(&spec, NULL);
success = (status == noErr);
}
}
}
return success;
}
/* + (BOOL)launchGrowlIfInstalled
*
*Returns YES if the Growl helper app began launching or was already running.
*Returns NO and performs no other action if the Growl prefPane is not properly
* installed.
*If Growl is installed but disabled, the application will be registered and
* GrowlHelperApp will then quit. This method will still return YES if Growl
* is installed but disabled.
*/
+ (BOOL) launchGrowlIfInstalled {
return [self _launchGrowlIfInstalledWithRegistrationDictionary:nil];
}
@end

View File

@ -0,0 +1,307 @@
//
// GrowlDefines.h
//
#ifndef _GROWLDEFINES_H
#define _GROWLDEFINES_H
#ifdef __OBJC__
#define XSTR(x) (@x)
#define STRING NSString *
#else
#define XSTR CFSTR
#define STRING CFStringRef
#endif
/*! @header GrowlDefines.h
* @abstract Defines all the notification keys.
* @discussion Defines all the keys used for registration with Growl and for
* Growl notifications.
*
* Most applications should use the functions or methods of Growl.framework
* instead of posting notifications such as those described here.
* @updated 2004-01-25
*/
// UserInfo Keys for Registration
#pragma mark UserInfo Keys for Registration
/*! @group Registration userInfo keys */
/* @abstract Keys for the userInfo dictionary of a GROWL_APP_REGISTRATION distributed notification.
* @discussion The values of these keys describe the application and the
* notifications it may post.
*
* Your application must register with Growl before it can post Growl
* notifications (and have them not be ignored). However, as of Growl 0.6,
* posting GROWL_APP_REGISTRATION notifications directly is no longer the
* preferred way to register your application. Your application should instead
* use Growl.framework's delegate system.
* See +[GrowlApplicationBridge setGrowlDelegate:] or Growl_SetDelegate for
* more information.
*/
/*! @defined GROWL_APP_NAME
* @abstract The name of your application.
* @discussion The name of your application. This should remain stable between
* different versions and incarnations of your application.
* For example, "SurfWriter" is a good app name, whereas "SurfWriter 2.0" and
* "SurfWriter Lite" are not.
*/
#define GROWL_APP_NAME XSTR("ApplicationName")
/*! @defined GROWL_APP_ICON
* @abstract The image data for your application's icon.
* @discussion Image data representing your application's icon. This may be
* superimposed on a notification icon as a badge, used as the notification
* icon when a notification-specific icon is not supplied, or ignored
* altogether, depending on the display. Must be in a format supported by
* NSImage, such as TIFF, PNG, GIF, JPEG, BMP, PICT, or PDF.
*
* Optional. Not supported by all display plugins.
*/
#define GROWL_APP_ICON XSTR("ApplicationIcon")
/*! @defined GROWL_NOTIFICATIONS_DEFAULT
* @abstract The array of notifications to turn on by default.
* @discussion These are the names of the notifications that should be enabled
* by default when your application registers for the first time. If your
* application reregisters, Growl will look here for any new notification
* names found in GROWL_NOTIFICATIONS_ALL, but ignore any others.
*/
#define GROWL_NOTIFICATIONS_DEFAULT XSTR("DefaultNotifications")
/*! @defined GROWL_NOTIFICATIONS_ALL
* @abstract The array of all notifications your application can send.
* @discussion These are the names of all of the notifications that your
* application may post. See GROWL_NOTIFICATION_NAME for a discussion of good
* notification names.
*/
#define GROWL_NOTIFICATIONS_ALL XSTR("AllNotifications")
/*! @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
* application bundle for auto-discovery. The current ticket version is 1.
*/
#define GROWL_TICKET_VERSION XSTR("TicketVersion")
// UserInfo Keys for Notifications
#pragma mark UserInfo Keys for Notifications
/*! @group Notification userInfo keys */
/* @abstract Keys for the userInfo dictionary of a GROWL_NOTIFICATION distributed notification.
* @discussion The values of these keys describe the content of a Growl
* notification.
*
* Not all of these keys are supported by all displays. Only the name, title,
* and description of a notification are universal. Most of the built-in
* displays do support all of these keys, and most other visual displays
* probably will also. But, as of 0.6, the Log, MailMe, and Speech displays
* support only textual data.
*/
/*! @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. */
#define GROWL_NOTIFICATION_NAME XSTR("NotificationName")
/*! @defined GROWL_NOTIFICATION_TITLE
* @abstract The title to display in the notification.
* @discussion The title of the notification. Should be very brief.
* The title usually says what happened, e.g. "Download complete".
*/
#define GROWL_NOTIFICATION_TITLE XSTR("NotificationTitle")
/*! @defined GROWL_NOTIFICATION_DESCRIPTION
* @abstract The description to display in the notification.
* @discussion The description should be longer and more verbose than the title.
* The description usually tells the subject of the action,
* e.g. "Growl-0.6.dmg downloaded in 5.02 minutes".
*/
#define GROWL_NOTIFICATION_DESCRIPTION XSTR("NotificationDescription")
/*! @defined GROWL_NOTIFICATION_ICON
* @discussion Image data for the notification icon. Must be in a format
* supported by NSImage, such as TIFF, PNG, GIF, JPEG, BMP, PICT, or PDF.
*
* Optional. Not supported by all display plugins.
*/
#define GROWL_NOTIFICATION_ICON XSTR("NotificationIcon")
/*! @defined GROWL_NOTIFICATION_APP_ICON
* @discussion Image data for the application icon, in case GROWL_APP_ICON does
* not apply for some reason. Must be in a format supported by NSImage, such
* as TIFF, PNG, GIF, JPEG, BMP, PICT, or PDF.
*
* Optional. Not supported by all display plugins.
*/
#define GROWL_NOTIFICATION_APP_ICON XSTR("NotificationAppIcon")
/*! @defined GROWL_NOTIFICATION_PRIORITY
* @discussion The priority of the notification as an integer number from
* -2 to +2 (+2 being highest).
*
* Optional. Not supported by all display plugins.
*/
#define GROWL_NOTIFICATION_PRIORITY XSTR("NotificationPriority")
/*! @defined GROWL_NOTIFICATION_STICKY
* @discussion A Boolean number controlling whether the notification is sticky.
*
* Optional. Not supported by all display plugins.
*/
#define GROWL_NOTIFICATION_STICKY XSTR("NotificationSticky")
/*! @defined GROWL_NOTIFICATION_CLICK_CONTEXT
* @abstract Identifies which notification was clicked.
* @discussion An identifier for the notification for clicking purposes.
*
* This will be passed back to the application when the notification is
* clicked. It must be plist-encodable (a data, dictionary, array, number, or
* string object), and it should be unique for each notification you post.
* A good click context would be a UUID string returned by NSProcessInfo or
* CFUUID.
*
* Optional. Not supported by all display plugins.
*/
#define GROWL_NOTIFICATION_CLICK_CONTEXT XSTR("NotificationClickContext")
/*! @defined GROWL_DISPLAY_PLUGIN
* @discussion The name of a display plugin which should be used for this notification.
* Optional. If this key is not set or the specified display plugin does not
* exist, the display plugin stored in the application ticket is used. This key
* allows applications to use different default display plugins for their
* notifications. The user can still override those settings in the preference
* pane.
*/
#define GROWL_DISPLAY_PLUGIN XSTR("NotificationDisplayPlugin")
/*! @defined GROWL_NOTIFICATION_IDENTIFIER
* @abstract An identifier for the notification for coalescing purposes.
* Notifications with the same identifier fall into the same class; only
* the last notification of a class is displayed on the screen. If a
* notification of the same class is currently being displayed, it is
* replaced by this notification.
*
* Optional. Not supported by all display plugins.
*/
#define GROWL_NOTIFICATION_IDENTIFIER XSTR("GrowlNotificationIdentifier")
/*! @defined GROWL_APP_PID
* @abstract The process identifier of the process which sends this
* notification. If this field is set, the application will only receive
* clicked and timed out notifications which originate from this process.
*
* Optional.
*/
#define GROWL_APP_PID XSTR("ApplicationPID")
// Notifications
#pragma mark Notifications
/*! @group Notification names */
/* @abstract Names of distributed notifications used by Growl.
* @discussion These are notifications used by applications (directly or
* indirectly) to interact with Growl, and by Growl for interaction between
* its components.
*
* Most of these should no longer be used in Growl 0.6 and later, in favor of
* Growl.framework's GrowlApplicationBridge APIs.
*/
/*! @defined GROWL_APP_REGISTRATION
* @abstract The distributed notification for registering your application.
* @discussion This is the name of the distributed notification that can be
* used to register applications with Growl.
*
* The userInfo dictionary for this notification can contain these keys:
* <ul>
* <li>GROWL_APP_NAME</li>
* <li>GROWL_APP_ICON</li>
* <li>GROWL_NOTIFICATIONS_ALL</li>
* <li>GROWL_NOTIFICATIONS_DEFAULT</li>
* </ul>
*
* No longer recommended as of Growl 0.6. An alternate method of registering
* is to use Growl.framework's delegate system.
* See +[GrowlApplicationBridge setGrowlDelegate:] or Growl_SetDelegate for
* more information.
*/
#define GROWL_APP_REGISTRATION XSTR("GrowlApplicationRegistrationNotification")
/*! @defined GROWL_APP_REGISTRATION_CONF
* @abstract The distributed notification for confirming registration.
* @discussion The name of the distributed notification sent to confirm the
* registration. Used by the Growl preference pane. Your application probably
* does not need to use this notification.
*/
#define GROWL_APP_REGISTRATION_CONF XSTR("GrowlApplicationRegistrationConfirmationNotification")
/*! @defined GROWL_NOTIFICATION
* @abstract The distributed notification for Growl notifications.
* @discussion This is what it all comes down to. This is the name of the
* distributed notification that your application posts to actually send a
* Growl notification.
*
* The userInfo dictionary for this notification can contain these keys:
* <ul>
* <li>GROWL_NOTIFICATION_NAME (required)</li>
* <li>GROWL_NOTIFICATION_TITLE (required)</li>
* <li>GROWL_NOTIFICATION_DESCRIPTION (required)</li>
* <li>GROWL_NOTIFICATION_ICON</li>
* <li>GROWL_NOTIFICATION_APP_ICON</li>
* <li>GROWL_NOTIFICATION_PRIORITY</li>
* <li>GROWL_NOTIFICATION_STICKY</li>
* <li>GROWL_NOTIFICATION_CLICK_CONTEXT</li>
* <li>GROWL_APP_NAME (required)</li>
* </ul>
*
* No longer recommended as of Growl 0.6. Three alternate methods of posting
* notifications are +[GrowlApplicationBridge notifyWithTitle:description:notificationName:iconData:priority:isSticky:clickContext:],
* Growl_NotifyWithTitleDescriptionNameIconPriorityStickyClickContext, and
* Growl_PostNotification.
*/
#define GROWL_NOTIFICATION XSTR("GrowlNotification")
/*! @defined GROWL_SHUTDOWN
* @abstract The distributed notification name that tells Growl to shutdown.
* @discussion The Growl preference pane posts this notification when the
* "Stop Growl" button is clicked.
*/
#define GROWL_SHUTDOWN XSTR("GrowlShutdown")
/*! @defined GROWL_PING
* @abstract A distributed notification to check whether Growl is running.
* @discussion This is used by the Growl preference pane. If it receives a
* GROWL_PONG, the preference pane takes this to mean that Growl is running.
*/
#define GROWL_PING XSTR("Honey, Mind Taking Out The Trash")
/*! @defined GROWL_PONG
* @abstract The distributed notification sent in reply to GROWL_PING.
* @discussion GrowlHelperApp posts this in reply to GROWL_PING.
*/
#define GROWL_PONG XSTR("What Do You Want From Me, Woman")
/*! @defined GROWL_IS_READY
* @abstract The distributed notification sent when Growl starts up.
* @discussion GrowlHelperApp posts this when it has begin listening on all of
* its sources for new notifications. GrowlApplicationBridge (in
* Growl.framework), upon receiving this notification, reregisters using the
* registration dictionary supplied by its delegate.
*/
#define GROWL_IS_READY XSTR("Lend Me Some Sugar; I Am Your Neighbor!")
/*! @defined GROWL_NOTIFICATION_CLICKED
* @abstract The distributed notification sent when a supported notification is clicked.
* @discussion When a Growl notification with a click context is clicked on by
* the user, Growl posts this distributed notification.
* The GrowlApplicationBridge responds to this notification by calling a
* callback in its delegate.
*/
#define GROWL_NOTIFICATION_CLICKED XSTR("GrowlClicked!")
#define GROWL_NOTIFICATION_TIMED_OUT XSTR("GrowlTimedOut!")
/*! @group Other symbols */
/* Symbols which don't fit into any of the other categories. */
/*! @defined GROWL_KEY_CLICKED_CONTEXT
* @abstract Used internally as the key for the clickedContext passed over DNC.
* @discussion This key is used in GROWL_NOTIFICATION_CLICKED, and contains the
* click context that was supplied in the original notification.
*/
#define GROWL_KEY_CLICKED_CONTEXT XSTR("ClickedContext")
/*! @defined GROWL_REG_DICT_EXTENSION
* @abstract The filename extension for registration dictionaries.
* @discussion The GrowlApplicationBridge in Growl.framework registers with
* Growl by creating a file with the extension of .(GROWL_REG_DICT_EXTENSION)
* and opening it in the GrowlHelperApp. This happens whether or not Growl is
* running; if it was stopped, it quits immediately without listening for
* notifications.
*/
#define GROWL_REG_DICT_EXTENSION XSTR("growlRegDict")
#endif //ndef _GROWLDEFINES_H

View File

@ -0,0 +1,386 @@
//
// GrowlDefinesInternal.h
// Growl
//
// Created by Karl Adam on Mon May 17 2004.
// Copyright (c) 2004 the Growl Project. All rights reserved.
//
#ifndef _GROWL_GROWLDEFINESINTERNAL_H
#define _GROWL_GROWLDEFINESINTERNAL_H
#include <CoreFoundation/CoreFoundation.h>
#ifdef __OBJC__
#define XSTR(x) (@x)
#else /* !__OBJC__ */
#define XSTR CFSTR
#endif /* __OBJC__ */
/*! @header GrowlDefinesInternal.h
* @abstract Defines internal Growl macros and types.
* @ignore ATTRIBUTE_PACKED
* @discussion These constants are used both by GrowlHelperApp and by plug-ins.
*
* Notification keys (used in GrowlHelperApp, in GrowlApplicationBridge, and
* by applications that don't use GrowlApplicationBridge) are defined in
* GrowlDefines.h.
*/
/*! @defined GROWL_TCP_PORT
* @abstract The TCP listen port for Growl notification servers.
*/
#define GROWL_TCP_PORT 23052
/*! @defined GROWL_UDP_PORT
* @abstract The UDP listen port for Growl notification servers.
*/
#define GROWL_UDP_PORT 9887
/*! @defined GROWL_PROTOCOL_VERSION
* @abstract The current version of the Growl network-notifications protocol (without encryption).
*/
#define GROWL_PROTOCOL_VERSION 1
/*! @defined GROWL_PROTOCOL_VERSION_AES128
* @abstract The current version of the Growl network-notifications protocol (with AES-128 encryption).
*/
#define GROWL_PROTOCOL_VERSION_AES128 2
/*! @defined GROWL_TYPE_REGISTRATION
* @abstract The packet type of registration packets with MD5 authentication.
*/
#define GROWL_TYPE_REGISTRATION 0
/*! @defined GROWL_TYPE_NOTIFICATION
* @abstract The packet type of notification packets with MD5 authentication.
*/
#define GROWL_TYPE_NOTIFICATION 1
/*! @defined GROWL_TYPE_REGISTRATION_SHA256
* @abstract The packet type of registration packets with SHA-256 authentication.
*/
#define GROWL_TYPE_REGISTRATION_SHA256 2
/*! @defined GROWL_TYPE_NOTIFICATION_SHA256
* @abstract The packet type of notification packets with SHA-256 authentication.
*/
#define GROWL_TYPE_NOTIFICATION_SHA256 3
/*! @defined GROWL_TYPE_REGISTRATION_NOAUTH
* @abstract The packet type of registration packets without authentication.
*/
#define GROWL_TYPE_REGISTRATION_NOAUTH 4
/*! @defined GROWL_TYPE_NOTIFICATION_NOAUTH
* @abstract The packet type of notification packets without authentication.
*/
#define GROWL_TYPE_NOTIFICATION_NOAUTH 5
#define ATTRIBUTE_PACKED __attribute((packed))
/*! @struct GrowlNetworkPacket
* @abstract This struct is a header common to all incoming Growl network
* packets which identifies the type and version of the packet.
*/
struct GrowlNetworkPacket {
unsigned char version;
unsigned char type;
} ATTRIBUTE_PACKED;
/*!
* @struct GrowlNetworkRegistration
* @abstract The format of a registration packet.
* @discussion A Growl client that wants to register with a Growl server sends
* a packet in this format.
* @field common The Growl packet header.
* @field appNameLen The name of the application that is registering.
* @field numAllNotifications The number of notifications in the list.
* @field numDefaultNotifications The number of notifications in the list that are enabled by default.
* @field data Variable-sized data.
*/
struct GrowlNetworkRegistration {
struct GrowlNetworkPacket common;
/* This name is used both internally and in the Growl
* preferences.
*
* The application name should remain stable between different versions
* and incarnations of your application.
* For example, "SurfWriter" is a good app name, whereas "SurfWriter 2.0"
* and "SurfWriter Lite" are not.
*
* In addition to being unsigned, the application name length is in
* network byte order.
*/
unsigned short appNameLen;
/* These names are used both internally and in the Growl
* preferences. For this reason, they should be human-readable.
*/
unsigned char numAllNotifications;
unsigned char numDefaultNotifications;
/* The variable-sized data of a registration is:
* - The application name, in UTF-8 encoding, for appNameLen bytes.
* - The list of all notification names.
* - The list of default notifications, as 8-bit unsigned indices into the list of all notifications.
* - The MD5/SHA256 checksum of all the data preceding the checksum.
*
* Each notification name is encoded as:
* - Length: two bytes, unsigned, network byte order.
* - Name: As many bytes of UTF-8-encoded text as the length says.
* And there are numAllNotifications of these.
*/
unsigned char data[];
} ATTRIBUTE_PACKED;
/*!
* @struct GrowlNetworkNotification
* @abstract The format of a notification packet.
* @discussion A Growl client that wants to post a notification to a Growl
* server sends a packet in this format.
* @field common The Growl packet header.
* @field flags The priority number and the sticky bit.
* @field nameLen The length of the notification name.
* @field titleLen The length of the notification title.
* @field descriptionLen The length of the notification description.
* @field appNameLen The length of the application name.
* @field data Variable-sized data.
*/
struct GrowlNetworkNotification {
struct GrowlNetworkPacket common;
/*!
* @struct GrowlNetworkNotificationFlags
* @abstract Various flags.
* @discussion This 16-bit packed structure contains the priority as a
* signed 3-bit integer from -2 to +2, and the sticky flag as a single bit.
* The high 12 bits of the structure are reserved for future use.
* @field reserved reserved for future use.
* @field priority the priority as a signed 3-bit integer from -2 to +2.
* @field sticky the sticky flag.
*/
struct GrowlNetworkNotificationFlags {
unsigned reserved: 12;
signed priority: 3;
unsigned sticky: 1;
} ATTRIBUTE_PACKED flags; //size = 16 (12 + 3 + 1)
/* In addition to being unsigned, the notification name length
* is in network byte order.
*/
unsigned short nameLen;
/* @discussion In addition to being unsigned, the title length is in
* network byte order.
*/
unsigned short titleLen;
/* In addition to being unsigned, the description length is in
* network byte order.
*/
unsigned short descriptionLen;
/* In addition to being unsigned, the application name length
* is in network byte order.
*/
unsigned short appNameLen;
/* The variable-sized data of a notification is:
* - Notification name, in UTF-8 encoding, for nameLen bytes.
* - Title, in UTF-8 encoding, for titleLen bytes.
* - Description, in UTF-8 encoding, for descriptionLen bytes.
* - Application name, in UTF-8 encoding, for appNameLen bytes.
* - The MD5/SHA256 checksum of all the data preceding the checksum.
*/
unsigned char data[];
} ATTRIBUTE_PACKED;
/*! @defined GrowlEnabledKey
* @abstract Preference key controlling whether Growl is enabled.
* @discussion If this is false, then when GrowlHelperApp is launched to open
* a Growl registration dictionary file, GrowlHelperApp will quit when it has
* finished processing the file instead of listening for notifications.
*/
#define GrowlEnabledKey XSTR("GrowlEnabled")
/*! @defined GROWL_SCREENSHOT_MODE
* @abstract Preference and notification key controlling whether to save a screenshot of the notification.
* @discussion This is for GHA's private usage. If your application puts this
* key into a notification dictionary, GHA will clobber it. This key is only
* allowed in the notification dictionaries GHA passes to displays.
*
* If this key contains an object whose boolValue is not NO, the display is
* asked to save a screenshot of the notification to
* ~/Library/Application\ Support/Growl/Screenshots.
*/
#define GROWL_SCREENSHOT_MODE XSTR("ScreenshotMode")
/*! @defined GROWL_APP_LOCATION
* @abstract The location of this application.
* @discussion Contains either the POSIX path to the application, or a file-data dictionary (as used by the Dock).
* contains the file's alias record and its pathname.
*/
#define GROWL_APP_LOCATION XSTR("AppLocation")
#endif //ndef _GROWL_GROWLDEFINESINTERNAL_H
/* --- 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
* plug-ins (when the prefpane is selected).
*/
#define SYNCHRONIZE_GROWL_PREFS() CFPreferencesAppSynchronize(CFSTR("com.Growl.GrowlHelperApp"))
/*! @function UPDATE_GROWL_PREFS
* @abstract Tells GrowlHelperApp to update its prefs.
* @discussion This macro is intended for use by plug-ins.
* It sends a notification to tell GrowlHelperApp to update its preferences.
*/
#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];\
} while(0)
/*! @function READ_GROWL_PREF_VALUE
* @abstract Reads the given pref value from the plug-in's preferences.
* @discussion This macro is intended for use by plug-ins. It reads the value for the
* given key from the plug-in's preferences (which are stored in a dictionary inside of
* GrowlHelperApp's prefs).
* @param key The preference key to read the value of.
* @param domain The bundle ID of the plug-in.
* @param type The type of the result expected.
* @param result A pointer to an id. Set to the value if exists, left unchanged if not.
*
* If the value is set, you are responsible for releasing it.
*/
#define READ_GROWL_PREF_VALUE(key, domain, type, result) do {\
CFDictionaryRef prefs = (CFDictionaryRef)CFPreferencesCopyAppValue((CFStringRef)domain, \
CFSTR("com.Growl.GrowlHelperApp")); \
if (prefs) {\
if (CFDictionaryContainsKey(prefs, key)) {\
*result = (type)CFDictionaryGetValue(prefs, key); \
CFRetain(*result); \
} \
CFRelease(prefs); } \
} while(0)
/*! @function WRITE_GROWL_PREF_VALUE
* @abstract Writes the given pref value to the plug-in's preferences.
* @discussion This macro is intended for use by plug-ins. It writes the given
* value to the plug-in's preferences.
* @param key The preference key to write the value of.
* @param value The value to write to the preferences. It should be either a
* CoreFoundation type or toll-free bridged with one.
* @param domain The bundle ID of the plug-in.
*/
#define WRITE_GROWL_PREF_VALUE(key, value, domain) do {\
CFDictionaryRef staticPrefs = (CFDictionaryRef)CFPreferencesCopyAppValue((CFStringRef)domain, \
CFSTR("com.Growl.GrowlHelperApp")); \
CFMutableDictionaryRef prefs; \
if (staticPrefs == NULL) {\
prefs = CFDictionaryCreateMutable(NULL, 0, NULL, NULL); \
} else {\
prefs = CFDictionaryCreateMutableCopy(NULL, 0, staticPrefs); \
CFRelease(staticPrefs); \
}\
CFDictionarySetValue(prefs, key, value); \
CFPreferencesSetAppValue((CFStringRef)domain, prefs, CFSTR("com.Growl.GrowlHelperApp")); \
CFRelease(prefs); } while(0)
/*! @function READ_GROWL_PREF_BOOL
* @abstract Reads the given Boolean from the plug-in's preferences.
* @discussion This is a wrapper around READ_GROWL_PREF_VALUE() intended for
* use with Booleans.
* @param key The preference key to read the Boolean from.
* @param domain The bundle ID of the plug-in.
* @param result A pointer to a Boolean type. Left unchanged if the value doesn't exist.
*/
#define READ_GROWL_PREF_BOOL(key, domain, result) do {\
CFBooleanRef boolValue = NULL; \
READ_GROWL_PREF_VALUE(key, domain, CFBooleanRef, &boolValue); \
if (boolValue) {\
*result = CFBooleanGetValue(boolValue); \
CFRelease(boolValue); \
} } while(0)
/*! @function WRITE_GROWL_PREF_BOOL
* @abstract Writes the given Boolean to the plug-in's preferences.
* @discussion This is a wrapper around WRITE_GROWL_PREF_VALUE() intended for
* use with Booleans.
* @param key The preference key to write the Boolean for.
* @param value The Boolean value to write to the preferences.
* @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)
/*! @function READ_GROWL_PREF_INT
* @abstract Reads the given integer from the plug-in's preferences.
* @discussion This is a wrapper around READ_GROWL_PREF_VALUE() intended for
* use with integers.
* @param key The preference key to read the integer from.
* @param domain The bundle ID of the plug-in.
* @param result A pointer to an integer. Leaves unchanged if the value doesn't exist.
*/
#define READ_GROWL_PREF_INT(key, domain, result) do {\
CFNumberRef intValue = NULL; \
READ_GROWL_PREF_VALUE(key, domain, CFNumberRef, &intValue); \
if (intValue) {\
CFNumberGetValue(intValue, kCFNumberIntType, result); \
CFRelease(intValue); \
} } while(0)
/*! @function WRITE_GROWL_PREF_INT
* @abstract Writes the given integer to the plug-in's preferences.
* @discussion This is a wrapper around WRITE_GROWL_PREF_VALUE() intended for
* use with integers.
* @param key The preference key to write the integer for.
* @param value The integer value to write to the preferences.
* @param domain The bundle ID of the plug-in.
*/
#define WRITE_GROWL_PREF_INT(key, value, domain) do {\
CFNumberRef intValue = CFNumberCreate(NULL, kCFNumberIntType, &value); \
WRITE_GROWL_PREF_VALUE(key, intValue, domain); \
CFRelease(intValue); } while(0)
/*! @function READ_GROWL_PREF_FLOAT
* @abstract Reads the given float from the plug-in's preferences.
* @discussion This is a wrapper around READ_GROWL_PREF_VALUE() intended for
* use with floats.
* @param key The preference key to read the float from.
* @param domain The bundle ID of the plug-in.
* @param result A pointer to a float. Leaves unchanged if the value doesn't exist.
*/
#define READ_GROWL_PREF_FLOAT(key, domain, result) do {\
CFNumberRef floatValue = NULL; \
READ_GROWL_PREF_VALUE(key, domain, CFNumberRef, &floatValue); \
if (floatValue) {\
CFNumberGetValue(floatValue, kCFNumberFloatType, result); \
CFRelease(floatValue); \
} } while(0)
/*! @function WRITE_GROWL_PREF_FLOAT
* @abstract Writes the given float to the plug-in's preferences.
* @discussion This is a wrapper around WRITE_GROWL_PREF_VALUE() intended for
* use with floats.
* @param key The preference key to write the float for.
* @param value The float value to write to the preferences.
* @param domain The bundle ID of the plug-in.
*/
#define WRITE_GROWL_PREF_FLOAT(key, value, domain) do {\
CFNumberRef floatValue = CFNumberCreate(NULL, kCFNumberFloatType, &value); \
WRITE_GROWL_PREF_VALUE(key, floatValue, domain); \
CFRelease(floatValue); } while(0)
#endif /* __OBJC__ */

View File

@ -0,0 +1,30 @@
//
// 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

@ -0,0 +1,171 @@
//
// 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,24 @@
//
// GrowlNotificationServer.h
// Growl
//
// Created by Ingmar Stein on 15.11.04.
// 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>
@protocol GrowlNotificationProtocol
- (oneway void) registerApplicationWithDictionary:(bycopy NSDictionary *)dict;
- (oneway void) postNotificationWithDictionary:(bycopy NSDictionary *)notification;
- (bycopy NSString *) growlVersion;
@end
@interface GrowlPathway : NSObject <GrowlNotificationProtocol> {
}
- (void) registerApplicationWithDictionary:(NSDictionary *)dict;
- (void) postNotificationWithDictionary:(NSDictionary *)dict;
@end

View File

@ -0,0 +1,69 @@
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is Growl implementation of nsIAlertsService.
#
# The Initial Developer of the Original Code is
# Shawn Wilsher <me@shawnwilsher.com>.
# Portions created by the Initial Developer are Copyright (C) 2006
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****
DEPTH = ../../../../../..
topsrcdir = @top_srcdir@
srcdir = @srcdir@
VPATH = @srcdir@
include $(DEPTH)/config/autoconf.mk
MODULE = alerts
LIBRARY_NAME = growl_s
FORCE_STATIC_LIB = 1
LIBXUL_LIBRARY = 1
CMSRCS = \
GrowlApplicationBridge.m \
GrowlPathUtil.m \
NSURLAdditions.m \
$(NULL)
CSRCS = \
CFGrowlAdditions.c \
$(NULL)
include $(topsrcdir)/config/rules.mk
LDFLAGS += \
-framework Carbon \
-framework Foundation \
-framework AppKit \
$(NULL)
EXTRA_DSO_LDOPTS += \
$(MOZ_COMPONENT_LIBS) \
$(NULL)

View File

@ -0,0 +1,23 @@
//
// 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

@ -0,0 +1,132 @@
//
// 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

View File

@ -0,0 +1,27 @@
Copyright (c) The Growl Project, 2004-2007
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of Growl nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,102 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Growl implementation of nsIAlertsService.
*
* The Initial Developer of the Original Code is
* Shawn Wilsher <me@shawnwilsher.com>.
* Portions created by the Initial Developer are Copyright (C) 2006-2007
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#import "GrowlApplicationBridge.h"
#include "nsIObserver.h"
#import <Cocoa/Cocoa.h>
// XXX should this be localized? Bug 378527
#define NOTIFICATION_NAME @"Application Notice"
#define OBSERVER_KEY @"ALERT_OBSERVER"
#define COOKIE_KEY @"ALERT_COOKIE"
@interface mozGrowlDelegate : NSObject <GrowlApplicationBridgeDelegate>
{
@private
PRUint32 mKey;
NSMutableDictionary* mDict;
}
/**
* Dispatches a notification to Growl
*
* @param aTitle The title of the notification
* @param aText The body of the notification
* @param aImage The image data, or [NSData data] if no image
* @param aKey The observer key to use as a lookup (or 0 if no observer)
* @param aCookie The string to be used as a cookie if there is an observer
*/
+ (void) notifyWithTitle:(const nsAString&)aTitle
description:(const nsAString&)aText
iconData:(NSData*)aImage
key:(PRUint32)aKey
cookie:(const nsAString&)aCookie;
/**
* Adds an nsIObserver that we can query later for dispatching obsevers.
*
* @param aObserver The observer we are adding.
* @return The key it was stored in.
*/
- (PRUint32) addObserver:(nsIObserver*)aObserver;
/**
* Gives Growl the application name.
*/
- (NSString*) applicationNameForGrowl;
/**
* Gives Growl the complete list of notifications this application will ever
* send, and also which notifications should be enabled by default.
*/
- (NSDictionary *) registrationDictionaryForGrowl;
/**
* Informs us that a Growl notification timed out.
*
* @param clickContext The object passed back from growl.
*/
- (void) growlNotificationTimedOut:(id)clickContext;
/**
* Informs us that a Growl notification was clicked.
*
* @param clickContext The object passed back from growl.
*/
- (void) growlNotificationWasClicked:(id)clickContext;
@end

View File

@ -0,0 +1,181 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Growl implementation of nsIAlertsService.
*
* The Initial Developer of the Original Code is
* Shawn Wilsher <me@shawnwilsher.com>.
* Portions created by the Initial Developer are Copyright (C) 2006-2007
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#import "mozGrowlDelegate.h"
#include "nsIObserver.h"
#include "nsString.h"
#include "nscore.h"
#include "nsCOMPtr.h"
#include "nsServiceManagerUtils.h"
#include "nsIXULAppInfo.h"
@implementation mozGrowlDelegate
- (id) init
{
if ((self = [super init])) {
mKey = 0;
mDict = [[NSMutableDictionary dictionaryWithCapacity: 8] retain];
}
return self;
}
- (void) dealloc
{
[mDict release];
[super dealloc];
}
+ (void) notifyWithTitle:(const nsAString&)aTitle
description:(const nsAString&)aText
iconData:(NSData*)aImage
key:(PRUint32)aKey
cookie:(const nsAString&)aCookie
{
NSDictionary* clickContext = nil;
if (aKey) {
clickContext = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithUnsignedInt: aKey],
OBSERVER_KEY,
[NSArray arrayWithObject:
[NSString stringWithCharacters: aCookie.BeginReading()
length: aCookie.Length()]],
COOKIE_KEY,
nil];
}
[GrowlApplicationBridge
notifyWithTitle: [NSString stringWithCharacters: aTitle.BeginReading()
length: aTitle.Length()]
description: [NSString stringWithCharacters: aText.BeginReading()
length: aText.Length()]
notificationName: NOTIFICATION_NAME
iconData: aImage
priority: 0
isSticky: NO
clickContext: clickContext];
}
- (PRUint32) addObserver:(nsIObserver*)aObserver
{
NS_ADDREF(aObserver); // We now own a reference to this!
mKey++;
[mDict setObject: [NSValue valueWithPointer: aObserver]
forKey: [NSNumber numberWithUnsignedInt: mKey]];
return mKey;
}
- (NSDictionary *) registrationDictionaryForGrowl
{
return [NSDictionary dictionaryWithObjectsAndKeys:
[NSArray arrayWithObjects: NOTIFICATION_NAME, nil],
GROWL_NOTIFICATIONS_ALL,
[NSArray arrayWithObjects: NOTIFICATION_NAME, nil],
GROWL_NOTIFICATIONS_DEFAULT,
nil];
}
- (NSString*) applicationNameForGrowl
{
nsresult rv;
nsCOMPtr<nsIXULAppInfo> appInfo =
do_GetService("@mozilla.org/xre/app-info;1", &rv);
NS_ENSURE_SUCCESS(rv, nil);
nsCAutoString appName;
rv = appInfo->GetName(appName);
NS_ENSURE_SUCCESS(rv, nil);
nsAutoString name = NS_ConvertUTF8toUTF16(appName);
return [NSString stringWithCharacters: name.BeginReading()
length: name.Length()];
}
- (void) growlNotificationTimedOut:(id)clickContext
{
NS_ASSERTION([clickContext valueForKey: OBSERVER_KEY] != nil,
"OBSERVER_KEY not found!");
NS_ASSERTION([clickContext valueForKey: COOKIE_KEY] != nil,
"COOKIE_KEY not found!");
nsIObserver* observer = NS_STATIC_CAST(nsIObserver*,
[[mDict objectForKey: [clickContext valueForKey: OBSERVER_KEY]]
pointerValue]);
[mDict removeObjectForKey: [clickContext valueForKey: OBSERVER_KEY]];
NSString* cookie = [[clickContext valueForKey: COOKIE_KEY] objectAtIndex: 0];
if (observer) {
nsAutoString tmp;
tmp.SetLength([cookie length]);
[cookie getCharacters:tmp.BeginWriting()];
observer->Observe(nsnull, "alertfinished", tmp.get());
NS_RELEASE(observer);
}
}
- (void) growlNotificationWasClicked:(id)clickContext
{
NS_ASSERTION([clickContext valueForKey: OBSERVER_KEY] != nil,
"OBSERVER_KEY not found!");
NS_ASSERTION([clickContext valueForKey: COOKIE_KEY] != nil,
"COOKIE_KEY not found!");
nsIObserver* observer = NS_STATIC_CAST(nsIObserver*,
[[mDict objectForKey: [clickContext valueForKey: OBSERVER_KEY]]
pointerValue]);
[mDict removeObjectForKey: [clickContext valueForKey: OBSERVER_KEY]];
NSString* cookie = [[clickContext valueForKey: COOKIE_KEY] objectAtIndex: 0];
if (observer) {
nsAutoString tmp;
tmp.SetLength([cookie length]);
[cookie getCharacters:tmp.BeginWriting()];
observer->Observe(nsnull, "alertclickcallback", tmp.get());
observer->Observe(nsnull, "alertfinished", tmp.get());
NS_RELEASE(observer);
}
}
@end

View File

@ -0,0 +1,64 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Growl implementation of nsIAlertsService.
*
* The Initial Developer of the Original Code is
* Shawn Wilsher <me@shawnwilsher.com>.
* Portions created by the Initial Developer are Copyright (C) 2006-2007
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#ifndef nsAlertsImageLoadListener_h_
#define nsAlertsImageLoadListener_h_
#import "mozGrowlDelegate.h"
#include "nsIStreamLoader.h"
#include "nsString.h"
class nsAlertsImageLoadListener : public nsIStreamLoaderObserver
{
public:
nsAlertsImageLoadListener(const nsAString& aAlertTitle,
const nsAString& aAlertText,
PRBool aAlertClickable,
const nsAString& aAlertCookie,
PRUint32 aAlertListenerKey);
NS_DECL_ISUPPORTS
NS_DECL_NSISTREAMLOADEROBSERVER
private:
nsString mAlertTitle;
nsString mAlertText;
PRBool mAlertClickable;
nsString mAlertCookie;
PRUint32 mAlertListenerKey;
};
#endif // nsAlertsImageLoadListener_h_

View File

@ -0,0 +1,94 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Growl implementation of nsIAlertsService.
*
* The Initial Developer of the Original Code is
* Shawn Wilsher <me@shawnwilsher.com>.
* Portions created by the Initial Developer are Copyright (C) 2006-2007
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsAlertsImageLoadListener.h"
#ifdef DEBUG
#include "nsIRequest.h"
#include "nsIChannel.h"
#include "nsIURI.h"
#include "nsCOMPtr.h"
#endif
NS_IMPL_ISUPPORTS1(nsAlertsImageLoadListener, nsIStreamLoaderObserver)
nsAlertsImageLoadListener::nsAlertsImageLoadListener(const nsAString& aAlertTitle,
const nsAString& aAlertText,
PRBool aAlertClickable,
const nsAString& aAlertCookie,
PRUint32 aAlertListenerKey) :
mAlertTitle(aAlertTitle), mAlertText(aAlertText),
mAlertClickable(aAlertClickable), mAlertCookie(aAlertCookie),
mAlertListenerKey(aAlertListenerKey)
{
}
NS_IMETHODIMP
nsAlertsImageLoadListener::OnStreamComplete(nsIStreamLoader* aLoader,
nsISupports* aContext,
nsresult aStatus,
PRUint32 aLength,
const PRUint8* aResult)
{
#ifdef DEBUG
// print a load error on bad status
nsCOMPtr<nsIRequest> request;
aLoader->GetRequest(getter_AddRefs(request));
nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
if (NS_FAILED(aStatus)) {
if (channel) {
nsCOMPtr<nsIURI> uri;
channel->GetURI(getter_AddRefs(uri));
if (uri) {
nsCAutoString uriSpec;
uri->GetSpec(uriSpec);
printf("Failed to load %s\n", uriSpec.get());
}
}
}
#endif
[mozGrowlDelegate notifyWithTitle: mAlertTitle
description: mAlertText
iconData: NS_FAILED(aStatus) ? [NSData data] :
[NSData dataWithBytes: aResult
length: aLength]
key: mAlertListenerKey
cookie: mAlertCookie];
return NS_OK;
}

View File

@ -0,0 +1,72 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Growl implementation of nsIAlertsService.
*
* The Initial Developer of the Original Code is
* Shawn Wilsher <me@shawnwilsher.com>.
* Portions created by the Initial Developer are Copyright (C) 2006-2007
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#ifndef nsAlertsService_h_
#define nsAlertsService_h_
#include "nsIAlertsService.h"
#include "nsIObserver.h"
#include "nsToolkitCompsCID.h"
struct GrowlDelegateWrapper;
class nsAlertsService : public nsIAlertsService,
public nsIObserver
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIALERTSSERVICE
NS_DECL_NSIOBSERVER
nsAlertsService();
nsresult Init();
private:
GrowlDelegateWrapper* mDelegate;
virtual ~nsAlertsService();
};
class nsModuleComponentInfo;
NS_METHOD nsAlertsServiceRegister(nsIComponentManager* aCompMgr,
nsIFile *aPath,
const char* registryLocation,
const char* componentType,
const nsModuleComponentInfo* info);
NS_METHOD nsAlertsServiceUnregister(nsIComponentManager* aCompMgr,
nsIFile* aPath,
const char* registryLocation,
const nsModuleComponentInfo* info);
#endif // nsAlertsService_h_

View File

@ -0,0 +1,183 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Growl implementation of nsIAlertsService.
*
* The Initial Developer of the Original Code is
* Shawn Wilsher <me@shawnwilsher.com>.
* Portions created by the Initial Developer are Copyright (C) 2006-2007
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsAlertsService.h"
#include "nsString.h"
#include "nsAlertsImageLoadListener.h"
#include "nsIURI.h"
#include "nsIStreamLoader.h"
#include "nsNetUtil.h"
#include "nsCRT.h"
#include "nsCOMPtr.h"
#include "nsIServiceManager.h"
#include "nsICategoryManager.h"
#include "nsMemory.h"
#import "mozGrowlDelegate.h"
#import "GrowlApplicationBridge.h"
NS_IMPL_THREADSAFE_ADDREF(nsAlertsService)
NS_IMPL_THREADSAFE_RELEASE(nsAlertsService)
NS_INTERFACE_MAP_BEGIN(nsAlertsService)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIAlertsService)
NS_INTERFACE_MAP_ENTRY(nsIObserver)
NS_INTERFACE_MAP_ENTRY(nsIAlertsService)
NS_INTERFACE_MAP_END_THREADSAFE
struct GrowlDelegateWrapper
{
mozGrowlDelegate* delegate;
GrowlDelegateWrapper()
{
delegate = [[mozGrowlDelegate alloc] init];
}
~GrowlDelegateWrapper()
{
[delegate release];
}
};
nsresult
nsAlertsService::Init()
{
if ([GrowlApplicationBridge isGrowlInstalled] == NO)
return NS_ERROR_SERVICE_NOT_AVAILABLE;
mDelegate = new GrowlDelegateWrapper();
return NS_OK;
}
nsAlertsService::nsAlertsService() : mDelegate(nsnull) {}
nsAlertsService::~nsAlertsService()
{
if (mDelegate)
delete mDelegate;
}
NS_IMETHODIMP
nsAlertsService::ShowAlertNotification(const nsAString& aImageUrl,
const nsAString& aAlertTitle,
const nsAString& aAlertText,
PRBool aAlertClickable,
const nsAString& aAlertCookie,
nsIObserver* aAlertListener)
{
NS_ASSERTION(mDelegate->delegate == [GrowlApplicationBridge growlDelegate],
"Growl Delegate was not registered properly.");
PRUint32 ind = 0;
if (aAlertListener)
ind = [mDelegate->delegate addObserver: aAlertListener];
nsCOMPtr<nsIURI> uri;
nsresult rv = NS_NewURI(getter_AddRefs(uri), aImageUrl);
if (NS_FAILED(rv)) {
// image uri failed to resolve, so dispatch to growl with no image
[mozGrowlDelegate notifyWithTitle: aAlertTitle
description: aAlertText
iconData: [NSData data]
key: ind
cookie: aAlertCookie];
return NS_OK;
}
nsCOMPtr<nsAlertsImageLoadListener> listener =
new nsAlertsImageLoadListener(aAlertTitle, aAlertText, aAlertClickable,
aAlertCookie, ind);
nsCOMPtr<nsIStreamLoader> loader;
rv = NS_NewStreamLoader(getter_AddRefs(loader), uri, listener);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
NS_IMETHODIMP
nsAlertsService::Observe(nsISupports* aSubject, const char* aTopic,
const PRUnichar* aData)
{
if (nsCRT::strcmp(aTopic, "app-startup") == 0) // registers with Growl here
[GrowlApplicationBridge setGrowlDelegate: mDelegate->delegate];
return NS_OK;
}
NS_METHOD
nsAlertsServiceRegister(nsIComponentManager* aCompMgr,
nsIFile* aPath,
const char* registryLocation,
const char* componentType,
const nsModuleComponentInfo* info)
{
nsresult rv;
nsCOMPtr<nsICategoryManager> catman =
do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
char* prev = nsnull;
rv = catman->AddCategoryEntry("app-startup", "nsAlertsService",
NS_ALERTSERVICE_CONTRACTID, PR_TRUE, PR_TRUE,
&prev);
if (prev)
nsMemory::Free(prev);
return rv;
}
NS_METHOD
nsAlertsServiceUnregister(nsIComponentManager* aCompMgr,
nsIFile* aPath,
const char* registryLocation,
const nsModuleComponentInfo* info)
{
nsresult rv;
nsCOMPtr<nsICategoryManager> catman =
do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv);
if (NS_FAILED(rv)) return rv;
rv = catman->DeleteCategoryEntry("app-startup", "nsAlertsService", PR_TRUE);
return rv;
}

View File

@ -54,6 +54,10 @@ ifneq (,$(filter $(MOZ_WIDGET_TOOLKIT),windows gtk gtk2))
ALERTS_SERVICE=1
DEFINES += -DALERTS_SERVICE
endif
ifneq (,$(filter cocoa, $(MOZ_WIDGET_TOOLKIT)))
ALERTS_SERVICE_MAC=1
DEFINES += -DALERTS_SERVICE_MAC
endif
REQUIRES = \
xpcom \
@ -97,11 +101,24 @@ LOCAL_INCLUDES = \
-I$(srcdir)/../downloads/src \
-I$(srcdir)/../startup/src \
-I$(srcdir)/../typeaheadfind/src \
-I$(srcdir)/../alerts/src \
-I$(srcdir)/../url-classifier/src \
-I$(srcdir)/../feeds/src \
$(NULL)
ifdef ALERTS_SERVICE
LOCAL_INCLUDES += \
-I$(srcdir)/../alerts/src \
$(NULL)
endif
ifdef ALERTS_SERVICE_MAC
LOCAL_INCLUDES += \
-I$(srcdir)/../alerts/src/mac \
$(NULL)
EXTRA_DSO_LDOPTS += \
-framework Cocoa \
$(NULL)
endif
SHARED_LIBRARY_LIBS = \
../startup/src/$(LIB_PREFIX)appstartup_s.$(LIB_SUFFIX) \
$(NULL)
@ -118,6 +135,10 @@ endif
ifdef ALERTS_SERVICE
SHARED_LIBRARY_LIBS += ../alerts/src/$(LIB_PREFIX)alerts_s.$(LIB_SUFFIX)
endif
ifdef ALERTS_SERVICE_MAC
SHARED_LIBRARY_LIBS += ../alerts/src/mac/$(LIB_PREFIX)alerts_s.$(LIB_SUFFIX)
SHARED_LIBRARY_LIBS += ../alerts/src/mac/growl/$(LIB_PREFIX)growl_s.$(LIB_SUFFIX)
endif
ifdef MOZ_URL_CLASSIFIER
SHARED_LIBRARY_LIBS += ../url-classifier/src/$(LIB_PREFIX)urlclassifier_s.$(LIB_SUFFIX)

View File

@ -40,7 +40,7 @@
#include "nsUserInfo.h"
#include "nsXPFEComponentsCID.h"
#include "nsToolkitCompsCID.h"
#ifdef ALERTS_SERVICE
#if defined(ALERTS_SERVICE) || defined(ALERTS_SERVICE_MAC)
#include "nsAlertsService.h"
#endif
@ -73,6 +73,8 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsUserInfo)
#ifdef ALERTS_SERVICE
NS_GENERIC_FACTORY_CONSTRUCTOR(nsAlertsService)
#elif defined(ALERTS_SERVICE_MAC)
NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsAlertsService, Init)
#endif
#ifndef MOZ_SUITE
@ -116,6 +118,13 @@ static const nsModuleComponentInfo components[] =
NS_ALERTSSERVICE_CID,
NS_ALERTSERVICE_CONTRACTID,
nsAlertsServiceConstructor },
#elif defined(ALERTS_SERVICE_MAC)
{ "Alerts Service",
NS_ALERTSSERVICE_CID,
NS_ALERTSERVICE_CONTRACTID,
nsAlertsServiceConstructor,
nsAlertsServiceRegister,
nsAlertsServiceUnregister },
#endif
#ifndef MOZ_SUITE
// XXX Suite isn't ready to include this just yet