diff --git a/build.xml b/build.xml index b9a1ec25b..94bd3eb55 100644 --- a/build.xml +++ b/build.xml @@ -799,8 +799,8 @@ bundle-plugin-msnaccregwizz,bundle-plugin-sipaccregwizz, bundle-plugin-yahooaccregwizz,bundle-plugin-aimaccregwizz, bundle-version,bundle-version-impl,bundle-shutdown-timeout, - bundle-growlnotification,bundle-swingnotification,bundle-sparkle, - bundle-plugin-branding, + bundle-growlnotification,bundle-swingnotification,bundle-galagonotification, + bundle-sparkle, bundle-plugin-branding, bundle-osdependent,bundle-browserlauncher,bundle-gibberish, bundle-gibberish-slick,bundle-plugin-gibberishaccregwizz, bundle-plugin-call-history-form, @@ -1058,6 +1058,8 @@ prefix="net/java/sip/communicator/impl/media/codec/audio"/> + + + + + + + + + + + + +static dbus_bool_t +GalagoNotification_messageAppendString( + JNIEnv *env, DBusMessageIter *iter, jstring jstr) +{ + const jbyte *str; + dbus_bool_t success; + const char *emptyStr = ""; + + if (jstr) + { + str = (*env)->GetStringUTFChars(env, jstr, NULL); + if (!str) + return FALSE; + } + else + str = NULL; + success + = dbus_message_iter_append_basic( + iter, + DBUS_TYPE_STRING, + str ? &str : &emptyStr); + if (str) + (*env)->ReleaseStringUTFChars(env, jstr, str); + return success; +} + +static dbus_bool_t +GalagoNotification_notifyAppendArgs( + JNIEnv *env, DBusMessage *message, jstring appName, jlong replacesId, + jstring appIcon, jstring summary, jstring body) +{ + DBusMessageIter iter; + dbus_uint32_t _replacesId; + DBusMessageIter subIter; + dbus_int32_t _expireTimeout; + + dbus_message_iter_init_append(message, &iter); + + if (!GalagoNotification_messageAppendString(env, &iter, appName)) + return FALSE; + + _replacesId = replacesId; + if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_UINT32, &_replacesId)) + return FALSE; + + if (!GalagoNotification_messageAppendString(env, &iter, appIcon)) + return FALSE; + + if (!GalagoNotification_messageAppendString(env, &iter, summary)) + return FALSE; + + if (!GalagoNotification_messageAppendString(env, &iter, body)) + return FALSE; + + if (!dbus_message_iter_open_container( + &iter, + DBUS_TYPE_ARRAY, + DBUS_TYPE_STRING_AS_STRING, + &subIter)) + return FALSE; + if (!dbus_message_iter_close_container(&iter, &subIter)) + return FALSE; + + if (!dbus_message_iter_open_container( + &iter, + DBUS_TYPE_ARRAY, + "{sv}", + &subIter)) + return FALSE; + if (!dbus_message_iter_close_container(&iter, &subIter)) + return FALSE; + + _expireTimeout = -1; + if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_INT32, &_expireTimeout)) + return FALSE; + + return TRUE; +} + +static jobjectArray +GalagoNotification_stringArray2jstringArray( + JNIEnv *env, char **stringArray, int stringArraySize) +{ + jclass stringClass; + jobjectArray jstringArray; + + stringClass = (*env)->FindClass(env, "java/lang/String"); + if (stringClass) + { + jstringArray + = (*env)->NewObjectArray(env, stringArraySize, stringClass, NULL); + if (jstringArray) + { + int i; + + for (i = 0; i < stringArraySize; i++) + { + jstring jstr = (*env)->NewStringUTF(env, *(stringArray + i)); + + if (jstr) + { + (*env)->SetObjectArrayElement(env, jstringArray, i, jstr); + if ((*env)->ExceptionCheck(env)) + { + jstringArray = NULL; + break; + } + } + else + { + jstringArray = NULL; + break; + } + } + } + } + else + jstringArray = NULL; + return jstringArray; +} + +static void +GalagoNotification_throwException(JNIEnv *env, DBusError *error) +{ + /* TODO Auto-generated method stub */ + jclass clazz + = (*env) + ->FindClass( + env, + "net/java/sip/communicator/impl/galagonotification/DBusException"); + + if (clazz) + (*env)->ThrowNew(env, clazz, error->message); +} + +JNIEXPORT jlong JNICALL +Java_net_java_sip_communicator_impl_galagonotification_GalagoNotification_dbus_1bus_1get_1session( + JNIEnv *env, jclass clazz) +{ + DBusError error; + DBusConnection *connection; + + dbus_error_init(&error); + connection = dbus_bus_get(DBUS_BUS_SESSION, &error); + + if (connection) + dbus_connection_set_exit_on_disconnect(connection, FALSE); + else if (dbus_error_is_set(&error)) + { + GalagoNotification_throwException(env, &error); + dbus_error_free(&error); + } + return connection; +} + +JNIEXPORT void JNICALL +Java_net_java_sip_communicator_impl_galagonotification_GalagoNotification_dbus_1connection_1unref( + JNIEnv *env, jclass clazz, jlong connection) +{ + dbus_connection_unref((DBusConnection *) connection); +} + +JNIEXPORT jobjectArray JNICALL +Java_net_java_sip_communicator_impl_galagonotification_GalagoNotification_getCapabilities( + JNIEnv *env, jclass clazz, jlong connection) +{ + DBusMessage *message; + jobjectArray jcapabilities = NULL; + + message + = dbus_message_new_method_call( + "org.freedesktop.Notifications", + "/org/freedesktop/Notifications", + "org.freedesktop.Notifications", + "GetCapabilities"); + if (message) + { + DBusError error; + DBusMessage *reply; + + dbus_error_init(&error); + reply + = dbus_connection_send_with_reply_and_block( + (DBusConnection *) connection, + message, + -1, + &error); + if (reply) + { + char **capabilities; + int capabilityCount; + + if (dbus_message_get_args( + reply, + &error, + DBUS_TYPE_ARRAY, + DBUS_TYPE_STRING, + &capabilities, + &capabilityCount, + DBUS_TYPE_INVALID)) + { + jcapabilities + = GalagoNotification_stringArray2jstringArray( + env, + capabilities, + capabilityCount); + dbus_free_string_array(capabilities); + } + else + { + GalagoNotification_throwException(env, &error); + dbus_error_free(&error); + } + dbus_message_unref(reply); + } + else if (dbus_error_is_set(&error)) + { + GalagoNotification_throwException(env, &error); + dbus_error_free(&error); + } + dbus_message_unref(message); + } + return jcapabilities; +} + +JNIEXPORT jlong JNICALL +Java_net_java_sip_communicator_impl_galagonotification_GalagoNotification_notify( + JNIEnv *env, jclass clazz, jlong connection, jstring appName, + jlong replacesId, jstring appIcon, jstring summary, jstring body) +{ + DBusMessage *message; + jlong jid = 0; + + message + = dbus_message_new_method_call( + "org.freedesktop.Notifications", + "/org/freedesktop/Notifications", + "org.freedesktop.Notifications", + "Notify"); + if (message) + { + if (GalagoNotification_notifyAppendArgs( + env, + message, + appName, + replacesId, + appIcon, + summary, + body)) + { + DBusError error; + DBusMessage *reply; + + dbus_error_init(&error); + reply + = dbus_connection_send_with_reply_and_block( + (DBusConnection *) connection, + message, + -1, + &error); + if (reply) + { + dbus_uint32_t id; + + if (dbus_message_get_args( + reply, + &error, + DBUS_TYPE_UINT32, + &id, + DBUS_TYPE_INVALID)) + jid = id; + else + { + GalagoNotification_throwException(env, &error); + dbus_error_free(&error); + } + dbus_message_unref(reply); + } + else if (dbus_error_is_set(&error)) + { + GalagoNotification_throwException(env, &error); + dbus_error_free(&error); + } + } + dbus_message_unref(message); + } + return jid; +} diff --git a/src/native/linux/galagonotification/net_java_sip_communicator_impl_galagonotification_GalagoNotification.h b/src/native/linux/galagonotification/net_java_sip_communicator_impl_galagonotification_GalagoNotification.h new file mode 100644 index 000000000..2f3882abc --- /dev/null +++ b/src/native/linux/galagonotification/net_java_sip_communicator_impl_galagonotification_GalagoNotification.h @@ -0,0 +1,45 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class net_java_sip_communicator_impl_galagonotification_GalagoNotification */ + +#ifndef _Included_net_java_sip_communicator_impl_galagonotification_GalagoNotification +#define _Included_net_java_sip_communicator_impl_galagonotification_GalagoNotification +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: net_java_sip_communicator_impl_galagonotification_GalagoNotification + * Method: dbus_bus_get_session + * Signature: ()J + */ +JNIEXPORT jlong JNICALL Java_net_java_sip_communicator_impl_galagonotification_GalagoNotification_dbus_1bus_1get_1session + (JNIEnv *, jclass); + +/* + * Class: net_java_sip_communicator_impl_galagonotification_GalagoNotification + * Method: dbus_connection_unref + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_net_java_sip_communicator_impl_galagonotification_GalagoNotification_dbus_1connection_1unref + (JNIEnv *, jclass, jlong); + +/* + * Class: net_java_sip_communicator_impl_galagonotification_GalagoNotification + * Method: getCapabilities + * Signature: (J)[Ljava/lang/String; + */ +JNIEXPORT jobjectArray JNICALL Java_net_java_sip_communicator_impl_galagonotification_GalagoNotification_getCapabilities + (JNIEnv *, jclass, jlong); + +/* + * Class: net_java_sip_communicator_impl_galagonotification_GalagoNotification + * Method: notify + * Signature: (JLjava/lang/String;JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)J + */ +JNIEXPORT jlong JNICALL Java_net_java_sip_communicator_impl_galagonotification_GalagoNotification_notify + (JNIEnv *, jclass, jlong, jstring, jlong, jstring, jstring, jstring); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/net/java/sip/communicator/impl/galagonotification/DBusException.java b/src/net/java/sip/communicator/impl/galagonotification/DBusException.java new file mode 100644 index 000000000..fca706b3d --- /dev/null +++ b/src/net/java/sip/communicator/impl/galagonotification/DBusException.java @@ -0,0 +1,30 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.galagonotification; + +/** + * Implements Exception for D-Bus errors reported through the native + * DBusError structure. + * + * @author Lubomir Marinov + */ +public class DBusException + extends Exception +{ + + /** + * Initializes a new DBusException instance with the specified + * detail message. + * + * @param message the detail message to later be reported by the new + * instance through its {@link #getMessage()} + */ + public DBusException(String message) + { + super(message); + } +} diff --git a/src/net/java/sip/communicator/impl/galagonotification/GalagoNotification.java b/src/net/java/sip/communicator/impl/galagonotification/GalagoNotification.java new file mode 100644 index 000000000..5a1dd2e43 --- /dev/null +++ b/src/net/java/sip/communicator/impl/galagonotification/GalagoNotification.java @@ -0,0 +1,95 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.galagonotification; + +/** + * Declares the native functions required by the galagonotification bundle. + * + * @author Lubomir Marinov + */ +public final class GalagoNotification +{ + static + { + System.loadLibrary("galagonotification"); + } + + /** + * Connects to the DBUS_BUS_SESSION D-Bus bus daemon and registers + * with it. + * + * @return a new reference to a DBusConnection to the + * DBUS_BUS_SESSION D-Bus bus daemon + * @throws DBusException if connecting to and registering with the + * DBUS_BUS_SESSION D-Bus bus daemon fails + */ + public static native long dbus_bus_get_session() + throws DBusException; + + /** + * Decrements the reference count of the specified DBusConnection + * and finalizes it if the count reaches zero. + * + * @param connection the DBusConnection to decrement the reference + * count of + */ + public static native void dbus_connection_unref(long connection); + + /** + * Invokes org.freedesktop.Notifications.GetCapabilities through + * the specified DBusConnection in order to retrieve the optional + * capabilities supported by the freedesktop.org Desktop Notifications + * server. + * + * @param connection the DBusConnection with the freedesktop.org + * Desktop Notifications server + * @return an array of Strings listing the optional capabilities + * supported by the freedesktop.org Desktop Notifications server + * @throws DBusException if retrieving the optional capabilities of the + * freedesktop.org Desktop Notifications server fails + */ + public static native String[] getCapabilities(long connection) + throws DBusException; + + /** + * Invokes org.freedesktop.Notifications.Notify through the + * specified DBusConnection in order to send a notification to the + * freedesktop.org Desktop Notifications server. + * + * @param connection the DBusConnection with the freedesktop.org + * Desktop Notifications server + * @param appName the optional name of the application sending the + * notification + * @param replacesId the optional notification identifier of an existing + * notification to be replaced by the notification being sent; 0 to + * not replace any existing notification + * @param appIcon the optional program icon of the application sending the + * notification. Not supported at this time. + * @param summary the summary text briefly describing the notification + * @param body the optional detailed body text of the notification + * @return the unique identifier of the sent notification if + * replacesId is 0; replacesId if + * replacesId is not 0 + * @throws DBusException if sending the notification to the freedesktop.org + * Desktop Notifications server fails + */ + public static native long notify( + long connection, + String appName, + long replacesId, + String appIcon, + String summary, + String body) + throws DBusException; + + /** + * Prevents the creation of GalagoNotification instances. + */ + private GalagoNotification() + { + } +} diff --git a/src/net/java/sip/communicator/impl/galagonotification/GalagoNotificationActivator.java b/src/net/java/sip/communicator/impl/galagonotification/GalagoNotificationActivator.java new file mode 100644 index 000000000..c8254ab88 --- /dev/null +++ b/src/net/java/sip/communicator/impl/galagonotification/GalagoNotificationActivator.java @@ -0,0 +1,159 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.galagonotification; + +import net.java.sip.communicator.service.resources.*; +import net.java.sip.communicator.service.systray.*; +import net.java.sip.communicator.util.*; + +import org.osgi.framework.*; + +/** + * Implements BundleActivator for the galagonotification bundle which + * provides an implementation of PopupMessageHandler according to the + * freedesktop.org Desktop Notifications spec. + * + * @author Lubomir Marinov + */ +public class GalagoNotificationActivator + implements BundleActivator +{ + + /** + * The Logger used by the GalagoNotificationActivator + * class and its instances for logging output. + */ + private static final Logger logger + = Logger.getLogger(GalagoNotificationActivator.class); + + /** + * The context in which the galagonotification bundle is executing. + */ + private static BundleContext bundleContext; + + /** + * The DBusConnection pointer to the D-Bus connection through which + * the freedesktop.org Desktop Notifications are being sent. + */ + static long dbusConnection; + + /** + * The resources such as internationalized and localized text and images + * used by the galagonotification bundle. + */ + private static ResourceManagementService resources; + + /** + * Gets the resources such as internationalized and localized text and + * images used by the galagonotification bundle. + * + * @return the resources such as internationalized and localized text and + * images used by the galagonotification bundle + */ + public static ResourceManagementService getResources() + { + if (resources == null) + resources = ResourceManagementServiceUtils.getService(bundleContext); + return resources; + } + + /** + * Starts the galagonotification bundle. Registers its + * PopupMessageHandler implementation if it is supported by the + * current operating system. + * + * @param bundleContext the context in which the galagonotification bundle + * is to execute + * @throws Exception if the PopupMessageHandler implementation of + * the galagonotification bundle is not supported by the current operating + * system + * @see BundleActivator#start(BundleContext) + */ + public void start(BundleContext bundleContext) + throws Exception + { + long dbusConnection = GalagoNotification.dbus_bus_get_session(); + + if (dbusConnection != 0) + { + + /* + * We don't much care about the very capabilities because they are + * optional, we just want to make sure that the service exists. + */ + String[] capabilities + = GalagoNotification.getCapabilities(dbusConnection); + + if (capabilities != null) + { + if (logger.isDebugEnabled()) + { + logger + .debug( + "org.freedesktop.Notifications.GetCapabilities:"); + for (String capability : capabilities) + logger.debug("\t" + capability); + } + + /* + * The native counterpart may return null without throwing an + * exception even when it fails to retrieve the capabilities. So + * it will not be safe to use galagonotification in this case. + * It is also unclear whether the server will return null when + * it does not support any optional capabilities. So it's not + * totally safe to assume that null means that + * galagonotification cannot be used. Anyway, displaying only + * the message title may be insufficient for our purposes so + * we'll require the optional "body" capability and solve the + * above questions. + */ + boolean bodyIsImplemented = false; + + for (String capability : capabilities) + if ("body".equals(capability)) + { + bodyIsImplemented = true; + break; + } + if (bodyIsImplemented) + { + GalagoNotificationActivator.bundleContext = bundleContext; + GalagoNotificationActivator.dbusConnection = dbusConnection; + + bundleContext + .registerService( + PopupMessageHandler.class.getName(), + new GalagoPopupMessageHandler(), + null); + } + else + GalagoNotification.dbus_connection_unref(dbusConnection); + } + } + } + + /** + * Stops the galagonotification bundle. + * + * @param bundleContext the context in which the galagonotification bundle + * is to stop its execution + * @throws Exception if there was an error during the stopping of the native + * functionality supporting the PopupNotificationHandler + * implementation of the galagonotification bundle + */ + public void stop(BundleContext bundleContext) + throws Exception + { + if (dbusConnection != 0) + { + GalagoNotification.dbus_connection_unref(dbusConnection); + dbusConnection = 0; + } + if (bundleContext.equals(GalagoNotificationActivator.bundleContext)) + GalagoNotificationActivator.bundleContext = null; + } +} diff --git a/src/net/java/sip/communicator/impl/galagonotification/GalagoPopupMessageHandler.java b/src/net/java/sip/communicator/impl/galagonotification/GalagoPopupMessageHandler.java new file mode 100644 index 000000000..4ddb5d352 --- /dev/null +++ b/src/net/java/sip/communicator/impl/galagonotification/GalagoPopupMessageHandler.java @@ -0,0 +1,85 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.galagonotification; + +import net.java.sip.communicator.service.systray.*; +import net.java.sip.communicator.util.*; + +/** + * Implements PopupMessageHandler according to the freedesktop.org + * Desktop Notifications spec. + * + * @author Lubomir Marinov + */ +public class GalagoPopupMessageHandler + extends AbstractPopupMessageHandler +{ + + /** + * The Logger used by the GalagoPopupMessageHandler class + * and its instances for logging output. + */ + private static final Logger logger + = Logger.getLogger(GalagoPopupMessageHandler.class); + + /** + * Returns the preference index of this PopupMessageHandler which + * indicates how many features it supports. + * + * @return the preference index of this PopupMessageHandler which + * indicates how many features it supports + * @see PopupMessageHandler#getPreferenceIndex() + */ + public int getPreferenceIndex() + { + return 1; // using a native popup mechanism + } + + /** + * Shows the title and the message of the specified PopupMessage. + * + * @param popupMessage the PopupMessage specifying the title and + * the message to be shown + * @see PopupMessageHandler#showPopupMessage(PopupMessage) + */ + public void showPopupMessage(PopupMessage popupMessage) + { + try + { + GalagoNotification + .notify( + GalagoNotificationActivator.dbusConnection, + null, + 0, + null, + popupMessage.getMessageTitle(), + popupMessage.getMessage()); + } + catch (DBusException dbe) + { + logger.error("Failed to show PopupMessage " + popupMessage, dbe); + } + } + + /** + * Gets the human-readable localized description of this + * PopupMessageHandler. + * + * @return the human-readable localized description of this + * PopupMessageHandler + * @see PopupMessageHandler#toString() + */ + @Override + public String toString() + { + return + GalagoNotificationActivator + .getResources() + .getI18NString( + "impl.galagonotification.POPUP_MESSAGE_HANDLER"); + } +} diff --git a/src/net/java/sip/communicator/impl/galagonotification/galagonotification.manifest.mf b/src/net/java/sip/communicator/impl/galagonotification/galagonotification.manifest.mf new file mode 100644 index 000000000..adeaa3ab7 --- /dev/null +++ b/src/net/java/sip/communicator/impl/galagonotification/galagonotification.manifest.mf @@ -0,0 +1,10 @@ +Bundle-Activator: net.java.sip.communicator.impl.galagonotification.GalagoNotificationActivator +Bundle-Name: Desktop Notifications Provider +Bundle-Description: A bundle which implements notifications according to the freedesktop.org Desktop Notifications spec. +Bundle-Vendor: sip-communicator.org +Bundle-Version: 0.0.1 +System-Bundle: yes +Import-Package: net.java.sip.communicator.service.systray, + net.java.sip.communicator.service.resources, + net.java.sip.communicator.util, + org.osgi.framework