package org.limewire.ui.swing.tray;
import java.io.IOException;
import java.util.HashSet;
/**
* Wrapper for the <a href="http://www.growl.info/">Growl</a> system which can
* be used on Mac OS X to display popup notifications.
* <p>
* The system is based on each application having a number of named 'notification'
* types. Each type can be enabled or disabled by default, but the user can
* change this via the Growl interface - individual application programs don't
* need to provide an interface.
* <p>
* This pure Java implementation compiles, links, and runs on all platforms.
* Where Growl is not supported or installed, it does nothing. You can find
* out whether Growl is in use via the {@link #getState()} method.
* <p>
* It uses AppleScript via the command-line 'osascript' utility which is
* available in all supported OS X versions. Each notification requires
* a separate invocation of this process, meaning that it isn't very efficient
* - but by the nature of these popups, you shouldn't be generating many each
* second, so in practice any delay is no issue.
* <p>
* Example usage:
* <pre>
* String FUN_NOTIFICATION="Fun notification",
* BORING_NOTIFICATION="Boring notification";
* GrowlWrapper gw=new GrowlWrapper("MyApp","Finder",
* new String[] {FUN_NOTIFICATION,BORING_NOTIFICATION},
* new String[] {FUN_NOTIFICATION});
* gw.notify(FUN_NOTIFICATION,
* "Fun stuff","I bet you're glad you can see this!");
* gw.notify(BORING_NOTIFICATION,
* "Boring stuff","I bet you regret seeing this!");
* </pre>
* Released publicly under the BSD license.
* @author Samuel Marshall, <a target="_top" href="http://www.leafdigital.com/software/">leafdigital.com</a>
*/
class GrowlWrapper
{
/** Growl application name */
private String application;
/** Available Growl notifications */
private HashSet<String> notifications=new HashSet<String>();
/** Wrapper state; one of the GROWL_xx constants */
private int state;
/** State: Growl is available and working */
public final static int GROWL_OK=0;
/** State: This computer is not running Mac OS X */
public final static int GROWL_NOT_MAC=1;
/** State: The 'osascript' program used to run AppleScript could not be found */
public final static int GROWL_NO_APPLESCRIPT=2;
/** State: An AppleScript error occurred, indicating that Growl is not available */
public final static int GROWL_UNAVAILABLE=3;
/** State: An unexpected error occurred during registration */
public final static int GROWL_UNEXPECTED_ERROR=4;
/**
* Constructs a GrowlWrapper. (Note that you need to construct a new object
* if you want to change any of these settings.)
* @param application Name of application (for use in Growl settings)
* @param applicationForIcon Name of application whose icon will be included
* in the popup (as defined by Apple; it's looking for the name part of a
* .app file you've run before) - may be null for no icon
* @param allNotifications Array of strings corresponding to events that
* can be notified (as used in Growl settings)
* @param defaultNotifications Array (must be a subset of the former)
* of events that should be turned on by default
* @throws IllegalArgumentException If a required parameter is null, or
* if one of the default notifications is not included in the 'all
* notifications' list
*/
public GrowlWrapper(String application,String applicationForIcon,
String[] allNotifications,String[] defaultNotifications)
throws IllegalArgumentException
{
if(application==null || allNotifications==null || defaultNotifications==null)
throw new IllegalArgumentException(
"application,allNotifications and defaultNotifications are required parameters");
if(allNotifications.length<1)
throw new IllegalArgumentException(
"Must specify at least one notification type");
this.application=application;
for(int i=0;i<allNotifications.length;i++)
{
notifications.add(allNotifications[i]);
}
for(int i=0;i<defaultNotifications.length;i++)
{
if(!notifications.contains(defaultNotifications[i]))
throw new IllegalArgumentException("Default notifications must be "+
"included in the allNotifications array too");
}
state=GROWL_OK;
// Test for Mac
// Code from http://developer.apple.com/technotes/tn2002/tn2110.html
String lcOSName = System.getProperty("os.name").toLowerCase();
if(!lcOSName.startsWith("mac os x"))
{
state=GROWL_NOT_MAC;
}
else
{
// Create AppleScript process
Process p=null;
try
{
p=Runtime.getRuntime().exec("osascript");
}
catch(IOException e)
{
state=GROWL_NO_APPLESCRIPT;
}
if(state==GROWL_OK)
{
// Send the register script
try
{
if (p == null) {
return;
}
// Send script
p.getOutputStream().write(getScript(application,applicationForIcon,
allNotifications,defaultNotifications,
null,null,null).getBytes("UTF-8"));
// Close stdin and wait for process to complete
p.getOutputStream().close();
try
{
p.waitFor();
}
catch(InterruptedException e)
{
}
// Check if there is anything on stderr (shouldn't be)
if(p.getErrorStream().read()!=-1)
state=GROWL_UNAVAILABLE;
}
catch(IOException e)
{
// By the time it gets to here, not really expecting problems of this
// type as they should have shown up earlier
state=GROWL_UNEXPECTED_ERROR;
}
}
}
}
/**
* Obtains the state of the wrapper.
* @return One of the GROWL_xx constants
*/
public int getState()
{
return state;
}
/**
* Generates a notification popup.
* @param notification Notification event ID, as previously supplied to the
* constructor
* @param title Title for popup
* @param description Text for popup (can include line breaks)
* @throws IllegalArgumentException If the notification wasn't defined,
* or if a parameter is null
*/
public void notify(String notification,String title,String description)
throws IllegalArgumentException
{
// Check parameters
if(notification==null || title==null || description==null)
throw new IllegalArgumentException("Parameters may not be null");
if(!notifications.contains(notification))
throw new IllegalArgumentException("Unknown notification '"+notification+"'");
// Do nothing if Growl is not running
if(state!=GROWL_OK) return;
try
{
// Run process and send script
Process p=Runtime.getRuntime().exec("osascript");
p.getOutputStream().write(getScript(application,null,null,null,
notification,title,description).getBytes("UTF-8"));
p.getOutputStream().close();
// No need to wait for process to exit as we don't care what the
// response is
}
catch(IOException e)
{
// Ignore errors - any likely problems should have been detected
// in constructor
}
}
/**
* Quotes a string for inclusion in AppleScript.
* @param input String
* @return String surrounded by double quotes and with necessasry backslashes
*/
private static String appleScriptQuote(String input)
{
StringBuffer sb=new StringBuffer("\"");
for(int i=0;i<input.length();i++)
{
char c=input.charAt(i);
switch(c)
{
case '"' : sb.append("\\\""); break;
case '\n' : sb.append("\\n"); break;
case '\\' : sb.append("\\\\"); break;
default: sb.append(c);
}
}
sb.append('"');
return sb.toString();
}
/**
* Internal code which builds up a suitable AppleScript based on the
* example on the Growl site. This can both register and generate an
* event.
* @param application Name of application (for Growl prefs)
* @param applicationForIcon Name of application to use for icon, or null if none
* (may be null if not registering)
* @param allNotifications Array of notification names or null if not registering
* @param defaultNotifications Array of notification names or null if not registering
* @param notification Name of notification to generate or null if not generating
* @param title Title of popup or null if not generating
* @param description Text of popup or null if not generating
* @return AppleScript in a string
*/
private static String getScript(
String application,String applicationForIcon,
String[] allNotifications,String[] defaultNotifications,
String notification,String title,String description)
{
StringBuffer sb=new StringBuffer();
sb.append("tell application \"GrowlHelperApp\"\n");
if(allNotifications!=null)
{
sb.append("register as application ");
sb.append(appleScriptQuote(application));
sb.append(" all notifications {");
for(int i=0;i<allNotifications.length;i++)
{
if(i>0) sb.append(',');
sb.append(appleScriptQuote(allNotifications[i]));
}
sb.append("} default notifications {");
for(int i=0;i<defaultNotifications.length;i++)
{
if(i>0) sb.append(',');
sb.append(appleScriptQuote(defaultNotifications[i]));
}
if(applicationForIcon!=null)
{
sb.append("} icon of application ");
sb.append(appleScriptQuote(applicationForIcon));
}
else
{
sb.append("}");
}
}
if(notification!=null)
{
sb.append("\nnotify with name");
sb.append(appleScriptQuote(notification));
sb.append(" title ");
sb.append(appleScriptQuote(title));
sb.append(" description ");
sb.append(appleScriptQuote(description));
sb.append(" application name ");
sb.append(appleScriptQuote(application));
}
sb.append("\nend tell\n");
return sb.toString();
}
}