diff --git a/libgrowl/build.xml b/libgrowl/build.xml
new file mode 100644
index 00000000..bf22b556
--- /dev/null
+++ b/libgrowl/build.xml
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/libgrowl/src/java/info/growl/DummyGrowl.java b/libgrowl/src/java/info/growl/DummyGrowl.java
new file mode 100644
index 00000000..135fbe06
--- /dev/null
+++ b/libgrowl/src/java/info/growl/DummyGrowl.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2008, Michael Stringer
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * 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.
+ * * 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 ``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 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.
+ */
+
+package info.growl;
+
+import java.awt.image.RenderedImage;
+
+/**
+ * Dummy implementation of the Growl API for situations where the native code
+ * cannot be loaded (non-Mac systems).
+ *
+ * @author Michael Stringer
+ * @version 0.1
+ */
+class DummyGrowl implements Growl {
+
+ /**
+ * {@inheritDoc}
+ */
+ public void addNotification(String name, boolean enabledByDefault) {
+ // does nothing
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void register() throws GrowlException {
+ // does nothing
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void sendNotification(String name, String title, String body)
+ throws GrowlException {
+ // does nothing
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void sendNotification(String name, String title, String body,
+ RenderedImage icon) throws GrowlException {
+ // does nothing
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setIcon(RenderedImage icon) throws GrowlException {
+ // does nothing
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void sendNotification(String name, String title, String body,
+ String callbackContext) throws GrowlException {
+ // does nothing
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void sendNotification(String name, String title, String body,
+ String callbackContext, RenderedImage icon) throws GrowlException {
+ // does nothing
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void addCallbackListener(GrowlCallbackListener listener) {
+ // does nothing
+ }
+}
diff --git a/libgrowl/src/java/info/growl/Growl.java b/libgrowl/src/java/info/growl/Growl.java
new file mode 100644
index 00000000..9ea3abed
--- /dev/null
+++ b/libgrowl/src/java/info/growl/Growl.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2008, Michael Stringer
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * 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.
+ * * 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 ``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 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.
+ */
+
+package info.growl;
+
+import java.awt.image.RenderedImage;
+
+/**
+ * Interface for sending notifications to Growl.
+ *
+ * @author Michael Stringer
+ * @version 0.1
+ */
+public interface Growl {
+ /**
+ * Registers this Growl object with the Growl service.
+ *
+ * @throws GrowlException
+ * If an error occurs during the registration.
+ */
+ public void register() throws GrowlException;
+
+ /**
+ * Sets the icon to display for notifications from this Growl
.
+ *
+ * This must be called before calling {@link #register()}. If the
+ * icon is changed after {@link #register()} has been called then another
+ * call to {@link #register()} must be made.
+ *
+ * @param icon
+ * The icon to display.
+ * @throws GrowlException
+ * If an error occurs while setting the icon.
+ */
+ public void setIcon(RenderedImage icon) throws GrowlException;
+
+ /**
+ * Adds a notification type for this Growl
. This must
+ * be called before calling {@link #register()}. If the another
+ * notification type is added after {@link #register()} has been called then
+ * another call to {@link #register()} must be made.
+ *
+ * @param name
+ * The name of the notification type. This is what appears in
+ * the Growl settings.
+ * @param enabledByDefault
+ * true
if this notification type should be
+ * enabled by default. This can be overridden by the user in
+ * the Growl settings.
+ */
+ public void addNotification(String name, boolean enabledByDefault);
+
+ /**
+ * Adds a click callback listener.
+ *
+ * @param listener
+ * The callback listener to add.
+ */
+ public void addCallbackListener(GrowlCallbackListener listener);
+
+ /**
+ * Sends a notification to Growl for displaying. This must be called
+ * after calling {@link #register()}.
+ *
+ * @param name
+ * Name of the notification type that has been registered.
+ * @param title
+ * The title of the notification.
+ * @param body
+ * The body of the notification.
+ * @throws GrowlException
+ * If the notification could not be sent.
+ */
+ public void sendNotification(String name, String title, String body)
+ throws GrowlException;
+
+ /**
+ * Sends a notification to Growl for displaying. This must be called
+ * after calling {@link #register()}.
+ *
+ * @param name
+ * Name of the notification type that has been registered.
+ * @param title
+ * The title of the notification.
+ * @param body
+ * The body of the notification.
+ * @param icon
+ * The icon to display with the notification.
+ * @throws GrowlException
+ * If the notification could not be sent.
+ */
+ public void sendNotification(String name, String title, String body,
+ RenderedImage icon) throws GrowlException;
+
+ /**
+ * Sends a notification to Growl for displaying. This must be called
+ * after calling {@link #register()}.
+ *
+ * @param name
+ * Name of the notification type that has been registered.
+ * @param title
+ * The title of the notification.
+ * @param body
+ * The body of the notification.
+ * @param callbackContext
+ * A unique ID that will be sent to any registered
+ * {@link GrowlCallbackListener}s. If this is
+ * null
then clicks will be ignored.
+ * @throws GrowlException
+ * If the notification could not be sent.
+ */
+ public void sendNotification(String name, String title, String body,
+ String callbackContext) throws GrowlException;
+
+ /**
+ * Sends a notification to Growl for displaying. This must be called
+ * after calling {@link #register()}.
+ *
+ * @param name
+ * Name of the notification type that has been registered.
+ * @param title
+ * The title of the notification.
+ * @param body
+ * The body of the notification.
+ * @param callbackContext
+ * A unique ID that will be sent to any registered
+ * {@link GrowlCallbackListener}s. If this is
+ * null
then clicks will be ignored.
+ * @param icon
+ * The icon to display with the notification.
+ * @throws GrowlException
+ * If the notification could not be sent.
+ */
+ public void sendNotification(String name, String title, String body,
+ String callbackContext, RenderedImage icon) throws GrowlException;
+}
diff --git a/libgrowl/src/java/info/growl/GrowlCallbackListener.java b/libgrowl/src/java/info/growl/GrowlCallbackListener.java
new file mode 100644
index 00000000..f294d9d9
--- /dev/null
+++ b/libgrowl/src/java/info/growl/GrowlCallbackListener.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2008, Michael Stringer
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * 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.
+ * * 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 ``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 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.
+ */
+
+package info.growl;
+
+/**
+ * Callback interface for handling users clicking on notifications.
+ *
+ * @author Michael Stringer
+ * @version 0.1
+ */
+public interface GrowlCallbackListener {
+
+ /**
+ * Signals that the user has clicked on a notification with an identifier of
+ * clickContext
.
+ *
+ * @param clickContext
+ * The ID of the notification clicked.
+ */
+ public void notificationWasClicked(String clickContext);
+}
diff --git a/libgrowl/src/java/info/growl/GrowlException.java b/libgrowl/src/java/info/growl/GrowlException.java
new file mode 100644
index 00000000..d4759966
--- /dev/null
+++ b/libgrowl/src/java/info/growl/GrowlException.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2008, Michael Stringer
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * 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.
+ * * 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 ``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 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.
+ */
+
+package info.growl;
+
+/**
+ * An exception class for handling errors while sending messages to Growl.
+ *
+ * @author Michael Stringer
+ * @version 0.1
+ */
+public class GrowlException extends Exception {
+ private static final long serialVersionUID = 2642457707267962686L;
+
+ /**
+ * Creates a new GrowlException
.
+ *
+ * @param message
+ * The error message.
+ */
+ public GrowlException(String message) {
+ super(message);
+ }
+
+ /**
+ * Creates a new GrowlException
.
+ *
+ * @param message
+ * The error message.
+ * @param cause
+ * The underlying cause.
+ */
+ public GrowlException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/libgrowl/src/java/info/growl/GrowlNative.java b/libgrowl/src/java/info/growl/GrowlNative.java
new file mode 100644
index 00000000..65a18e84
--- /dev/null
+++ b/libgrowl/src/java/info/growl/GrowlNative.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (c) 2008, Michael Stringer
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * 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.
+ * * 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 ``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 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.
+ */
+
+package info.growl;
+
+import java.awt.image.RenderedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.imageio.ImageIO;
+
+/**
+ * Growl notification implementation. This uses JNI to send messages to Growl.
+ *
+ * @author Michael Stringer
+ * @version 0.1
+ */
+class GrowlNative implements Growl {
+ private String appName;
+ private List notifications;
+ private List callbackListeners;
+ private byte[] imageData;
+
+ private native void sendNotification(String appName, String name,
+ String title, String message, String callbackContext, byte[] icon);
+
+ private native void registerApp(String appName, byte[] image,
+ List notifications);
+
+ /**
+ * Creates a new GrowlNative
instance for the specified
+ * application name.
+ *
+ * @param appName
+ * The name of the application sending notifications.
+ */
+ GrowlNative(String appName) {
+ notifications = new ArrayList();
+ callbackListeners = new ArrayList();
+ this.appName = appName;
+ }
+
+ void fireCallbacks(String callbackContext) {
+ for (GrowlCallbackListener listener : callbackListeners) {
+ listener.notificationWasClicked(callbackContext);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void register() throws GrowlException {
+ registerApp(appName, imageData, notifications);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void addNotification(String name, boolean enabledByDefault) {
+ notifications.add(new NotificationType(name, enabledByDefault));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void addCallbackListener(GrowlCallbackListener listener) {
+ callbackListeners.add(listener);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setIcon(RenderedImage icon) throws GrowlException {
+ try {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ImageIO.write(icon, "png", baos);
+
+ imageData = baos.toByteArray();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void sendNotification(String name, String title, String body)
+ throws GrowlException {
+ if (!notifications.contains(new NotificationType(name, false))) {
+ System.out.println("contains: " + notifications);
+ throw new GrowlException("Unregistered notification name [" + name
+ + "]");
+ }
+ sendNotification(appName, name, title, body, null, null);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void sendNotification(String name, String title, String body,
+ RenderedImage icon) throws GrowlException {
+ if (!notifications.contains(new NotificationType(name, false))) {
+ System.out.println("contains: " + notifications);
+ throw new GrowlException("Unregistered notification name [" + name
+ + "]");
+ }
+
+ try {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ImageIO.write(icon, "png", baos);
+
+ byte[] image = baos.toByteArray();
+
+ sendNotification(appName, name, title, body, null, image);
+ } catch (IOException ioe) {
+ throw new GrowlException("Failed to convert Image", ioe);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void sendNotification(String name, String title, String body,
+ String callbackContext) throws GrowlException {
+ if (!notifications.contains(new NotificationType(name, false))) {
+ System.out.println("contains: " + notifications);
+ throw new GrowlException("Unregistered notification name [" + name
+ + "]");
+ }
+
+ sendNotification(appName, name, title, body, callbackContext, null);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void sendNotification(String name, String title, String body,
+ String callbackContext, RenderedImage icon) throws GrowlException {
+ if (!notifications.contains(new NotificationType(name, false))) {
+ System.out.println("contains: " + notifications);
+ throw new GrowlException("Unregistered notification name [" + name
+ + "]");
+ }
+
+ try {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ImageIO.write(icon, "png", baos);
+
+ byte[] image = baos.toByteArray();
+
+ sendNotification(appName, name, title, body, callbackContext, image);
+ } catch (IOException ioe) {
+ throw new GrowlException("Failed to convert Image", ioe);
+ }
+ }
+
+ private class NotificationType {
+ private String name;
+ private boolean enabledByDefault;
+
+ public NotificationType(String name, boolean enabledByDefault) {
+ this.name = name;
+ this.enabledByDefault = enabledByDefault;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public boolean isEnabledByDefault() {
+ return enabledByDefault;
+ }
+
+ public boolean equals(Object other) {
+ if (!(other instanceof NotificationType)) {
+ return false;
+ }
+
+ NotificationType otherType = (NotificationType) other;
+
+ return name.equals(otherType.name);
+ }
+ }
+}
diff --git a/libgrowl/src/java/info/growl/GrowlUtils.java b/libgrowl/src/java/info/growl/GrowlUtils.java
new file mode 100644
index 00000000..916d5daa
--- /dev/null
+++ b/libgrowl/src/java/info/growl/GrowlUtils.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2008, Michael Stringer
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * 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.
+ * * 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 ``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 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.
+ */
+
+package info.growl;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Utility class for sending notifications using Growl.
+ *
+ * @author Michael Stringer
+ * @version 0.1
+ */
+public final class GrowlUtils {
+ private static final boolean GROWL_LOADED;
+ private static Map instances = new HashMap();
+
+ static {
+ boolean loaded = false;
+ try {
+ System.loadLibrary("growl");
+ loaded = true;
+ } catch (UnsatisfiedLinkError ule) {
+ System.out.println("Failed to load Growl library");
+ }
+
+ GROWL_LOADED = loaded;
+ }
+
+ /**
+ * Utility method - should not be instantiated.
+ */
+ private GrowlUtils() {
+
+ }
+
+ /**
+ * Gets a Growl
instance to use for the specified application
+ * name. Multiple calls to this method will return the same instance.
+ *
+ * @param appName
+ * The name of the application.
+ * @return The Growl
instance to use.
+ */
+ public static Growl getGrowlInstance(String appName) {
+ Growl instance = instances.get(appName);
+
+ if (instance == null) {
+ if (GROWL_LOADED) {
+ instance = new GrowlNative(appName);
+ } else {
+ instance = new DummyGrowl();
+ }
+
+ instances.put(appName, instance);
+ }
+
+ return instance;
+ }
+
+ /**
+ * Gets whether messages can be sent to Growl. If this returns
+ * false
then {@link #getGrowlInstance(String)} will return a
+ * dummy object.
+ *
+ * @return true
if messages can be sent to Growl.
+ */
+ public static boolean isGrowlLoaded() {
+ return GROWL_LOADED;
+ }
+}
diff --git a/libgrowl/src/java/info/growl/test/TestGrowl.java b/libgrowl/src/java/info/growl/test/TestGrowl.java
new file mode 100644
index 00000000..e570279a
--- /dev/null
+++ b/libgrowl/src/java/info/growl/test/TestGrowl.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2008, Michael Stringer
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * 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.
+ * * 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 ``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 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.
+ */
+
+package info.growl.test;
+
+import info.growl.Growl;
+import info.growl.GrowlCallbackListener;
+import info.growl.GrowlException;
+import info.growl.GrowlUtils;
+
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+
+import javax.imageio.ImageIO;
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JOptionPane;
+import javax.swing.SwingUtilities;
+
+/**
+ * Simple test class for Growl.
+ *
+ * @author Michael Stringer
+ * @version 0.1
+ */
+public class TestGrowl extends JFrame {
+ private static final String APP_NAME = "Test Java App";
+ private static final String NOTIF_3_CALLBACK = "Notif3";
+
+ public TestGrowl() {
+ super("Growl for Java");
+ setSize(320, 290);
+
+ buildComponents();
+ setVisible(true);
+ setDefaultCloseOperation(EXIT_ON_CLOSE);
+ }
+
+ private void buildComponents() {
+ getContentPane().setLayout(new GridLayout(6, 1));
+
+ Action action = new AbstractAction("Register") {
+ public void actionPerformed(ActionEvent ae) {
+ try {
+ Growl growl = GrowlUtils.getGrowlInstance(APP_NAME);
+
+ growl.addNotification("Test Notification 1", true);
+ growl.addNotification("Test Notification 2", false);
+ growl.addNotification("Test Notification 3", true);
+
+ GrowlCallbackListener listener = new GrowlCallbackListener() {
+ public void notificationWasClicked(
+ final String clickContext) {
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ if (NOTIF_3_CALLBACK.equals(clickContext)) {
+ JOptionPane
+ .showMessageDialog(
+ TestGrowl.this,
+ "User clicked on 'Test Notification 3'");
+ }
+ }
+ });
+ }
+ };
+
+ growl.addCallbackListener(listener);
+ growl.register();
+ } catch (GrowlException ge) {
+ ge.printStackTrace();
+ }
+ }
+ };
+
+ getContentPane().add(new JButton(action));
+
+ action = new AbstractAction("Send 'Test Notification 1'") {
+ public void actionPerformed(ActionEvent ae) {
+ try {
+ Growl growl = GrowlUtils.getGrowlInstance(APP_NAME);
+
+ growl.sendNotification("Test Notification 1",
+ "Test Notif 1", "This is a test");
+ } catch (GrowlException ge) {
+ ge.printStackTrace();
+ }
+ }
+ };
+ getContentPane().add(new JButton(action));
+
+ action = new AbstractAction("Send 'Test Notification 2'") {
+ public void actionPerformed(ActionEvent ae) {
+ try {
+ Growl growl = GrowlUtils.getGrowlInstance(APP_NAME);
+ BufferedImage image = ImageIO.read(TestGrowl.class
+ .getResource("/images/duke.gif"));
+ growl.sendNotification("Test Notification 2",
+ "Test Notif 2", "This is another test", image);
+ } catch (GrowlException ge) {
+ ge.printStackTrace();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ };
+ getContentPane().add(new JButton(action));
+
+ action = new AbstractAction("Test Callback 'Notification 3'") {
+ public void actionPerformed(ActionEvent ae) {
+ try {
+ Growl growl = GrowlUtils.getGrowlInstance(APP_NAME);
+
+ growl.sendNotification("Test Notification 3",
+ "Callback Test", "Click me - I dares you!", NOTIF_3_CALLBACK);
+ } catch (GrowlException ge) {
+ ge.printStackTrace();
+ }
+ }
+ };
+ getContentPane().add(new JButton(action));
+
+ action = new AbstractAction("Reg & Test App 2") {
+ public void actionPerformed(ActionEvent ae) {
+ try {
+ Growl growl = GrowlUtils.getGrowlInstance("Other App");
+
+ growl.addNotification("A Notification", true);
+
+ BufferedImage image = ImageIO.read(TestGrowl.class
+ .getResource("/images/duke.gif"));
+ growl.setIcon(image);
+
+ growl.register();
+ growl.sendNotification("A Notification", "Testin",
+ "Blah de blah blah");
+ } catch (GrowlException ge) {
+ ge.printStackTrace();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ };
+ getContentPane().add(new JButton(action));
+
+ action = new AbstractAction("Exit") {
+ public void actionPerformed(ActionEvent ae) {
+ System.exit(0);
+ }
+ };
+ getContentPane().add(new JButton(action));
+ }
+
+ public static final void main(String[] args) {
+ new TestGrowl();
+ }
+}
diff --git a/libgrowl/src/native/GrowlJava.mm b/libgrowl/src/native/GrowlJava.mm
new file mode 100644
index 00000000..e78d5899
--- /dev/null
+++ b/libgrowl/src/native/GrowlJava.mm
@@ -0,0 +1,180 @@
+
+#import
+#import
+#import
+
+#import "info_growl_GrowlNative.h"
+#import "GrowlJavaCallback.h"
+
+jclass jListClass;
+jmethodID jListSize;
+jmethodID jListGet;
+
+jclass gnClass;
+jmethodID gnFireCallbacks;
+
+jclass ntClass;
+jmethodID ntGetName;
+jmethodID ntIsEnabledByDefault;
+
+jclass gcClass;
+jmethodID gcNotificationWasClicked;
+
+/*
+ * Convert a Java String into a Cocoa NSString.
+ */
+NSString *convertJavaStringToCocoa(JNIEnv *env, jstring javaString) {
+ NSString *cocoaString;
+ const char *nativeString;
+
+ nativeString = env->GetStringUTFChars(javaString, JNI_FALSE);
+ cocoaString = [NSString stringWithUTF8String:nativeString];
+
+ return cocoaString;
+}
+
+/**
+ * Initialise references to the necessary Java classes & methods.
+ */
+void initJavaRefs(JNIEnv *env) {
+ jListClass = env->FindClass("java/util/List");
+ jListSize = env->GetMethodID(jListClass, "size", "()I");
+ jListGet = env->GetMethodID(jListClass, "get", "(I)Ljava/lang/Object;");
+
+ gnClass = env->FindClass("info/growl/GrowlNative");
+ gnFireCallbacks = env->GetMethodID(gnClass, "fireCallbacks", "(Ljava/lang/String;)V");
+
+ ntClass = env->FindClass("info/growl/GrowlNative$NotificationType");
+ ntGetName = env->GetMethodID(ntClass, "getName", "()Ljava/lang/String;");
+ ntIsEnabledByDefault = env->GetMethodID(ntClass, "isEnabledByDefault", "()Z");
+
+ gcClass = env->FindClass("info/growl/GrowlCallbackListener");
+ gcNotificationWasClicked = env->GetMethodID(gcClass, "notificationWasClicked", "(Ljava/lang/String;)V");
+
+ env->ExceptionDescribe();
+}
+
+/*
+ * Class: info_growl_GrowlNative
+ * Method: sendNotification
+ * Signature: (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[B)V
+ */
+JNIEXPORT void JNICALL Java_info_growl_GrowlNative_sendNotification
+(JNIEnv *env, jobject, jstring jAppName, jstring jNotifName, jstring jTitle, jstring jBody,
+ jstring jCallbackId, jbyteArray jImage) {
+ NSAutoreleasePool *releasePool = [[NSAutoreleasePool alloc] init];
+ NSString *appName = convertJavaStringToCocoa(env, jAppName);
+ NSString *notifName = convertJavaStringToCocoa(env, jNotifName);
+ NSString *title = convertJavaStringToCocoa(env, jTitle);
+ NSString *body = convertJavaStringToCocoa(env, jBody);
+ NSDistributedNotificationCenter *notifCenter = [NSDistributedNotificationCenter defaultCenter];
+ NSMutableDictionary *notifDictionary = [[NSMutableDictionary alloc] init];
+
+ // initialise Java classes/methods
+ initJavaRefs(env);
+
+ // build the dictionary for the notification
+ [notifDictionary setObject:appName forKey:@"ApplicationName"];
+
+ [notifDictionary setObject:appName forKey:@"ApplicationName"];
+ [notifDictionary setObject:notifName forKey:@"NotificationName"];
+ [notifDictionary setObject:title forKey:@"NotificationTitle"];
+ [notifDictionary setObject:body forKey:@"NotificationDescription"];
+
+ if (jCallbackId != NULL) {
+ // notification has a callback - register it
+ NSString *callbackId = convertJavaStringToCocoa(env, jCallbackId);
+ [notifDictionary setObject:callbackId forKey:@"NotificationClickContext"];
+ }
+
+ if (jImage != NULL) {
+ // notification has a custom icon - add it
+ jbyte *nativeImageData = env->GetByteArrayElements(jImage, NULL);
+ NSData *imageData = [NSData dataWithBytes:nativeImageData length:env->GetArrayLength(jImage)];
+ env->ReleaseByteArrayElements(jImage, nativeImageData, JNI_ABORT);
+
+ NSImage *image = [[NSImage alloc] initWithData:imageData];
+
+ [notifDictionary setObject:[image TIFFRepresentation] forKey:@"NotificationIcon"];
+ }
+
+ // send the notification
+ [notifCenter postNotificationName:@"GrowlNotification"
+ object:nil
+ userInfo:notifDictionary
+ deliverImmediately:true];
+
+ // clean up
+ [releasePool release];
+}
+
+/*
+ * Class: info_growl_GrowlNative
+ * Method: registerApp
+ * Signature: (Ljava/lang/String;[BLjava/util/List;)V
+ */
+JNIEXPORT void JNICALL Java_info_growl_GrowlNative_registerApp
+(JNIEnv *env, jobject jThis, jstring jAppName, jbyteArray jImage, jobject jNotifList) {
+ NSAutoreleasePool *releasePool = [[NSAutoreleasePool alloc] init];
+ NSDistributedNotificationCenter *notifCenter = [NSDistributedNotificationCenter defaultCenter];
+ NSMutableDictionary *regDictionary = [[NSMutableDictionary alloc] init];
+ NSString *appName = convertJavaStringToCocoa(env, jAppName);
+ NSMutableArray *notifNames = [[NSMutableArray alloc] init];
+ NSMutableArray *defaultNotifs = [[NSMutableArray alloc] init];
+
+ // initialise Java classes/methods
+ initJavaRefs(env);
+
+ // build the dictionary for the registration
+ [regDictionary setObject:appName forKey:@"ApplicationName"];
+
+ // loop through the notifications and add them to the list
+ int notifSize = env->CallIntMethod(jNotifList, jListSize);
+ for (int i = 0; i < notifSize; i++) {
+ jobject notifType = env->CallObjectMethod(jNotifList, jListGet, i);
+ jstring jNotifName = (jstring)env->CallObjectMethod(notifType, ntGetName);
+ NSString *notifName = convertJavaStringToCocoa(env, jNotifName);
+
+ [notifNames addObject:notifName];
+
+ if (env->CallBooleanMethod(notifType, ntIsEnabledByDefault)) {
+ // add to default list
+ [defaultNotifs addObject:notifName];
+ }
+ }
+
+ [regDictionary setObject:notifNames forKey:@"AllNotifications"];
+ [regDictionary setObject:defaultNotifs forKey:@"DefaultNotifications"];
+
+ // register the click callback
+ jobject callbackRef = env->NewGlobalRef(jThis);
+ GrowlJavaCallback *callback = [[GrowlJavaCallback alloc] initWithCallback:callbackRef];
+
+ NSString *growlNotifName = [[NSString alloc] initWithFormat:@"%@%@",
+ appName, @"GrowlClicked!"];
+
+ [notifCenter addObserver:callback
+ selector:@selector(growlNotificationWasClicked:)
+ name:growlNotifName
+ object:nil];
+
+ if (jImage != NULL) {
+ // set the image for this application's registration
+ jbyte *nativeImageData = env->GetByteArrayElements(jImage, NULL);
+ NSData *imageData = [NSData dataWithBytes:nativeImageData length:env->GetArrayLength(jImage)];
+ env->ReleaseByteArrayElements(jImage, nativeImageData, JNI_ABORT);
+
+ NSImage *image = [[NSImage alloc] initWithData:imageData];
+
+ [regDictionary setObject:[image TIFFRepresentation] forKey:@"ApplicationIcon"];
+ }
+
+ // send the registration
+ [notifCenter postNotificationName:@"GrowlApplicationRegistrationNotification"
+ object:nil
+ userInfo:regDictionary
+ deliverImmediately:true];
+
+ // clean up
+ [releasePool release];
+}
diff --git a/libgrowl/src/native/GrowlJavaCallback.h b/libgrowl/src/native/GrowlJavaCallback.h
new file mode 100644
index 00000000..37980499
--- /dev/null
+++ b/libgrowl/src/native/GrowlJavaCallback.h
@@ -0,0 +1,14 @@
+
+#import
+
+@interface GrowlJavaCallback : NSObject {
+ jobject _callbackObject;
+ jclass _callbackClass;
+ jmethodID _callbackMethod;
+}
+
+- (id) initWithCallback : (jobject) callback;
+
+- (void) growlNotificationWasClicked : (NSNotification *)notification;
+
+@end
\ No newline at end of file
diff --git a/libgrowl/src/native/GrowlJavaCallback.mm b/libgrowl/src/native/GrowlJavaCallback.mm
new file mode 100644
index 00000000..d3b14fa0
--- /dev/null
+++ b/libgrowl/src/native/GrowlJavaCallback.mm
@@ -0,0 +1,51 @@
+
+#import
+#import
+#import
+
+#import "GrowlJavaCallback.h"
+
+static JavaVM *cachedJVM = NULL;
+@implementation GrowlJavaCallback
+
+- (id) initWithCallback : (jobject) callback {
+ self = [super init];
+
+ _callbackObject = callback;
+
+ return self;
+}
+
+- (void) growlNotificationWasClicked : (NSNotification *)notification {
+ // get the callback id
+ NSDictionary *callbackDictionary = (NSDictionary *)[notification userInfo];
+ NSString *callbackId = (NSString *)[callbackDictionary objectForKey:@"ClickedContext"];
+ const char *nativeString;
+ jstring convertedString;
+
+ if (cachedJVM == NULL) {
+ jsize jvmCount;
+ jint jvmError = JNI_GetCreatedJavaVMs(&cachedJVM, 1, &jvmCount);
+
+ if (jvmError != 0) {
+ // TODO panic like crazy
+ NSLog(@"Error error error");
+ }
+ }
+
+ if (cachedJVM != NULL) {
+ JNIEnv *env = NULL;
+ cachedJVM->GetEnv((void **)&env, JNI_VERSION_1_2);
+
+ _callbackClass = env->GetObjectClass(_callbackObject);
+ _callbackMethod = env->GetMethodID(_callbackClass, "fireCallbacks", "(Ljava/lang/String;)V");
+
+ nativeString = [callbackId UTF8String];
+ convertedString = env->NewStringUTF(nativeString);
+
+ // fire off the callback
+ env->CallVoidMethod(_callbackObject, _callbackMethod, convertedString);
+ }
+}
+
+@end
\ No newline at end of file
diff --git a/libgrowl/src/native/Makefile b/libgrowl/src/native/Makefile
new file mode 100644
index 00000000..a6d5e9a6
--- /dev/null
+++ b/libgrowl/src/native/Makefile
@@ -0,0 +1,30 @@
+CXX = g++
+CXXFLAGS = -W -Wall -c -I/System/Library/Frameworks/JavaVM.framework/Headers -I/System/Library/Frameworks/AppKit.framework/Headers -I/System/Library/Frameworks/Foundation.framework/Headers -isysroot /Developer/SDKs/MacOSX10.5.sdk -arch i386 -arch ppc -arch x86_64 -mmacosx-version-min=10.4
+MMFLAGS = -Wno-protocol -Wundeclared-selector
+LDFLAGS = -dynamiclib -isysroot /Developer/SDKs/MacOSX10.5.sdk -arch i386 -arch ppc -arch x86_64 -mmacosx-version-min=10.4
+LDSUFFIXES = -framework JavaVM -framework Foundation -framework AppKit
+SO_NAME = libgrowl.jnilib
+
+.SUFFIXES: .o .mm
+.mm.o:
+ $(CXX) $(MMFLAGS) $(CXXFLAGS) $<
+
+MMSRCS = GrowlJava.mm \
+ GrowlJavaCallback.mm
+
+.PHONY: all clean
+
+all: $(SO_NAME)
+
+SO_o = $(MMSRCS:.mm=.o)
+
+.cpp.o:
+ $(CXX) $(CXXFLAGS) $(INCLUDES) $(EXTRA_INCLUDES) -o $@ $<
+
+$(SO_NAME): $(SO_o)
+ $(CXX) $(LDFLAGS) $(LIBS) $(SO_o) -o $@ $(LDSUFFIXES)
+
+clean:
+ rm -f *.jnilib
+ rm -f *.o
+
diff --git a/libgrowl/src/resources/images/duke.gif b/libgrowl/src/resources/images/duke.gif
new file mode 100644
index 00000000..1e5ac908
Binary files /dev/null and b/libgrowl/src/resources/images/duke.gif differ