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