aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.bzrignore46
-rw-r--r--bindings/mono/ApplicationIndicator.custom26
-rw-r--r--bindings/mono/Makefile.am53
-rw-r--r--bindings/mono/libappindicator-api.metadata26
-rw-r--r--configure.ac11
-rw-r--r--docs/reference/libappindicator-sections.txt2
-rw-r--r--docs/reference/tmpl/libappindicator-unused.sgml6
-rw-r--r--example/simple-client.c35
-rw-r--r--src/Makefile.am19
-rw-r--r--src/application-service-appstore.c475
-rw-r--r--src/application-service-appstore.h9
-rw-r--r--src/application-service-lru-file.c473
-rw-r--r--src/application-service-lru-file.h59
-rw-r--r--src/application-service-marshal.list3
-rw-r--r--src/application-service-watcher.c56
-rw-r--r--src/application-service.c11
-rw-r--r--src/application-service.xml7
-rw-r--r--src/dbus-properties-client.h139
-rw-r--r--src/dbus-shared.h8
-rw-r--r--src/indicator-application.c216
-rw-r--r--src/libappindicator/app-indicator-enum-types.gen.c.in (renamed from src/libappindicator/app-indicator-enum-types.c.in)0
-rw-r--r--src/libappindicator/app-indicator.c532
-rw-r--r--src/libappindicator/app-indicator.h22
-rw-r--r--src/notification-item.xml7
-rw-r--r--src/notification-watcher.xml8
-rw-r--r--tests/Makefile.am37
-rw-r--r--tests/test-defines.h4
-rw-r--r--tests/test-libappindicator-dbus-client.c2
-rw-r--r--tests/test-libappindicator-fallback-item.c130
-rw-r--r--tests/test-libappindicator-fallback-watcher.c97
30 files changed, 2381 insertions, 138 deletions
diff --git a/.bzrignore b/.bzrignore
index 4856033..a218817 100644
--- a/.bzrignore
+++ b/.bzrignore
@@ -44,3 +44,49 @@ src/libapplication_la-application-service-marshal.lo
src/libapplication_la-indicator-application.lo
src/libappindicator/app-indicator-enum-types.c
src/libappindicator/app-indicator-enum-types.h
+gtk-doc.make
+py-compile
+bindings/mono/appindicator-sharp-0.1.pc
+bindings/mono/appindicator-sharp.dll
+bindings/mono/appindicator-sharp.dll.config
+bindings/mono/generated
+bindings/mono/generated-stamp
+bindings/mono/libappindicator-api.raw
+bindings/mono/libappindicator-api.xml
+bindings/mono/examples/indicator-example
+bindings/python/.deps
+bindings/python/.libs
+bindings/python/_appindicator.la
+bindings/python/appindicator.c
+bindings/python/appindicator.lo
+bindings/python/appindicatormodule.lo
+docs/reference/.libs
+docs/reference/gtkdoc-in-srcdir
+docs/reference/html
+docs/reference/html-build.stamp
+docs/reference/html.stamp
+docs/reference/libappindicator-decl-list.txt
+docs/reference/libappindicator-decl.txt
+docs/reference/libappindicator-docs.sgml
+docs/reference/libappindicator-overrides.txt
+docs/reference/libappindicator-undeclared.txt
+docs/reference/libappindicator-undocumented.txt
+docs/reference/libappindicator-unused.txt
+docs/reference/libappindicator.args
+docs/reference/libappindicator.hierarchy
+docs/reference/libappindicator.interfaces
+docs/reference/libappindicator.prerequisites
+docs/reference/libappindicator.signals
+docs/reference/scan-build.stamp
+docs/reference/sgml-build.stamp
+docs/reference/sgml.stamp
+docs/reference/tmpl-build.stamp
+docs/reference/tmpl.stamp
+docs/reference/version.xml
+docs/reference/xml
+docs/reference/tmpl/app-indicator.sgml
+docs/reference/tmpl/app-indicator.sgml.bak
+src/libappindicator/appindicator-0.1.pc
+tests/test-libappindicator-fallback-item
+tests/test-libappindicator-fallback-watcher
+tests/test-libappindicator-fallback
diff --git a/bindings/mono/ApplicationIndicator.custom b/bindings/mono/ApplicationIndicator.custom
new file mode 100644
index 0000000..11d16a8
--- /dev/null
+++ b/bindings/mono/ApplicationIndicator.custom
@@ -0,0 +1,26 @@
+[DllImport ("libappindicator.so.0")]
+static extern int app_indicator_get_status (IntPtr i);
+
+[DllImport ("libappindicator.so.0")]
+static extern int app_indicator_get_category (IntPtr i);
+
+[DllImport ("libappindicator.so.0")]
+static extern void app_indicator_set_status (IntPtr i, int s);
+
+ [GLib.Property ("status")]
+ public Status Status {
+ get {
+ return (Status) app_indicator_get_status (Handle);
+ }
+
+ set {
+ app_indicator_set_status (Handle, (int) value);
+ }
+ }
+
+ [GLib.Property ("category")]
+ public Category Category {
+ get {
+ return (Category) app_indicator_get_category (Handle);
+ }
+ } \ No newline at end of file
diff --git a/bindings/mono/Makefile.am b/bindings/mono/Makefile.am
index dedbeb7..455a23e 100644
--- a/bindings/mono/Makefile.am
+++ b/bindings/mono/Makefile.am
@@ -6,6 +6,7 @@ pkgconfig_DATA = appindicator-sharp-0.1.pc
TEST = AppIndicator.Test.dll
API = libappindicator-api.xml
+MIDDLE_API = libappindicator-api.middle
RAW_API = libappindicator-api.raw
METADATA = libappindicator-api.metadata
ASSEMBLY_NAME = appindicator-sharp
@@ -13,16 +14,18 @@ ASSEMBLY = appindicator-sharp.dll
TARGET = $(ASSEMBLY) $(ASSEMBLY).config
assemblydir = $(libdir)/appindicator-sharp-0.1
assembly_DATA = $(TARGET)
-CLEANFILES = $(ASSEMBLY) $(ASSEMBLY).mdb generated-stamp generated/*.cs $(API) $(RAW_API) $(TEST)
+CLEANFILES = $(ASSEMBLY) $(ASSEMBLY).mdb generated-stamp generated/*.cs $(API) $(MIDDLE_API) $(RAW_API) $(TEST)
DISTCLEANFILES = $(ASSEMBLY).config
TEST_SOURCES = TestIndicator.cs
-EXTRA_DIST = \
- $(RAW_API) \
- $(METADATA) \
- appindicator-sharp-0.1.pc.in \
- appindicator-sharp.dll.config.in \
- app-indicator.sources.xml \
- $(ASSEMBLY_NAME).snk \
+customs = ApplicationIndicator.custom
+EXTRA_DIST = \
+ $(RAW_API) \
+ $(METADATA) \
+ appindicator-sharp-0.1.pc.in \
+ appindicator-sharp.dll.config.in \
+ app-indicator.sources.xml \
+ $(ASSEMBLY_NAME).snk \
+ $(customs) \
$(TEST_SOURCES)
GACUTIL_FLAGS="-package $(ASSEMBLY_NAME) -root $(DESTDIR)$(prefix)/lib"
@@ -33,19 +36,41 @@ test_references = $(GTK_SHARP_LIBS) $(NUNIT_LIBS) -r:$(ASSEMBLY)
$(RAW_API): app-indicator.sources.xml
$(GAPI_PARSER) app-indicator.sources.xml
-$(API): $(METADATA) $(RAW_API)
- cp $(srcdir)/$(RAW_API) $(API)
- chmod u+w $(API)
+$(MIDDLE_API): $(METADATA) $(RAW_API)
+ cp $(srcdir)/$(RAW_API) $(MIDDLE_API)
+ chmod u+w $(MIDDLE_API)
@if test -n '$(METADATA)'; then \
- echo "$(GAPI_FIXUP) --api=$(API) --metadata=$(srcdir)/$(METADATA)"; \
- $(GAPI_FIXUP) --api=$(API) --metadata=$(srcdir)/$(METADATA); \
+ echo "$(GAPI_FIXUP) --api=$(MIDDLE_API) --metadata=$(srcdir)/$(METADATA)"; \
+ $(GAPI_FIXUP) --api=$(MIDDLE_API) --metadata=$(srcdir)/$(METADATA); \
fi
+$(API): $(MIDDLE_API) Makefile.am
+ sed -e "s|PROP_ID_S|ID|" \
+ -e "s|PROP_ID_S|id|" \
+ -e "s|PROP_STATUS_S|Status|" \
+ -e "s|PROP_STATUS_S|status|" \
+ -e "s|PROP_CATEGORY_S|Category|" \
+ -e "s|PROP_CATEGORY_S|category|" \
+ -e "s|PROP_ICON_NAME_S|IconName|" \
+ -e "s|PROP_ICON_NAME_S|icon-name|" \
+ -e "s|PROP_ATTENTION_ICON_NAME_S|AttentionIconName|" \
+ -e "s|PROP_ATTENTION_ICON_NAME_S|attention-icon-name|" \
+ -e "s|PROP_ICON_THEME_PATH_S|IconThemePath|" \
+ -e "s|PROP_ICON_THEME_PATH_S|icon-theme-path|" \
+ -e "s|PROP_MENU_S|Menu|" \
+ -e "s|PROP_MENU_S|menu|" \
+ -e "s|PROP_CONNECTED_S|Connected|" \
+ -e "s|PROP_CONNECTED_S|connected|" \
+ $< > $@
+
api_includes = $(GTK_SHARP_CFLAGS)
-generated-stamp: $(API)
+build_customs = $(addprefix $(srcdir)/, $(customs))
+
+generated-stamp: $(API) $(build_customs)
rm -f generated/* && \
$(GAPI_CODEGEN) --generate $(API) $(api_includes) \
+ --customdir=$(srcdir) \
--outdir=generated --assembly-name=$(ASSEMBLY_NAME) \
&& touch generated-stamp
diff --git a/bindings/mono/libappindicator-api.metadata b/bindings/mono/libappindicator-api.metadata
index f019f0a..86f0f26 100644
--- a/bindings/mono/libappindicator-api.metadata
+++ b/bindings/mono/libappindicator-api.metadata
@@ -5,27 +5,27 @@
<attr path="/api/namespace/object[@cname='AppIndicator']/signal[@field_name='new_status']" name="name">NewStatus</attr>
<attr path="/api/namespace/object[@cname='AppIndicator']/signal[@field_name='connection_changed']" name="name">ConnectionChanged</attr>
<attr path="/api/namespace/object[@cname='AppIndicator']/signal[@field_name='new_icon']" name="name">NewIcon</attr>
- <attr path="/api/namespace/object[@cname='AppIndicator']/property[@cname='PROP_ID_S']" name="name">ID</attr>
- <attr path="/api/namespace/object[@cname='AppIndicator']/property[@cname='PROP_CATEGORY_S']" name="name">Category</attr>
- <attr path="/api/namespace/object[@cname='AppIndicator']/property[@cname='PROP_CATEGORY_S']" name="type">AppIndicatorCategory</attr>
- <attr path="/api/namespace/object[@cname='AppIndicator']/property[@cname='PROP_STATUS_S']" name="name">Status</attr>
- <attr path="/api/namespace/object[@cname='AppIndicator']/property[@cname='PROP_STATUS_S']" name="type">AppIndicatorStatus</attr>
- <attr path="/api/namespace/object[@cname='AppIndicator']/property[@cname='PROP_ICON_NAME_S']" name="name">IconName</attr>
- <attr path="/api/namespace/object[@cname='AppIndicator']/property[@cname='PROP_ATTENTION_ICON_NAME_S']" name="name">AttentionIconName</attr>
- <attr path="/api/namespace/object[@cname='AppIndicator']/property[@cname='PROP_MENU_S']" name="name">Menu</attr>
- <attr path="/api/namespace/object[@cname='AppIndicator']/property[@cname='PROP_MENU_S']" name="type">GtkMenu*</attr>
- <attr path="/api/namespace/object[@cname='AppIndicator']/property[@cname='PROP_CONNECTED_S']" name="name">Connected</attr>
+ <attr path="/api/namespace/object[@cname='AppIndicator']/property[@cname='id']" name="name">ID</attr>
+ <attr path="/api/namespace/object[@cname='AppIndicator']/property[@cname='PROP_CATEGORY_S']" name="hidden"></attr>
+ <attr path="/api/namespace/object[@cname='AppIndicator']/property[@cname='PROP_CATEGORY_S']" name="hidden"></attr>
+ <attr path="/api/namespace/object[@cname='AppIndicator']/property[@cname='PROP_STATUS_S']" name="hidden"></attr>
+ <attr path="/api/namespace/object[@cname='AppIndicator']/property[@cname='PROP_STATUS_S']" name="hidden"></attr>
+ <attr path="/api/namespace/object[@cname='AppIndicator']/property[@cname='icon-name']" name="name">IconName</attr>
+ <attr path="/api/namespace/object[@cname='AppIndicator']/property[@cname='attention-icon-name']" name="name">AttentionIconName</attr>
+ <attr path="/api/namespace/object[@cname='AppIndicator']/property[@cname='menu']" name="name">Menu</attr>
+ <attr path="/api/namespace/object[@cname='AppIndicator']/property[@cname='menu']" name="type">GtkMenu*</attr>
+ <attr path="/api/namespace/object[@cname='AppIndicator']/property[@cname='connected']" name="name">Connected</attr>
<attr path="/api/namespace/object[@cname='AppIndicator']/method[@name='SetMenu']" name="name">SetMenu</attr>
-
+
<remove-node path="/api/namespace/object/method[@cname='app_indicator_get_id']" />
<remove-node path="/api/namespace/object/method[@cname='app_indicator_get_status']" />
<remove-node path="/api/namespace/object/method[@cname='app_indicator_get_icon']" />
<remove-node path="/api/namespace/object/method[@cname='app_indicator_get_category']" />
<remove-node path="/api/namespace/object/method[@cname='app_indicator_get_attention_icon']" />
-
+
<remove-node path="/api/namespace/object/method[@cname='app_indicator_set_id']" />
<remove-node path="/api/namespace/object/method[@cname='app_indicator_set_status']" />
<remove-node path="/api/namespace/object/method[@cname='app_indicator_set_icon']" />
<remove-node path="/api/namespace/object/method[@cname='app_indicator_set_category']" />
- <remove-node path="/api/namespace/object/method[@cname='app_indicator_set_attention_icon']" />
+ <remove-node path="/api/namespace/object/method[@cname='app_indicator_set_attention_icon']" />
</metadata>
diff --git a/configure.ac b/configure.ac
index 46830aa..61be6f8 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,11 +1,11 @@
-AC_INIT(indicator-application, 0.0.6, ted@canonical.com)
-AC_COPYRIGHT([Copyright 2009 Canonical])
+AC_INIT(indicator-application, 0.0.10, ted@canonical.com)
+AC_COPYRIGHT([Copyright 2009, 2010 Canonical])
AC_PREREQ(2.53)
AM_CONFIG_HEADER(config.h)
-AM_INIT_AUTOMAKE(indicator-application, 0.0.6)
+AM_INIT_AUTOMAKE(indicator-application, 0.0.10)
AM_MAINTAINER_MODE
@@ -38,11 +38,13 @@ AC_CONFIG_MACRO_DIR(m4)
###########################
GTK_REQUIRED_VERSION=2.12
-INDICATOR_REQUIRED_VERSION=0.3.0
+INDICATOR_REQUIRED_VERSION=0.3.1
DBUSMENUGTK_REQUIRED_VERSION=0.1.1
+JSON_GLIB_REQUIRED_VERSION=0.7.6
PKG_CHECK_MODULES(INDICATOR, gtk+-2.0 >= $GTK_REQUIRED_VERSION
indicator >= $INDICATOR_REQUIRED_VERSION
+ json-glib-1.0 >= $JSON_GLIB_REQUIRED_VERSION
dbusmenu-gtk >= $DBUSMENUGTK_REQUIRED_VERSION)
AC_SUBST(INDICATOR_CFLAGS)
@@ -51,6 +53,7 @@ AC_SUBST(INDICATOR_LIBS)
###########################
# Check for Mono support
###########################
+
MONO_REQUIRED_VERSION=1.0
PKG_CHECK_MODULES(MONO_DEPENDENCY, mono >= $MONO_REQUIRED_VERSION, has_mono=true, has_mono=false)
diff --git a/docs/reference/libappindicator-sections.txt b/docs/reference/libappindicator-sections.txt
index b994297..70df0b8 100644
--- a/docs/reference/libappindicator-sections.txt
+++ b/docs/reference/libappindicator-sections.txt
@@ -18,6 +18,7 @@ AppIndicator
AppIndicatorClass
app_indicator_get_type
app_indicator_new
+app_indicator_new_with_path
app_indicator_set_status
app_indicator_set_attention_icon
app_indicator_set_menu
@@ -27,5 +28,6 @@ app_indicator_get_category
app_indicator_get_status
app_indicator_get_icon
app_indicator_get_attention_icon
+app_indicator_get_menu
</SECTION>
diff --git a/docs/reference/tmpl/libappindicator-unused.sgml b/docs/reference/tmpl/libappindicator-unused.sgml
index e69de29..1428b51 100644
--- a/docs/reference/tmpl/libappindicator-unused.sgml
+++ b/docs/reference/tmpl/libappindicator-unused.sgml
@@ -0,0 +1,6 @@
+<!-- ##### ARG AppIndicator:icon-path ##### -->
+<para>
+
+</para>
+
+
diff --git a/example/simple-client.c b/example/simple-client.c
index ea4da9d..6dcf5d1 100644
--- a/example/simple-client.c
+++ b/example/simple-client.c
@@ -34,6 +34,22 @@ item_clicked_cb (GtkWidget *widget, gpointer data)
g_print ("%s clicked!\n", text);
}
+static void
+toggle_sensitivity_cb (GtkWidget *widget, gpointer data)
+{
+ GtkWidget *target = (GtkWidget *)data;
+
+ gtk_menu_item_set_label (GTK_MENU_ITEM (target), "modified!!");
+ gtk_widget_set_sensitive (target, !GTK_WIDGET_IS_SENSITIVE (target));
+}
+
+static void
+image_clicked_cb (GtkWidget *widget, gpointer data)
+{
+ gtk_image_set_from_stock (GTK_IMAGE (GTK_IMAGE_MENU_ITEM (widget)->image),
+ GTK_STOCK_OPEN, GTK_ICON_SIZE_MENU);
+}
+
int
main (int argc, char ** argv)
{
@@ -53,20 +69,35 @@ main (int argc, char ** argv)
app_indicator_set_attention_icon(ci, "indicator-messages-new");
menu = gtk_menu_new ();
- GtkWidget *item = gtk_menu_item_new_with_label ("1");
+ GtkWidget *item = gtk_check_menu_item_new_with_label ("1");
g_signal_connect (item, "activate",
G_CALLBACK (item_clicked_cb), "1");
gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+ gtk_widget_show(item);
- item = gtk_menu_item_new_with_label ("2");
+ item = gtk_radio_menu_item_new_with_label (NULL, "2");
g_signal_connect (item, "activate",
G_CALLBACK (item_clicked_cb), "2");
gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+ gtk_widget_show(item);
item = gtk_menu_item_new_with_label ("3");
g_signal_connect (item, "activate",
G_CALLBACK (item_clicked_cb), "3");
gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+ gtk_widget_show(item);
+
+ GtkWidget *toggle_item = gtk_menu_item_new_with_label ("Toggle 3");
+ g_signal_connect (toggle_item, "activate",
+ G_CALLBACK (toggle_sensitivity_cb), item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), toggle_item);
+ gtk_widget_show(toggle_item);
+
+ item = gtk_image_menu_item_new_from_stock (GTK_STOCK_NEW, NULL);
+ g_signal_connect (item, "activate",
+ G_CALLBACK (image_clicked_cb), NULL);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+ gtk_widget_show(item);
app_indicator_set_menu (ci, GTK_MENU (menu));
diff --git a/src/Makefile.am b/src/Makefile.am
index f101d12..efae713 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -35,6 +35,8 @@ indicator_application_service_SOURCES = \
application-service.c \
application-service-appstore.h \
application-service-appstore.c \
+ application-service-lru-file.h \
+ application-service-lru-file.c \
application-service-marshal.h \
application-service-marshal.c \
application-service-server.h \
@@ -61,9 +63,21 @@ pkgconfig_DATA = libappindicator/appindicator-0.1.pc
pkgconfigdir = $(libdir)/pkgconfig
glib_enum_h = libappindicator/app-indicator-enum-types.h
-glib_enum_c = libappindicator/app-indicator-enum-types.c
+glib_enum_c = libappindicator/app-indicator-enum-types.gen.c
glib_enum_headers = $(libappindicator_headers)
+libappindicator/app-indicator-enum-types.c: libappindicator/app-indicator-enum-types.gen.c
+ sed -e "s|\"passive\"|\"Passive\"|" \
+ -e "s|\"active\"|\"Active\"|" \
+ -e "s|\"attention\"|\"NeedsAttention\"|" \
+ -e "s|\"application-status\"|\"ApplicationStatus\"|" \
+ -e "s|\"communications\"|\"Communications\"|" \
+ -e "s|\"system-services\"|\"SystemServices\"|" \
+ -e "s|\"hardware\"|\"Hardware\"|" \
+ -e "s|\"other\"|\"Other\"|" \
+ $< > $@
+DISTCLEANFILES += libappindicator/app-indicator-enum-types.c
+
lib_LTLIBRARIES = \
libappindicator.la
@@ -78,7 +92,7 @@ libappindicatorinclude_HEADERS = \
libappindicator_la_SOURCES = \
$(libappindicator_headers) \
- $(glib_enum_c) \
+ libappindicator/app-indicator-enum-types.c \
notification-watcher-client.h \
notification-item-server.h \
libappindicator/app-indicator.c
@@ -101,7 +115,6 @@ libappindicator_la_LIBADD = \
DBUS_SPECS = \
application-service.xml \
- dbus-properties.xml \
notification-item.xml \
notification-watcher.xml
diff --git a/src/application-service-appstore.c b/src/application-service-appstore.c
index 1391d33..70fab18 100644
--- a/src/application-service-appstore.c
+++ b/src/application-service-appstore.c
@@ -31,7 +31,7 @@ with this program. If not, see <http://www.gnu.org/licenses/>.
#include "dbus-shared.h"
/* DBus Prototypes */
-static gboolean _application_service_server_get_applications (ApplicationServiceAppstore * appstore, GArray ** apps);
+static gboolean _application_service_server_get_applications (ApplicationServiceAppstore * appstore, GPtrArray ** apps, GError ** error);
#include "application-service-server.h"
@@ -40,22 +40,48 @@ static gboolean _application_service_server_get_applications (ApplicationService
#define NOTIFICATION_ITEM_PROP_STATUS "Status"
#define NOTIFICATION_ITEM_PROP_ICON_NAME "IconName"
#define NOTIFICATION_ITEM_PROP_AICON_NAME "AttentionIconName"
+#define NOTIFICATION_ITEM_PROP_ICON_PATH "IconThemePath"
#define NOTIFICATION_ITEM_PROP_MENU "Menu"
+#define NOTIFICATION_ITEM_SIG_NEW_ICON "NewIcon"
+#define NOTIFICATION_ITEM_SIG_NEW_AICON "NewAttentionIcon"
+#define NOTIFICATION_ITEM_SIG_NEW_STATUS "NewStatus"
+
/* Private Stuff */
typedef struct _ApplicationServiceAppstorePrivate ApplicationServiceAppstorePrivate;
struct _ApplicationServiceAppstorePrivate {
DBusGConnection * bus;
GList * applications;
+ AppLruFile * lrufile;
+};
+
+#define APP_STATUS_PASSIVE_STR "Passive"
+#define APP_STATUS_ACTIVE_STR "Active"
+#define APP_STATUS_ATTENTION_STR "NeedsAttention"
+
+typedef enum _ApplicationStatus ApplicationStatus;
+enum _ApplicationStatus {
+ APP_STATUS_PASSIVE,
+ APP_STATUS_ACTIVE,
+ APP_STATUS_ATTENTION
};
typedef struct _Application Application;
struct _Application {
+ gchar * id;
+ gchar * category;
gchar * dbus_name;
gchar * dbus_object;
ApplicationServiceAppstore * appstore; /* not ref'd */
DBusGProxy * dbus_proxy;
DBusGProxy * prop_proxy;
+ gboolean validated; /* Whether we've gotten all the parameters and they look good. */
+ ApplicationStatus status;
+ gchar * icon;
+ gchar * aicon;
+ gchar * menu;
+ gchar * icon_path;
+ gboolean currently_free;
};
#define APPLICATION_SERVICE_APPSTORE_GET_PRIVATE(o) \
@@ -65,6 +91,7 @@ struct _Application {
enum {
APPLICATION_ADDED,
APPLICATION_REMOVED,
+ APPLICATION_ICON_CHANGED,
LAST_SIGNAL
};
@@ -75,6 +102,8 @@ static void application_service_appstore_class_init (ApplicationServiceAppstoreC
static void application_service_appstore_init (ApplicationServiceAppstore *self);
static void application_service_appstore_dispose (GObject *object);
static void application_service_appstore_finalize (GObject *object);
+static ApplicationStatus string_to_status(const gchar * status_string);
+static void apply_status (Application * app, ApplicationStatus status);
G_DEFINE_TYPE (ApplicationServiceAppstore, application_service_appstore, G_TYPE_OBJECT);
@@ -91,18 +120,24 @@ application_service_appstore_class_init (ApplicationServiceAppstoreClass *klass)
signals[APPLICATION_ADDED] = g_signal_new ("application-added",
G_TYPE_FROM_CLASS(klass),
G_SIGNAL_RUN_LAST,
- G_STRUCT_OFFSET (ApplicationServiceAppstore, application_added),
+ G_STRUCT_OFFSET (ApplicationServiceAppstoreClass, application_added),
NULL, NULL,
- _application_service_marshal_VOID__STRING_INT_STRING_STRING,
- G_TYPE_NONE, 4, G_TYPE_STRING, G_TYPE_INT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_NONE);
+ _application_service_marshal_VOID__STRING_INT_STRING_STRING_STRING,
+ G_TYPE_NONE, 5, G_TYPE_STRING, G_TYPE_INT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_NONE);
signals[APPLICATION_REMOVED] = g_signal_new ("application-removed",
G_TYPE_FROM_CLASS(klass),
G_SIGNAL_RUN_LAST,
- G_STRUCT_OFFSET (ApplicationServiceAppstore, application_removed),
+ G_STRUCT_OFFSET (ApplicationServiceAppstoreClass, application_removed),
NULL, NULL,
g_cclosure_marshal_VOID__INT,
G_TYPE_NONE, 1, G_TYPE_INT, G_TYPE_NONE);
-
+ signals[APPLICATION_ICON_CHANGED] = g_signal_new ("application-icon-changed",
+ G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (ApplicationServiceAppstoreClass, application_icon_changed),
+ NULL, NULL,
+ _application_service_marshal_VOID__INT_STRING,
+ G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_STRING, G_TYPE_NONE);
dbus_g_object_type_install_info(APPLICATION_SERVICE_APPSTORE_TYPE,
&dbus_glib__application_service_server_object_info);
@@ -116,6 +151,7 @@ application_service_appstore_init (ApplicationServiceAppstore *self)
ApplicationServiceAppstorePrivate * priv = APPLICATION_SERVICE_APPSTORE_GET_PRIVATE(self);
priv->applications = NULL;
+ priv->lrufile = NULL;
GError * error = NULL;
priv->bus = dbus_g_bus_get(DBUS_BUS_STARTER, &error);
@@ -155,6 +191,9 @@ application_service_appstore_finalize (GObject *object)
return;
}
+/* Return from getting the properties from the item. We're looking at those
+ and making sure we have everythign that we need. If we do, then we'll
+ move on up to sending this onto the indicator. */
static void
get_all_properties_cb (DBusGProxy * proxy, GHashTable * properties, GError * error, gpointer data)
{
@@ -166,43 +205,109 @@ get_all_properties_cb (DBusGProxy * proxy, GHashTable * properties, GError * err
Application * app = (Application *)data;
if (g_hash_table_lookup(properties, NOTIFICATION_ITEM_PROP_MENU) == NULL ||
+ g_hash_table_lookup(properties, NOTIFICATION_ITEM_PROP_ID) == NULL ||
+ g_hash_table_lookup(properties, NOTIFICATION_ITEM_PROP_CATEGORY) == NULL ||
+ g_hash_table_lookup(properties, NOTIFICATION_ITEM_PROP_STATUS) == NULL ||
g_hash_table_lookup(properties, NOTIFICATION_ITEM_PROP_ICON_NAME) == NULL) {
g_warning("Notification Item on object %s of %s doesn't have enough properties.", app->dbus_object, app->dbus_name);
g_free(app); // Need to do more than this, but it gives the idea of the flow we're going for.
return;
}
+ app->validated = TRUE;
+
+ app->id = g_value_dup_string(g_hash_table_lookup(properties, NOTIFICATION_ITEM_PROP_ID));
+ app->category = g_value_dup_string(g_hash_table_lookup(properties, NOTIFICATION_ITEM_PROP_CATEGORY));
ApplicationServiceAppstorePrivate * priv = APPLICATION_SERVICE_APPSTORE_GET_PRIVATE(app->appstore);
- priv->applications = g_list_prepend(priv->applications, app);
-
- /* TODO: We need to have the position determined better. This
- would involve looking at the name and category and sorting
- it with the other entries. */
-
- g_signal_emit(G_OBJECT(app->appstore),
- signals[APPLICATION_ADDED], 0,
- g_value_get_string(g_hash_table_lookup(properties, NOTIFICATION_ITEM_PROP_ICON_NAME)),
- 0, /* Position */
- app->dbus_name,
- g_value_get_string(g_hash_table_lookup(properties, NOTIFICATION_ITEM_PROP_MENU)),
- TRUE);
+ app_lru_file_touch(priv->lrufile, app->id, app->category);
+
+ app->icon = g_value_dup_string(g_hash_table_lookup(properties, NOTIFICATION_ITEM_PROP_ICON_NAME));
+ app->menu = g_value_dup_string(g_hash_table_lookup(properties, NOTIFICATION_ITEM_PROP_MENU));
+ if (g_hash_table_lookup(properties, NOTIFICATION_ITEM_PROP_AICON_NAME) != NULL) {
+ app->aicon = g_value_dup_string(g_hash_table_lookup(properties, NOTIFICATION_ITEM_PROP_ICON_NAME));
+ }
+
+ gpointer icon_path_data = g_hash_table_lookup(properties, NOTIFICATION_ITEM_PROP_ICON_PATH);
+ if (icon_path_data != NULL) {
+ app->icon_path = g_value_dup_string((GValue *)icon_path_data);
+ } else {
+ app->icon_path = g_strdup("");
+ }
+
+ apply_status(app, string_to_status(g_value_get_string(g_hash_table_lookup(properties, NOTIFICATION_ITEM_PROP_STATUS))));
return;
}
+/* Simple translation function -- could be optimized */
+static ApplicationStatus
+string_to_status(const gchar * status_string)
+{
+ if (!g_strcmp0(status_string, APP_STATUS_ACTIVE_STR))
+ return APP_STATUS_ACTIVE;
+ if (!g_strcmp0(status_string, APP_STATUS_ATTENTION_STR))
+ return APP_STATUS_ATTENTION;
+ return APP_STATUS_PASSIVE;
+}
+
+/* A small helper function to get the position of an application
+ in the app list. */
+static gint
+get_position (Application * app) {
+ ApplicationServiceAppstore * appstore = app->appstore;
+ ApplicationServiceAppstorePrivate * priv = APPLICATION_SERVICE_APPSTORE_GET_PRIVATE(appstore);
+
+ GList * applistitem = g_list_find(priv->applications, app);
+ if (applistitem == NULL) {
+ return -1;
+ }
+
+ return g_list_position(priv->applications, applistitem);
+}
+
/* A simple global function for dealing with freeing the information
in an Application structure */
static void
application_free (Application * app)
{
if (app == NULL) return;
+
+ /* Handle the case where this could be called by unref'ing one of
+ the proxy objects. */
+ if (app->currently_free) return;
+ app->currently_free = TRUE;
+
+ if (app->dbus_proxy) {
+ g_object_unref(app->dbus_proxy);
+ }
+ if (app->prop_proxy) {
+ g_object_unref(app->prop_proxy);
+ }
+ if (app->id != NULL) {
+ g_free(app->id);
+ }
+ if (app->category != NULL) {
+ g_free(app->category);
+ }
if (app->dbus_name != NULL) {
g_free(app->dbus_name);
}
if (app->dbus_object != NULL) {
g_free(app->dbus_object);
}
+ if (app->icon != NULL) {
+ g_free(app->icon);
+ }
+ if (app->aicon != NULL) {
+ g_free(app->aicon);
+ }
+ if (app->menu != NULL) {
+ g_free(app->menu);
+ }
+ if (app->icon_path != NULL) {
+ g_free(app->icon_path);
+ }
g_free(app);
return;
@@ -214,24 +319,229 @@ static void
application_removed_cb (DBusGProxy * proxy, gpointer userdata)
{
Application * app = (Application *)userdata;
+
+ /* Remove from the panel */
+ apply_status(app, APP_STATUS_PASSIVE);
+
+ /* Destroy the data */
+ application_free(app);
+ return;
+}
+
+static gboolean
+can_add_application (GList *applications, Application *app)
+{
+ if (applications)
+ {
+ GList *l = NULL;
+
+ for (l = applications; l != NULL; l = g_list_next (l))
+ {
+ Application *tmp_app = (Application *)l->data;
+
+ if (g_strcmp0 (tmp_app->dbus_name, app->dbus_name) == 0 &&
+ g_strcmp0 (tmp_app->dbus_object, app->dbus_object) == 0)
+ {
+ return FALSE;
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+/* This function takes two Application structure
+ pointers and uses the lrufile to compare them. */
+static gint
+app_sort_func (gconstpointer a, gconstpointer b, gpointer userdata)
+{
+ Application * appa = (Application *)a;
+ Application * appb = (Application *)b;
+ AppLruFile * lrufile = (AppLruFile *)userdata;
+
+ return app_lru_file_sort(lrufile, appa->id, appb->id);
+}
+
+/* Change the status of the application. If we're going passive
+ it removes it from the panel. If we're coming online, then
+ it add it to the panel. Otherwise it changes the icon. */
+static void
+apply_status (Application * app, ApplicationStatus status)
+{
+ if (app->status == status) {
+ return;
+ }
+ g_debug("Changing app status to: %d", status);
+
ApplicationServiceAppstore * appstore = app->appstore;
ApplicationServiceAppstorePrivate * priv = APPLICATION_SERVICE_APPSTORE_GET_PRIVATE(appstore);
- GList * applistitem = g_list_find(priv->applications, app);
- if (applistitem == NULL) {
- g_warning("Removing an application that isn't in the application list?");
+ /* This means we're going off line */
+ if (status == APP_STATUS_PASSIVE) {
+ gint position = get_position(app);
+ if (position == -1) return;
+
+ g_signal_emit(G_OBJECT(appstore),
+ signals[APPLICATION_REMOVED], 0,
+ position, TRUE);
+ priv->applications = g_list_remove(priv->applications, app);
+ } else {
+ /* Figure out which icon we should be using */
+ gchar * newicon = app->icon;
+ if (status == APP_STATUS_ATTENTION && app->aicon != NULL) {
+ newicon = app->aicon;
+ }
+
+ /* Determine whether we're already shown or not */
+ if (app->status == APP_STATUS_PASSIVE) {
+ if (can_add_application (priv->applications, app)) {
+ /* Put on panel */
+ priv->applications = g_list_insert_sorted_with_data (priv->applications, app, app_sort_func, priv->lrufile);
+
+ g_signal_emit(G_OBJECT(app->appstore),
+ signals[APPLICATION_ADDED], 0,
+ newicon,
+ g_list_index(priv->applications, app), /* Position */
+ app->dbus_name,
+ app->menu,
+ app->icon_path,
+ TRUE);
+ }
+ } else {
+ /* Icon update */
+ gint position = get_position(app);
+ if (position == -1) return;
+
+ g_signal_emit(G_OBJECT(appstore),
+ signals[APPLICATION_ICON_CHANGED], 0,
+ position, newicon, TRUE);
+ }
+ }
+
+ app->status = status;
+
+ return;
+}
+
+/* Gets the data back on an updated icon signal. Hopefully
+ a new fun icon. */
+static void
+new_icon_cb (DBusGProxy * proxy, GValue value, GError * error, gpointer userdata)
+{
+ /* Check for errors */
+ if (error != NULL) {
+ g_warning("Unable to get updated icon name: %s", error->message);
return;
}
- gint position = g_list_position(priv->applications, applistitem);
+ /* Grab the icon and make sure we have one */
+ const gchar * newicon = g_value_get_string(&value);
+ if (newicon == NULL) {
+ g_warning("Bad new icon :(");
+ return;
+ }
- g_signal_emit(G_OBJECT(appstore),
- signals[APPLICATION_REMOVED], 0,
- position, TRUE);
+ Application * app = (Application *) userdata;
- priv->applications = g_list_remove(priv->applications, app);
+ if (g_strcmp0(newicon, app->icon)) {
+ /* If the new icon is actually a new icon */
+ if (app->icon != NULL) g_free(app->icon);
+ app->icon = g_strdup(newicon);
+
+ if (app->status == APP_STATUS_ACTIVE) {
+ gint position = get_position(app);
+ if (position == -1) return;
+
+ g_signal_emit(G_OBJECT(app->appstore),
+ signals[APPLICATION_ICON_CHANGED], 0,
+ position, newicon, TRUE);
+ }
+ }
+
+ return;
+}
+
+/* Gets the data back on an updated aicon signal. Hopefully
+ a new fun icon. */
+static void
+new_aicon_cb (DBusGProxy * proxy, GValue value, GError * error, gpointer userdata)
+{
+ /* Check for errors */
+ if (error != NULL) {
+ g_warning("Unable to get updated icon name: %s", error->message);
+ return;
+ }
+
+ /* Grab the icon and make sure we have one */
+ const gchar * newicon = g_value_get_string(&value);
+ if (newicon == NULL) {
+ g_warning("Bad new icon :(");
+ return;
+ }
+
+ Application * app = (Application *) userdata;
+
+ if (g_strcmp0(newicon, app->aicon)) {
+ /* If the new icon is actually a new icon */
+ if (app->aicon != NULL) g_free(app->aicon);
+ app->aicon = g_strdup(newicon);
+
+ if (app->status == APP_STATUS_ATTENTION) {
+ gint position = get_position(app);
+ if (position == -1) return;
+
+ g_signal_emit(G_OBJECT(app->appstore),
+ signals[APPLICATION_ICON_CHANGED], 0,
+ position, newicon, TRUE);
+ }
+ }
+
+ return;
+}
+
+/* Called when the Notification Item signals that it
+ has a new icon. */
+static void
+new_icon (DBusGProxy * proxy, gpointer data)
+{
+ Application * app = (Application *)data;
+ if (!app->validated) return;
+
+ org_freedesktop_DBus_Properties_get_async(app->prop_proxy,
+ NOTIFICATION_ITEM_DBUS_IFACE,
+ NOTIFICATION_ITEM_PROP_ICON_NAME,
+ new_icon_cb,
+ app);
+ return;
+}
+
+/* Called when the Notification Item signals that it
+ has a new attention icon. */
+static void
+new_aicon (DBusGProxy * proxy, gpointer data)
+{
+ Application * app = (Application *)data;
+ if (!app->validated) return;
+
+ org_freedesktop_DBus_Properties_get_async(app->prop_proxy,
+ NOTIFICATION_ITEM_DBUS_IFACE,
+ NOTIFICATION_ITEM_PROP_AICON_NAME,
+ new_aicon_cb,
+ app);
+
+ return;
+}
+
+/* Called when the Notification Item signals that it
+ has a new status. */
+static void
+new_status (DBusGProxy * proxy, const gchar * status, gpointer data)
+{
+ Application * app = (Application *)data;
+ if (!app->validated) return;
+
+ apply_status(app, string_to_status(status));
- application_free(app);
return;
}
@@ -251,11 +561,18 @@ application_service_appstore_application_add (ApplicationServiceAppstore * appst
/* Build the application entry. This will be carried
along until we're sure we've got everything. */
- Application * app = g_new(Application, 1);
+ Application * app = g_new0(Application, 1);
+ app->validated = FALSE;
app->dbus_name = g_strdup(dbus_name);
app->dbus_object = g_strdup(dbus_object);
app->appstore = appstore;
+ app->status = APP_STATUS_PASSIVE;
+ app->icon = NULL;
+ app->aicon = NULL;
+ app->menu = NULL;
+ app->icon_path = NULL;
+ app->currently_free = FALSE;
/* Get the DBus proxy for the NotificationItem interface */
GError * error = NULL;
@@ -290,6 +607,34 @@ application_service_appstore_application_add (ApplicationServiceAppstore * appst
return;
}
+ /* Connect to signals */
+ dbus_g_proxy_add_signal(app->dbus_proxy,
+ NOTIFICATION_ITEM_SIG_NEW_ICON,
+ G_TYPE_INVALID);
+ dbus_g_proxy_add_signal(app->dbus_proxy,
+ NOTIFICATION_ITEM_SIG_NEW_AICON,
+ G_TYPE_INVALID);
+ dbus_g_proxy_add_signal(app->dbus_proxy,
+ NOTIFICATION_ITEM_SIG_NEW_STATUS,
+ G_TYPE_STRING,
+ G_TYPE_INVALID);
+
+ dbus_g_proxy_connect_signal(app->dbus_proxy,
+ NOTIFICATION_ITEM_SIG_NEW_ICON,
+ G_CALLBACK(new_icon),
+ app,
+ NULL);
+ dbus_g_proxy_connect_signal(app->dbus_proxy,
+ NOTIFICATION_ITEM_SIG_NEW_AICON,
+ G_CALLBACK(new_aicon),
+ app,
+ NULL);
+ dbus_g_proxy_connect_signal(app->dbus_proxy,
+ NOTIFICATION_ITEM_SIG_NEW_STATUS,
+ G_CALLBACK(new_status),
+ app,
+ NULL);
+
/* Get all the propertiees */
org_freedesktop_DBus_Properties_get_all_async(app->prop_proxy,
NOTIFICATION_ITEM_DBUS_IFACE,
@@ -301,6 +646,8 @@ application_service_appstore_application_add (ApplicationServiceAppstore * appst
return;
}
+/* Removes an application. Currently only works for the apps
+ that are shown. /TODO Need to fix that. */
void
application_service_appstore_application_remove (ApplicationServiceAppstore * appstore, const gchar * dbus_name, const gchar * dbus_object)
{
@@ -308,15 +655,81 @@ application_service_appstore_application_remove (ApplicationServiceAppstore * ap
g_return_if_fail(dbus_name != NULL && dbus_name[0] != '\0');
g_return_if_fail(dbus_object != NULL && dbus_object[0] != '\0');
+ ApplicationServiceAppstorePrivate * priv = APPLICATION_SERVICE_APPSTORE_GET_PRIVATE(appstore);
+ GList * listpntr;
+
+ for (listpntr = priv->applications; listpntr != NULL; listpntr = g_list_next(listpntr)) {
+ Application * app = (Application *)listpntr->data;
+
+ if (!g_strcmp0(app->dbus_name, dbus_name) && !g_strcmp0(app->dbus_object, dbus_object)) {
+ application_removed_cb(NULL, app);
+ break; /* NOTE: Must break as the list will become inconsistent */
+ }
+ }
return;
}
+/* Creates a basic appstore object and attaches the
+ LRU file object to it. */
+ApplicationServiceAppstore *
+application_service_appstore_new (AppLruFile * lrufile)
+{
+ g_return_val_if_fail(IS_APP_LRU_FILE(lrufile), NULL);
+ ApplicationServiceAppstore * appstore = APPLICATION_SERVICE_APPSTORE(g_object_new(APPLICATION_SERVICE_APPSTORE_TYPE, NULL));
+ ApplicationServiceAppstorePrivate * priv = APPLICATION_SERVICE_APPSTORE_GET_PRIVATE(appstore);
+ priv->lrufile = lrufile;
+ return appstore;
+}
+
/* DBus Interface */
static gboolean
-_application_service_server_get_applications (ApplicationServiceAppstore * appstore, GArray ** apps)
+_application_service_server_get_applications (ApplicationServiceAppstore * appstore, GPtrArray ** apps, GError ** error)
{
+ ApplicationServiceAppstorePrivate * priv = APPLICATION_SERVICE_APPSTORE_GET_PRIVATE(appstore);
+
+ *apps = g_ptr_array_new();
+ GList * listpntr;
+ gint position = 0;
+
+ for (listpntr = priv->applications; listpntr != NULL; listpntr = g_list_next(listpntr)) {
+ GValueArray * values = g_value_array_new(5);
+
+ GValue value = {0};
+
+ /* Icon name */
+ g_value_init(&value, G_TYPE_STRING);
+ g_value_set_string(&value, ((Application *)listpntr->data)->icon);
+ g_value_array_append(values, &value);
+ g_value_unset(&value);
+
+ /* Position */
+ g_value_init(&value, G_TYPE_INT);
+ g_value_set_int(&value, position++);
+ g_value_array_append(values, &value);
+ g_value_unset(&value);
+
+ /* DBus Address */
+ g_value_init(&value, G_TYPE_STRING);
+ g_value_set_string(&value, ((Application *)listpntr->data)->dbus_name);
+ g_value_array_append(values, &value);
+ g_value_unset(&value);
+
+ /* DBus Object */
+ g_value_init(&value, DBUS_TYPE_G_OBJECT_PATH);
+ g_value_set_static_boxed(&value, ((Application *)listpntr->data)->menu);
+ g_value_array_append(values, &value);
+ g_value_unset(&value);
+
+ /* Icon path */
+ g_value_init(&value, G_TYPE_STRING);
+ g_value_set_string(&value, ((Application *)listpntr->data)->icon_path);
+ g_value_array_append(values, &value);
+ g_value_unset(&value);
+
+ g_ptr_array_add(*apps, values);
+ }
- return FALSE;
+ return TRUE;
}
diff --git a/src/application-service-appstore.h b/src/application-service-appstore.h
index edf1a37..d2e0013 100644
--- a/src/application-service-appstore.h
+++ b/src/application-service-appstore.h
@@ -25,6 +25,7 @@ with this program. If not, see <http://www.gnu.org/licenses/>.
#include <glib.h>
#include <glib-object.h>
+#include "application-service-lru-file.h"
G_BEGIN_DECLS
@@ -40,15 +41,17 @@ typedef struct _ApplicationServiceAppstoreClass ApplicationServiceAppstoreClass;
struct _ApplicationServiceAppstoreClass {
GObjectClass parent_class;
+
+ void (*application_added) (ApplicationServiceAppstore * appstore, gchar *, gint, gchar *, gchar *, gpointer);
+ void (*application_removed) (ApplicationServiceAppstore * appstore, gint, gpointer);
+ void (*application_icon_changed)(ApplicationServiceAppstore * appstore, gint, const gchar *, gpointer);
};
struct _ApplicationServiceAppstore {
GObject parent;
-
- void (*application_added) (ApplicationServiceAppstore * appstore, gchar *, gint, gchar *, gchar *, gpointer);
- void (*application_removed) (ApplicationServiceAppstore * appstore, gint, gpointer);
};
+ApplicationServiceAppstore * application_service_appstore_new (AppLruFile * lrufile);
GType application_service_appstore_get_type (void);
void application_service_appstore_application_add (ApplicationServiceAppstore * appstore,
const gchar * dbus_name,
diff --git a/src/application-service-lru-file.c b/src/application-service-lru-file.c
new file mode 100644
index 0000000..c69a20f
--- /dev/null
+++ b/src/application-service-lru-file.c
@@ -0,0 +1,473 @@
+/*
+This object manages the database of when the entires were touched
+and loved. And writes that out to disk occationally as well.
+
+Copyright 2010 Canonical Ltd.
+
+Authors:
+ Ted Gould <ted@canonical.com>
+
+This program is free software: you can redistribute it and/or modify it
+under the terms of the GNU General Public License version 3, as published
+by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranties of
+MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+#include <gio/gio.h>
+#include <json-glib/json-glib.h>
+#include "application-service-lru-file.h"
+
+#define ENTRY_CATEGORY "category"
+#define ENTRY_FIRST_TIME "first-time"
+#define ENTRY_LAST_TIME "last-time"
+#define ENTRY_VERSION "version"
+
+#define CONFIG_DIR ("indicators" G_DIR_SEPARATOR_S "application")
+#define CONFIG_FILE "lru-file.json"
+
+typedef struct _AppLruFilePrivate AppLruFilePrivate;
+struct _AppLruFilePrivate {
+ GHashTable * apps;
+ gboolean dirty;
+ guint timer;
+ gchar * filename;
+};
+
+typedef struct _AppData AppData;
+struct _AppData {
+ gchar * category;
+ GTimeVal last_touched;
+ GTimeVal first_touched;
+};
+
+#define APP_LRU_FILE_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), APP_LRU_FILE_TYPE, AppLruFilePrivate))
+
+static void app_lru_file_class_init (AppLruFileClass *klass);
+static void app_lru_file_init (AppLruFile *self);
+static void app_lru_file_dispose (GObject *object);
+static void app_lru_file_finalize (GObject *object);
+static void app_data_free (gpointer data);
+static void get_dirty (AppLruFile * lrufile);
+static gboolean load_from_file (gpointer data);
+static void load_file_object_cb (JsonObject * obj, const gchar * key, JsonNode * value, gpointer data);
+static void clean_off_hash_cb (gpointer key, gpointer value, gpointer data);
+static void clean_off_write_end_cb (GObject * obj, GAsyncResult * res, gpointer data);
+
+G_DEFINE_TYPE (AppLruFile, app_lru_file, G_TYPE_OBJECT);
+
+/* Set up the class variable stuff */
+static void
+app_lru_file_class_init (AppLruFileClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (AppLruFilePrivate));
+
+ object_class->dispose = app_lru_file_dispose;
+ object_class->finalize = app_lru_file_finalize;
+
+ return;
+}
+
+/* Set all the data of the per-instance variables */
+static void
+app_lru_file_init (AppLruFile *self)
+{
+ AppLruFilePrivate * priv = APP_LRU_FILE_GET_PRIVATE(self);
+
+ /* Default values */
+ priv->apps = NULL;
+ priv->dirty = FALSE;
+ priv->timer = 0;
+ priv->filename = NULL;
+
+ /* Now let's build some stuff */
+ priv->apps = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, app_data_free);
+ priv->filename = g_build_filename(g_get_user_config_dir(), CONFIG_DIR, CONFIG_FILE, NULL);
+
+ /* No reason to delay other stuff for this, we'll
+ merge any values that get touched. */
+ g_idle_add(load_from_file, self);
+
+ return;
+}
+
+/* Get rid of objects and other big things */
+static void
+app_lru_file_dispose (GObject *object)
+{
+ AppLruFilePrivate * priv = APP_LRU_FILE_GET_PRIVATE(object);
+
+ if (priv->timer != 0) {
+ g_source_remove(priv->timer);
+ priv->timer = 0;
+ }
+
+ if (priv->apps != NULL) {
+ /* Cleans up the keys and entries itself */
+ g_hash_table_destroy(priv->apps);
+ priv->apps = NULL;
+ }
+
+ G_OBJECT_CLASS (app_lru_file_parent_class)->dispose (object);
+ return;
+}
+
+/* Deallocate memory */
+static void
+app_lru_file_finalize (GObject *object)
+{
+ AppLruFilePrivate * priv = APP_LRU_FILE_GET_PRIVATE(object);
+
+ if (priv->filename != NULL) {
+ g_free(priv->filename);
+ priv->filename = NULL;
+ }
+
+ G_OBJECT_CLASS (app_lru_file_parent_class)->finalize (object);
+ return;
+}
+
+/* Takes an AppData structure and free's the
+ memory from it. */
+static void
+app_data_free (gpointer data)
+{
+ AppData * appdata = (AppData *)data;
+
+ if (appdata->category != NULL) {
+ g_free(appdata->category);
+ }
+
+ g_free(appdata);
+
+ return;
+}
+
+/* Loads all of the data out of a json file */
+static gboolean
+load_from_file (gpointer data)
+{
+ AppLruFile * lrufile = (AppLruFile *)data;
+ AppLruFilePrivate * priv = APP_LRU_FILE_GET_PRIVATE(lrufile);
+
+ if (!g_file_test(priv->filename, G_FILE_TEST_EXISTS)) {
+ return FALSE;
+ }
+
+ JsonParser * parser = json_parser_new();
+
+ if (!json_parser_load_from_file(parser, priv->filename, NULL)) {
+ g_warning("Unable to parse JSON file '%s'", priv->filename);
+ g_object_unref(parser);
+ return FALSE;
+ }
+
+ JsonNode * root = json_parser_get_root(parser);
+ JsonObject * rootobj = json_node_get_object(root);
+ if (rootobj == NULL) {
+ g_warning("Malformed LRU file. The root node is not an object.");
+ g_object_unref(parser);
+ return FALSE;
+ }
+
+ json_object_foreach_member(rootobj, load_file_object_cb, lrufile);
+
+ g_object_unref(parser);
+ return FALSE;
+}
+
+/* Looks at the various things that we need on a node, makes
+ sure that we have them, and then copies them into the
+ application hash table of love. */
+static void
+load_file_object_cb (JsonObject * rootobj, const gchar * key, JsonNode * value, gpointer data)
+{
+ AppLruFile * lrufile = (AppLruFile *)data;
+ AppLruFilePrivate * priv = APP_LRU_FILE_GET_PRIVATE(lrufile);
+
+ /* We're not looking at this today. */
+ if (!g_strcmp0(key, ENTRY_VERSION)) {
+ return;
+ }
+
+ JsonObject * obj = json_node_get_object(value);
+ if (obj == NULL) {
+ g_warning("Data for node '%s' is not an object.", key);
+ return;
+ }
+
+ const gchar * obj_category = json_object_get_string_member(obj, ENTRY_CATEGORY);
+ const gchar * obj_first = json_object_get_string_member(obj, ENTRY_FIRST_TIME);
+ const gchar * obj_last = json_object_get_string_member(obj, ENTRY_LAST_TIME);
+
+ if (obj_category == NULL || obj_first == NULL || obj_last == NULL) {
+ g_warning("Node '%s' is missing data. Got: ('%s', '%s', '%s')", key, obj_category, obj_first, obj_last);
+ get_dirty(lrufile);
+ return;
+ }
+
+ /* Check to see how old this entry is. If it hasn't been
+ used in the last year, we remove the cruft. */
+ GTimeVal currenttime;
+ g_get_current_time(&currenttime);
+ GDate * currentdate = g_date_new();
+ g_date_set_time_val(currentdate, &currenttime);
+
+ GTimeVal lasttouch;
+ g_time_val_from_iso8601(obj_last, &lasttouch);
+ GDate * lastdate = g_date_new();
+ g_date_set_time_val(lastdate, &lasttouch);
+
+ gint spread = g_date_days_between(lastdate, currentdate);
+
+ g_date_free(currentdate);
+ g_date_free(lastdate);
+
+ if (spread > 365) {
+ g_debug("Removing node '%s' as it's %d days old.", key, spread);
+ get_dirty(lrufile);
+ return;
+ }
+
+ /* See if we already have one of these. It's a little bit
+ unlikely, but since we're async, we need to check */
+ gpointer datapntr = g_hash_table_lookup(priv->apps, key);
+ if (datapntr == NULL) {
+ /* Build a new node */
+ AppData * appdata = g_new0(AppData, 1);
+ appdata->category = g_strdup(obj_category);
+ g_time_val_from_iso8601(obj_first, &appdata->first_touched);
+ g_time_val_from_iso8601(obj_last, &appdata->last_touched);
+
+ g_hash_table_insert(priv->apps, g_strdup(key), appdata);
+ } else {
+ /* Merge nodes */
+ AppData * appdata = (AppData *)datapntr;
+ GTimeVal temptime;
+ g_time_val_from_iso8601(obj_first, &temptime);
+
+ if (temptime.tv_sec < appdata->first_touched.tv_sec) {
+ g_time_val_from_iso8601(obj_first, &appdata->first_touched);
+ }
+ }
+
+ return;
+}
+
+/* Write out our cache to a file so that we can unmark the dirty
+ bit and be happy. */
+static gboolean
+clean_off (gpointer data)
+{
+ AppLruFile * lrufile = (AppLruFile *)data;
+ AppLruFilePrivate * priv = APP_LRU_FILE_GET_PRIVATE(lrufile);
+ priv->timer = 0;
+
+ GError * error = NULL;
+
+ /* Check to see if our directory exists. Build it if not. */
+ gchar * dirname = g_build_filename(g_get_user_config_dir(), CONFIG_DIR, NULL);
+ if (!g_file_test(dirname, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) {
+ GFile * dirfile = g_file_new_for_path(dirname);
+ g_file_make_directory_with_parents(dirfile, NULL, NULL);
+ g_object_unref(dirfile);
+ }
+ g_free(dirname);
+
+ GFile * file = g_file_new_for_path(priv->filename);
+ GFileOutputStream * ostream = g_file_replace(file,
+ NULL, /* etag */
+ TRUE, /* backup */
+ G_FILE_CREATE_NONE, /* flags */
+ NULL, /* cancelable */
+ &error);
+ if (error != NULL) {
+ g_warning("Unable to open a file to store LRU file: %s", error->message);
+ return FALSE;
+ }
+
+ /* This is how the file will start */
+ GString * filestring = g_string_new("{\n \"" ENTRY_VERSION "\": 1");
+
+ /* Put the middle in. */
+ g_hash_table_foreach (priv->apps, clean_off_hash_cb, filestring);
+
+ /* And then tack on the end. */
+ g_string_append(filestring, "\n}\n");
+ gchar * filedata = g_string_free(filestring, FALSE);
+ g_output_stream_write_async(G_OUTPUT_STREAM(ostream),
+ filedata,
+ strlen(filedata),
+ G_PRIORITY_DEFAULT_IDLE,
+ NULL,
+ clean_off_write_end_cb,
+ filedata);
+
+ return FALSE; /* drop the timer */
+}
+
+/* Looks at every value in the applications hash table and
+ turns it into a string for writing out. */
+static void
+clean_off_hash_cb (gpointer key, gpointer value, gpointer data)
+{
+ /* Mega-cast */
+ gchar * id = (gchar *)key;
+ AppData * appdata = (AppData *)value;
+ GString * string = (GString *)data;
+
+ gchar * firsttime = g_time_val_to_iso8601(&appdata->first_touched);
+ gchar * lasttime = g_time_val_to_iso8601(&appdata->last_touched);
+
+ g_string_append_printf(string, ",\n \"%s\": { \"" ENTRY_FIRST_TIME "\": \"%s\", \"" ENTRY_LAST_TIME "\": \"%s\", \"" ENTRY_CATEGORY "\": \"%s\"}", id, firsttime, lasttime, appdata->category);
+
+ g_free(lasttime);
+ g_free(firsttime);
+
+ return;
+}
+
+/* Very much like clean_off_write_cb except that it is the
+ last actor on this Output Stream so it closes it. */
+static void
+clean_off_write_end_cb (GObject * obj, GAsyncResult * res, gpointer data)
+{
+ g_free(data);
+
+ GError * error = NULL;
+ g_output_stream_close(G_OUTPUT_STREAM(obj), NULL, &error);
+
+ if (error != NULL) {
+ g_warning("Unable to close LRU File: %s", error->message);
+ g_error_free(error);
+ }
+
+ return;
+}
+
+/* Sets the dirty bit if not already set and makes sure that
+ we have a timer to fix that at some point. */
+static void
+get_dirty (AppLruFile * lrufile)
+{
+ AppLruFilePrivate * priv = APP_LRU_FILE_GET_PRIVATE(lrufile);
+
+ priv->dirty = TRUE;
+
+ if (priv->timer == 0) {
+ priv->timer = g_timeout_add_seconds(60, clean_off, lrufile);
+ }
+
+ return;
+}
+
+/* API */
+
+/* Simple helper to create a new object */
+AppLruFile *
+app_lru_file_new (void)
+{
+ return APP_LRU_FILE(g_object_new(APP_LRU_FILE_TYPE, NULL));
+}
+
+/* This updates the timestamp for a particular
+ entry in the database. It also queues up a
+ write out of the database. But that'll happen
+ later. */
+void
+app_lru_file_touch (AppLruFile * lrufile, const gchar * id, const gchar * category)
+{
+ g_return_if_fail(IS_APP_LRU_FILE(lrufile));
+ g_return_if_fail(id != NULL && id[0] != '\0');
+ g_return_if_fail(category != NULL && category[0] != '\0');
+
+ AppData * appdata = NULL;
+ AppLruFilePrivate * priv = APP_LRU_FILE_GET_PRIVATE(lrufile);
+ gpointer data = g_hash_table_lookup(priv->apps, id);
+
+ if (data == NULL) {
+ /* Oh, we don't have one, let's build it and put it
+ into the hash table ourselves */
+ appdata = g_new0(AppData, 1);
+
+ appdata->category = g_strdup(category);
+ g_get_current_time(&(appdata->first_touched));
+ /* NOTE: last touched set below */
+
+ g_hash_table_insert(priv->apps, g_strdup(id), appdata);
+ } else {
+ /* Boring, we've got this one already */
+ appdata = (AppData *)data;
+ }
+
+ /* Touch it and mark the DB as dirty */
+ g_get_current_time(&(appdata->last_touched));
+ get_dirty(lrufile);
+ return;
+}
+
+/* Used to sort or compare different applications. */
+gint
+app_lru_file_sort (AppLruFile * lrufile, const gchar * id_a, const gchar * id_b)
+{
+ g_return_val_if_fail(IS_APP_LRU_FILE(lrufile), -1);
+
+ /* Let's first look to see if the IDs are the same, this
+ really shouldn't happen, but it'll be confusing if we
+ don't get it out of the way to start. */
+ if (g_strcmp0(id_a, id_b) == 0) {
+ return 0;
+ }
+
+ AppLruFilePrivate * priv = APP_LRU_FILE_GET_PRIVATE(lrufile);
+
+ /* Now make sure we have app data for both of these. If
+ not we'll assume that the one without is newer? */
+ gpointer data_a = g_hash_table_lookup(priv->apps, id_a);
+ if (data_a == NULL) {
+ return -1;
+ }
+
+ gpointer data_b = g_hash_table_lookup(priv->apps, id_b);
+ if (data_b == NULL) {
+ return 1;
+ }
+
+ /* Ugly casting */
+ AppData * appdata_a = (AppData *)data_a;
+ AppData * appdata_b = (AppData *)data_b;
+
+ /* Look at categories, we'll put the categories in alpha
+ order if they're not the same. */
+ gint catcompare = g_strcmp0(appdata_a->category, appdata_b->category);
+ if (catcompare != 0) {
+ return catcompare;
+ }
+
+ /* Now we're looking at the first time that these two were
+ seen in this account. Only using seconds. I mean, seriously. */
+ if (appdata_a->first_touched.tv_sec < appdata_b->first_touched.tv_sec) {
+ return -1;
+ }
+ if (appdata_b->first_touched.tv_sec < appdata_a->first_touched.tv_sec) {
+ return 1;
+ }
+
+ /* Eh, this seems roughly impossible. But if we have to choose,
+ I like A better. */
+ return 1;
+}
diff --git a/src/application-service-lru-file.h b/src/application-service-lru-file.h
new file mode 100644
index 0000000..7c92c82
--- /dev/null
+++ b/src/application-service-lru-file.h
@@ -0,0 +1,59 @@
+/*
+This object manages the database of when the entires were touched
+and loved. And writes that out to disk occationally as well.
+
+Copyright 2010 Canonical Ltd.
+
+Authors:
+ Ted Gould <ted@canonical.com>
+
+This program is free software: you can redistribute it and/or modify it
+under the terms of the GNU General Public License version 3, as published
+by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranties of
+MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __APP_LRU_FILE_H__
+#define __APP_LRU_FILE_H__
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define APP_LRU_FILE_TYPE (app_lru_file_get_type ())
+#define APP_LRU_FILE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), APP_LRU_FILE_TYPE, AppLruFile))
+#define APP_LRU_FILE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), APP_LRU_FILE_TYPE, AppLruFileClass))
+#define IS_APP_LRU_FILE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), APP_LRU_FILE_TYPE))
+#define IS_APP_LRU_FILE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), APP_LRU_FILE_TYPE))
+#define APP_LRU_FILE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), APP_LRU_FILE_TYPE, AppLruFileClass))
+
+typedef struct _AppLruFile AppLruFile;
+typedef struct _AppLruFileClass AppLruFileClass;
+
+struct _AppLruFileClass {
+ GObjectClass parent_class;
+
+};
+
+struct _AppLruFile {
+ GObject parent;
+
+};
+
+GType app_lru_file_get_type (void);
+
+AppLruFile * app_lru_file_new (void);
+void app_lru_file_touch (AppLruFile * lrufile, const gchar * id, const gchar * category);
+gint app_lru_file_sort (AppLruFile * lrufile, const gchar * id_a, const gchar * id_b);
+
+G_END_DECLS
+
+#endif
diff --git a/src/application-service-marshal.list b/src/application-service-marshal.list
index a122bf8..4ac8398 100644
--- a/src/application-service-marshal.list
+++ b/src/application-service-marshal.list
@@ -16,4 +16,5 @@
#
# You should have received a copy of the GNU General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
-VOID: STRING, INT, STRING, STRING
+VOID: STRING, INT, STRING, STRING, STRING
+VOID: INT, STRING
diff --git a/src/application-service-watcher.c b/src/application-service-watcher.c
index 984b9d4..eff249d 100644
--- a/src/application-service-watcher.c
+++ b/src/application-service-watcher.c
@@ -26,14 +26,16 @@ with this program. If not, see <http://www.gnu.org/licenses/>.
#include <dbus/dbus-glib.h>
#include <dbus/dbus-glib-lowlevel.h>
+#include <dbus/dbus-glib-bindings.h>
#include "application-service-watcher.h"
#include "dbus-shared.h"
-static gboolean _notification_watcher_server_register_service (ApplicationServiceWatcher * appwatcher, const gchar * service, DBusGMethodInvocation * method);
-static gboolean _notification_watcher_server_registered_services (ApplicationServiceWatcher * appwatcher, GArray ** apps);
+static gboolean _notification_watcher_server_register_status_notifier_item (ApplicationServiceWatcher * appwatcher, const gchar * service, DBusGMethodInvocation * method);
+static gboolean _notification_watcher_server_registered_status_notifier_items (ApplicationServiceWatcher * appwatcher, GArray ** apps);
static gboolean _notification_watcher_server_protocol_version (ApplicationServiceWatcher * appwatcher, char ** version);
static gboolean _notification_watcher_server_register_notification_host (ApplicationServiceWatcher * appwatcher, const gchar * host);
static gboolean _notification_watcher_server_is_notification_host_registered (ApplicationServiceWatcher * appwatcher, gboolean * haveHost);
+static void get_name_cb (DBusGProxy * proxy, guint status, GError * error, gpointer data);
#include "notification-watcher-server.h"
@@ -41,6 +43,7 @@ static gboolean _notification_watcher_server_is_notification_host_registered (Ap
typedef struct _ApplicationServiceWatcherPrivate ApplicationServiceWatcherPrivate;
struct _ApplicationServiceWatcherPrivate {
ApplicationServiceAppstore * appstore;
+ DBusGProxy * dbus_proxy;
};
#define APPLICATION_SERVICE_WATCHER_GET_PRIVATE(o) \
@@ -129,6 +132,23 @@ application_service_watcher_init (ApplicationServiceWatcher *self)
NOTIFICATION_WATCHER_DBUS_OBJ,
G_OBJECT(self));
+ priv->dbus_proxy = dbus_g_proxy_new_for_name_owner(session_bus,
+ DBUS_SERVICE_DBUS,
+ DBUS_PATH_DBUS,
+ DBUS_INTERFACE_DBUS,
+ &error);
+ if (error != NULL) {
+ g_error("Ah, can't get proxy to dbus: %s", error->message);
+ g_error_free(error);
+ return;
+ }
+
+ org_freedesktop_DBus_request_name_async(priv->dbus_proxy,
+ NOTIFICATION_WATCHER_DBUS_ADDR,
+ DBUS_NAME_FLAG_DO_NOT_QUEUE,
+ get_name_cb,
+ self);
+
return;
}
@@ -165,18 +185,26 @@ application_service_watcher_new (ApplicationServiceAppstore * appstore)
}
static gboolean
-_notification_watcher_server_register_service (ApplicationServiceWatcher * appwatcher, const gchar * service, DBusGMethodInvocation * method)
+_notification_watcher_server_register_status_notifier_item (ApplicationServiceWatcher * appwatcher, const gchar * service, DBusGMethodInvocation * method)
{
ApplicationServiceWatcherPrivate * priv = APPLICATION_SERVICE_WATCHER_GET_PRIVATE(appwatcher);
- application_service_appstore_application_add(priv->appstore, dbus_g_method_get_sender(method), service);
+ if (service[0] == '/') {
+ application_service_appstore_application_add(priv->appstore,
+ dbus_g_method_get_sender(method),
+ service);
+ } else {
+ application_service_appstore_application_add(priv->appstore,
+ service,
+ NOTIFICATION_ITEM_DEFAULT_OBJ);
+ }
dbus_g_method_return(method, G_TYPE_NONE);
return TRUE;
}
static gboolean
-_notification_watcher_server_registered_services (ApplicationServiceWatcher * appwatcher, GArray ** apps)
+_notification_watcher_server_registered_status_notifier_items (ApplicationServiceWatcher * appwatcher, GArray ** apps)
{
return FALSE;
@@ -203,3 +231,21 @@ _notification_watcher_server_is_notification_host_registered (ApplicationService
return TRUE;
}
+/* Function to handle the return of the get name. There isn't a whole
+ lot that can be done, but we're atleast going to tell people. */
+static void
+get_name_cb (DBusGProxy * proxy, guint status, GError * error, gpointer data)
+{
+ if (error != NULL) {
+ g_warning("Unable to get watcher name '%s' because: %s", NOTIFICATION_WATCHER_DBUS_ADDR, error->message);
+ return;
+ }
+
+ if (status != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER &&
+ status != DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER) {
+ g_warning("Unable to get watcher name '%s'", NOTIFICATION_WATCHER_DBUS_ADDR);
+ return;
+ }
+
+ return;
+}
diff --git a/src/application-service.c b/src/application-service.c
index 45295a1..ffd042e 100644
--- a/src/application-service.c
+++ b/src/application-service.c
@@ -25,6 +25,7 @@ with this program. If not, see <http://www.gnu.org/licenses/>.
#include "notification-item-client.h"
#include "application-service-appstore.h"
#include "application-service-watcher.h"
+#include "application-service-lru-file.h"
#include "dbus-shared.h"
/* The base main loop */
@@ -35,6 +36,8 @@ static ApplicationServiceAppstore * appstore = NULL;
static ApplicationServiceWatcher * watcher = NULL;
/* The service management interface */
static IndicatorService * service = NULL;
+/* The LRU file interface */
+static AppLruFile * lrufile = NULL;
/* Recieves the disonnection signal from the service
object and closes the mainloop. */
@@ -57,10 +60,13 @@ main (int argc, char ** argv)
/* Bring us up as a basic indicator service */
service = indicator_service_new(INDICATOR_APPLICATION_DBUS_ADDR);
- g_signal_connect(G_OBJECT(service), "disconnected", G_CALLBACK(service_disconnected), NULL);
+ g_signal_connect(G_OBJECT(service), INDICATOR_SERVICE_SIGNAL_SHUTDOWN, G_CALLBACK(service_disconnected), NULL);
+
+ /* Start up the LRU file reading */
+ lrufile = app_lru_file_new();
/* Building our app store */
- appstore = APPLICATION_SERVICE_APPSTORE(g_object_new(APPLICATION_SERVICE_APPSTORE_TYPE, NULL));
+ appstore = application_service_appstore_new(lrufile);
/* Adding a watcher for the Apps coming up */
watcher = application_service_watcher_new(appstore);
@@ -73,6 +79,7 @@ main (int argc, char ** argv)
g_object_unref(G_OBJECT(watcher));
g_object_unref(G_OBJECT(appstore));
g_object_unref(G_OBJECT(service));
+ g_object_unref(G_OBJECT(lrufile));
return 0;
}
diff --git a/src/application-service.xml b/src/application-service.xml
index fdd25bb..0b2e959 100644
--- a/src/application-service.xml
+++ b/src/application-service.xml
@@ -26,7 +26,7 @@ with this program. If not, see <http://www.gnu.org/licenses/>.
<!-- Methods -->
<method name="GetApplications">
- <arg type="a(siso)" name="applications" direction="out" />
+ <arg type="a(sisos)" name="applications" direction="out" />
</method>
<!-- Signals -->
@@ -35,10 +35,15 @@ with this program. If not, see <http://www.gnu.org/licenses/>.
<arg type="i" name="position" direction="out" />
<arg type="s" name="dbusaddress" direction="out" />
<arg type="o" name="dbusobject" direction="out" />
+ <arg type="s" name="iconpath" direction="out" />
</signal>
<signal name="ApplicationRemoved">
<arg type="i" name="position" direction="out" />
</signal>
+ <signal name="ApplicationIconChanged">
+ <arg type="i" name="position" direction="out" />
+ <arg type="s" name="icon_name" direction="out" />
+ </signal>
</interface>
</node>
diff --git a/src/dbus-properties-client.h b/src/dbus-properties-client.h
new file mode 100644
index 0000000..dc9f299
--- /dev/null
+++ b/src/dbus-properties-client.h
@@ -0,0 +1,139 @@
+/* Generated by dbus-binding-tool; do not edit! */
+
+#include <glib.h>
+#include <dbus/dbus-glib.h>
+
+G_BEGIN_DECLS
+
+#ifndef _DBUS_GLIB_ASYNC_DATA_FREE
+#define _DBUS_GLIB_ASYNC_DATA_FREE
+static
+#ifdef G_HAVE_INLINE
+inline
+#endif
+void
+_dbus_glib_async_data_free (gpointer stuff)
+{
+ g_slice_free (DBusGAsyncData, stuff);
+}
+#endif
+
+#ifndef DBUS_GLIB_CLIENT_WRAPPERS_org_freedesktop_DBus_Properties
+#define DBUS_GLIB_CLIENT_WRAPPERS_org_freedesktop_DBus_Properties
+
+static
+#ifdef G_HAVE_INLINE
+inline
+#endif
+gboolean
+org_freedesktop_DBus_Properties_get (DBusGProxy *proxy, const char * IN_Interface_Name, const char * IN_Property_Name, GValue* OUT_Value, GError **error)
+
+{
+ return dbus_g_proxy_call (proxy, "Get", error, G_TYPE_STRING, IN_Interface_Name, G_TYPE_STRING, IN_Property_Name, G_TYPE_INVALID, G_TYPE_VALUE, OUT_Value, G_TYPE_INVALID);
+}
+
+typedef void (*org_freedesktop_DBus_Properties_get_reply) (DBusGProxy *proxy, GValue OUT_Value, GError *error, gpointer userdata);
+
+static void
+org_freedesktop_DBus_Properties_get_async_callback (DBusGProxy *proxy, DBusGProxyCall *call, void *user_data)
+{
+ DBusGAsyncData *data = (DBusGAsyncData*) user_data;
+ GError *error = NULL;
+ GValue OUT_Value = {0};
+ dbus_g_proxy_end_call (proxy, call, &error, G_TYPE_VALUE, &OUT_Value, G_TYPE_INVALID);
+ (*(org_freedesktop_DBus_Properties_get_reply)data->cb) (proxy, OUT_Value, error, data->userdata);
+ return;
+}
+
+static
+#ifdef G_HAVE_INLINE
+inline
+#endif
+DBusGProxyCall*
+org_freedesktop_DBus_Properties_get_async (DBusGProxy *proxy, const char * IN_Interface_Name, const char * IN_Property_Name, org_freedesktop_DBus_Properties_get_reply callback, gpointer userdata)
+
+{
+ DBusGAsyncData *stuff;
+ stuff = g_slice_new (DBusGAsyncData);
+ stuff->cb = G_CALLBACK (callback);
+ stuff->userdata = userdata;
+ return dbus_g_proxy_begin_call (proxy, "Get", org_freedesktop_DBus_Properties_get_async_callback, stuff, _dbus_glib_async_data_free, G_TYPE_STRING, IN_Interface_Name, G_TYPE_STRING, IN_Property_Name, G_TYPE_INVALID);
+}
+static
+#ifdef G_HAVE_INLINE
+inline
+#endif
+gboolean
+org_freedesktop_DBus_Properties_set (DBusGProxy *proxy, const char * IN_Interface_Name, const char * IN_Property_Name, const GValue* IN_Value, GError **error)
+
+{
+ return dbus_g_proxy_call (proxy, "Set", error, G_TYPE_STRING, IN_Interface_Name, G_TYPE_STRING, IN_Property_Name, G_TYPE_VALUE, IN_Value, G_TYPE_INVALID, G_TYPE_INVALID);
+}
+
+typedef void (*org_freedesktop_DBus_Properties_set_reply) (DBusGProxy *proxy, GError *error, gpointer userdata);
+
+static void
+org_freedesktop_DBus_Properties_set_async_callback (DBusGProxy *proxy, DBusGProxyCall *call, void *user_data)
+{
+ DBusGAsyncData *data = (DBusGAsyncData*) user_data;
+ GError *error = NULL;
+ dbus_g_proxy_end_call (proxy, call, &error, G_TYPE_INVALID);
+ (*(org_freedesktop_DBus_Properties_set_reply)data->cb) (proxy, error, data->userdata);
+ return;
+}
+
+static
+#ifdef G_HAVE_INLINE
+inline
+#endif
+DBusGProxyCall*
+org_freedesktop_DBus_Properties_set_async (DBusGProxy *proxy, const char * IN_Interface_Name, const char * IN_Property_Name, const GValue* IN_Value, org_freedesktop_DBus_Properties_set_reply callback, gpointer userdata)
+
+{
+ DBusGAsyncData *stuff;
+ stuff = g_slice_new (DBusGAsyncData);
+ stuff->cb = G_CALLBACK (callback);
+ stuff->userdata = userdata;
+ return dbus_g_proxy_begin_call (proxy, "Set", org_freedesktop_DBus_Properties_set_async_callback, stuff, _dbus_glib_async_data_free, G_TYPE_STRING, IN_Interface_Name, G_TYPE_STRING, IN_Property_Name, G_TYPE_VALUE, IN_Value, G_TYPE_INVALID);
+}
+static
+#ifdef G_HAVE_INLINE
+inline
+#endif
+gboolean
+org_freedesktop_DBus_Properties_get_all (DBusGProxy *proxy, const char * IN_Interface_Name, GHashTable** OUT_Properties, GError **error)
+
+{
+ return dbus_g_proxy_call (proxy, "GetAll", error, G_TYPE_STRING, IN_Interface_Name, G_TYPE_INVALID, dbus_g_type_get_map ("GHashTable", G_TYPE_STRING, G_TYPE_VALUE), OUT_Properties, G_TYPE_INVALID);
+}
+
+typedef void (*org_freedesktop_DBus_Properties_get_all_reply) (DBusGProxy *proxy, GHashTable *OUT_Properties, GError *error, gpointer userdata);
+
+static void
+org_freedesktop_DBus_Properties_get_all_async_callback (DBusGProxy *proxy, DBusGProxyCall *call, void *user_data)
+{
+ DBusGAsyncData *data = (DBusGAsyncData*) user_data;
+ GError *error = NULL;
+ GHashTable* OUT_Properties;
+ dbus_g_proxy_end_call (proxy, call, &error, dbus_g_type_get_map ("GHashTable", G_TYPE_STRING, G_TYPE_VALUE), &OUT_Properties, G_TYPE_INVALID);
+ (*(org_freedesktop_DBus_Properties_get_all_reply)data->cb) (proxy, OUT_Properties, error, data->userdata);
+ return;
+}
+
+static
+#ifdef G_HAVE_INLINE
+inline
+#endif
+DBusGProxyCall*
+org_freedesktop_DBus_Properties_get_all_async (DBusGProxy *proxy, const char * IN_Interface_Name, org_freedesktop_DBus_Properties_get_all_reply callback, gpointer userdata)
+
+{
+ DBusGAsyncData *stuff;
+ stuff = g_slice_new (DBusGAsyncData);
+ stuff->cb = G_CALLBACK (callback);
+ stuff->userdata = userdata;
+ return dbus_g_proxy_begin_call (proxy, "GetAll", org_freedesktop_DBus_Properties_get_all_async_callback, stuff, _dbus_glib_async_data_free, G_TYPE_STRING, IN_Interface_Name, G_TYPE_INVALID);
+}
+#endif /* defined DBUS_GLIB_CLIENT_WRAPPERS_org_freedesktop_DBus_Properties */
+
+G_END_DECLS
diff --git a/src/dbus-shared.h b/src/dbus-shared.h
index f888e02..b8dc016 100644
--- a/src/dbus-shared.h
+++ b/src/dbus-shared.h
@@ -24,8 +24,10 @@ with this program. If not, see <http://www.gnu.org/licenses/>.
#define INDICATOR_APPLICATION_DBUS_OBJ "/org/ayatana/indicator/application/service"
#define INDICATOR_APPLICATION_DBUS_IFACE "org.ayatana.indicator.application.service"
-#define NOTIFICATION_WATCHER_DBUS_OBJ "/org/ayatana/indicator/application/NotificationWatcher"
-#define NOTIFICATION_WATCHER_DBUS_IFACE "org.ayatana.indicator.application.NotificationWatcher"
+#define NOTIFICATION_WATCHER_DBUS_ADDR "org.freedesktop.StatusNotifierWatcher"
+#define NOTIFICATION_WATCHER_DBUS_OBJ "/StatusNotifierWatcher"
+#define NOTIFICATION_WATCHER_DBUS_IFACE "org.freedesktop.StatusNotifierWatcher"
-#define NOTIFICATION_ITEM_DBUS_IFACE "org.ayatana.indicator.application.NotificationItem"
+#define NOTIFICATION_ITEM_DBUS_IFACE "org.freedesktop.StatusNotifierItem"
+#define NOTIFICATION_ITEM_DEFAULT_OBJ "/StatusNotifierItem"
diff --git a/src/indicator-application.c b/src/indicator-application.c
index f3566e4..6c053a9 100644
--- a/src/indicator-application.c
+++ b/src/indicator-application.c
@@ -41,6 +41,8 @@ with this program. If not, see <http://www.gnu.org/licenses/>.
#include "application-service-client.h"
#include "application-service-marshal.h"
+#define PANEL_ICON_SUFFIX "panel"
+
#define INDICATOR_APPLICATION_TYPE (indicator_application_get_type ())
#define INDICATOR_APPLICATION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), INDICATOR_APPLICATION_TYPE, IndicatorApplication))
#define INDICATOR_APPLICATION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), INDICATOR_APPLICATION_TYPE, IndicatorApplicationClass))
@@ -75,11 +77,13 @@ struct _IndicatorApplicationPrivate {
DBusGConnection * bus;
DBusGProxy * service_proxy;
GList * applications;
+ GHashTable * theme_dirs;
};
typedef struct _ApplicationEntry ApplicationEntry;
struct _ApplicationEntry {
IndicatorObjectEntry entry;
+ gchar * icon_path;
};
#define INDICATOR_APPLICATION_GET_PRIVATE(o) \
@@ -90,10 +94,15 @@ static void indicator_application_init (IndicatorApplication *self);
static void indicator_application_dispose (GObject *object);
static void indicator_application_finalize (GObject *object);
static GList * get_entries (IndicatorObject * io);
+static guint get_location (IndicatorObject * io, IndicatorObjectEntry * entry);
static void connected (IndicatorServiceManager * sm, gboolean connected, IndicatorApplication * application);
-static void application_added (DBusGProxy * proxy, const gchar * iconname, gint position, const gchar * dbusaddress, const gchar * dbusobject, IndicatorApplication * application);
+static void application_added (DBusGProxy * proxy, const gchar * iconname, gint position, const gchar * dbusaddress, const gchar * dbusobject, const gchar * icon_path, IndicatorApplication * application);
static void application_removed (DBusGProxy * proxy, gint position , IndicatorApplication * application);
+static void application_icon_changed (DBusGProxy * proxy, gint position, const gchar * iconname, IndicatorApplication * application);
static void get_applications (DBusGProxy *proxy, GPtrArray *OUT_applications, GError *error, gpointer userdata);
+static void get_applications_helper (gpointer data, gpointer user_data);
+static void theme_dir_unref(IndicatorApplication * ia, const gchar * dir);
+static void theme_dir_ref(IndicatorApplication * ia, const gchar * dir);
G_DEFINE_TYPE (IndicatorApplication, indicator_application, INDICATOR_OBJECT_TYPE);
@@ -110,13 +119,20 @@ indicator_application_class_init (IndicatorApplicationClass *klass)
IndicatorObjectClass * io_class = INDICATOR_OBJECT_CLASS(klass);
io_class->get_entries = get_entries;
+ io_class->get_location = get_location;
- dbus_g_object_register_marshaller(_application_service_marshal_VOID__STRING_INT_STRING_STRING,
+ dbus_g_object_register_marshaller(_application_service_marshal_VOID__STRING_INT_STRING_STRING_STRING,
G_TYPE_NONE,
G_TYPE_STRING,
G_TYPE_INT,
G_TYPE_STRING,
G_TYPE_STRING,
+ G_TYPE_STRING,
+ G_TYPE_INVALID);
+ dbus_g_object_register_marshaller(_application_service_marshal_VOID__INT_STRING,
+ G_TYPE_NONE,
+ G_TYPE_INT,
+ G_TYPE_STRING,
G_TYPE_INVALID);
return;
@@ -130,12 +146,15 @@ indicator_application_init (IndicatorApplication *self)
/* These are built in the connection phase */
priv->bus = NULL;
priv->service_proxy = NULL;
+ priv->theme_dirs = NULL;
priv->sm = indicator_service_manager_new(INDICATOR_APPLICATION_DBUS_ADDR);
g_signal_connect(G_OBJECT(priv->sm), INDICATOR_SERVICE_MANAGER_SIGNAL_CONNECTION_CHANGE, G_CALLBACK(connected), self);
priv->applications = NULL;
+ priv->theme_dirs = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+
return;
}
@@ -165,6 +184,15 @@ indicator_application_dispose (GObject *object)
priv->service_proxy = NULL;
}
+ if (priv->theme_dirs != NULL) {
+ while (g_hash_table_size(priv->theme_dirs)) {
+ GList * keys = g_hash_table_get_keys(priv->theme_dirs);
+ theme_dir_unref(INDICATOR_APPLICATION(object), (gchar *)keys->data);
+ }
+ g_hash_table_destroy(priv->theme_dirs);
+ priv->theme_dirs = NULL;
+ }
+
G_OBJECT_CLASS (indicator_application_parent_class)->dispose (object);
return;
}
@@ -211,11 +239,17 @@ connected (IndicatorServiceManager * sm, gboolean connected, IndicatorApplicatio
G_TYPE_INT,
G_TYPE_STRING,
G_TYPE_STRING,
+ G_TYPE_STRING,
G_TYPE_INVALID);
dbus_g_proxy_add_signal(priv->service_proxy,
"ApplicationRemoved",
G_TYPE_INT,
G_TYPE_INVALID);
+ dbus_g_proxy_add_signal(priv->service_proxy,
+ "ApplicationIconChanged",
+ G_TYPE_INT,
+ G_TYPE_STRING,
+ G_TYPE_INVALID);
/* Connect to them */
g_debug("Connect to them.");
@@ -229,6 +263,11 @@ connected (IndicatorServiceManager * sm, gboolean connected, IndicatorApplicatio
G_CALLBACK(application_removed),
application,
NULL /* Disconnection Signal */);
+ dbus_g_proxy_connect_signal(priv->service_proxy,
+ "ApplicationIconChanged",
+ G_CALLBACK(application_icon_changed),
+ application,
+ NULL /* Disconnection Signal */);
/* Query it for existing applications */
g_debug("Request current apps");
@@ -263,20 +302,49 @@ get_entries (IndicatorObject * io)
return retval;
}
+/* Finds the location of a specific entry */
+static guint
+get_location (IndicatorObject * io, IndicatorObjectEntry * entry)
+{
+ g_return_val_if_fail(IS_INDICATOR_APPLICATION(io), 0);
+ IndicatorApplicationPrivate * priv = INDICATOR_APPLICATION_GET_PRIVATE(io);
+ return g_list_index(priv->applications, entry);
+}
+
/* Here we respond to new applications by building up the
ApplicationEntry and signaling the indicator host that
we've got a new indicator. */
static void
-application_added (DBusGProxy * proxy, const gchar * iconname, gint position, const gchar * dbusaddress, const gchar * dbusobject, IndicatorApplication * application)
+application_added (DBusGProxy * proxy, const gchar * iconname, gint position, const gchar * dbusaddress, const gchar * dbusobject, const gchar * icon_path, IndicatorApplication * application)
{
g_debug("Building new application entry: %s with icon: %s", dbusaddress, iconname);
IndicatorApplicationPrivate * priv = INDICATOR_APPLICATION_GET_PRIVATE(application);
ApplicationEntry * app = g_new(ApplicationEntry, 1);
- app->entry.image = GTK_IMAGE(gtk_image_new_from_icon_name(iconname, GTK_ICON_SIZE_MENU));
+ app->icon_path = NULL;
+ if (icon_path != NULL && icon_path[0] != '\0') {
+ app->icon_path = g_strdup(icon_path);
+ theme_dir_ref(application, icon_path);
+ }
+
+ /* We make a long name using the suffix, and if that
+ icon is available we want to use it. Otherwise we'll
+ just use the name we were given. */
+ gchar * longname = g_strdup_printf("%s-%s", iconname, PANEL_ICON_SUFFIX);
+ if (gtk_icon_theme_has_icon(gtk_icon_theme_get_default(), longname)) {
+ app->entry.image = GTK_IMAGE(gtk_image_new_from_icon_name(longname, GTK_ICON_SIZE_MENU));
+ } else {
+ app->entry.image = GTK_IMAGE(gtk_image_new_from_icon_name(iconname, GTK_ICON_SIZE_MENU));
+ }
+ g_free(longname);
+
app->entry.label = NULL;
app->entry.menu = GTK_MENU(dbusmenu_gtkmenu_new((gchar *)dbusaddress, (gchar *)dbusobject));
+ /* Keep copies of these for ourself, just in case. */
+ g_object_ref(app->entry.image);
+ g_object_ref(app->entry.menu);
+
gtk_widget_show(GTK_WIDGET(app->entry.image));
priv->applications = g_list_insert(priv->applications, app, position);
@@ -302,6 +370,10 @@ application_removed (DBusGProxy * proxy, gint position, IndicatorApplication * a
priv->applications = g_list_remove(priv->applications, app);
g_signal_emit(G_OBJECT(application), INDICATOR_OBJECT_SIGNAL_ENTRY_REMOVED_ID, 0, &(app->entry), TRUE);
+ if (app->icon_path != NULL) {
+ theme_dir_unref(application, app->icon_path);
+ g_free(app->icon_path);
+ }
if (app->entry.image != NULL) {
g_object_unref(G_OBJECT(app->entry.image));
}
@@ -317,11 +389,147 @@ application_removed (DBusGProxy * proxy, gint position, IndicatorApplication * a
return;
}
+/* The callback for the signal that the icon for an application
+ has changed. */
+static void
+application_icon_changed (DBusGProxy * proxy, gint position, const gchar * iconname, IndicatorApplication * application)
+{
+ IndicatorApplicationPrivate * priv = INDICATOR_APPLICATION_GET_PRIVATE(application);
+ ApplicationEntry * app = (ApplicationEntry *)g_list_nth_data(priv->applications, position);
+
+ if (app == NULL) {
+ g_warning("Unable to find application at position: %d", position);
+ return;
+ }
+
+ /* We make a long name using the suffix, and if that
+ icon is available we want to use it. Otherwise we'll
+ just use the name we were given. */
+ gchar * longname = g_strdup_printf("%s-%s", iconname, PANEL_ICON_SUFFIX);
+ if (gtk_icon_theme_has_icon(gtk_icon_theme_get_default(), longname)) {
+ gtk_image_set_from_icon_name(app->entry.image, longname, GTK_ICON_SIZE_MENU);
+ } else {
+ gtk_image_set_from_icon_name(app->entry.image, iconname, GTK_ICON_SIZE_MENU);
+ }
+ g_free(longname);
+
+ return;
+}
+
/* This repsonds to the list of applications that the service
has and calls application_added on each one of them. */
static void
get_applications (DBusGProxy *proxy, GPtrArray *OUT_applications, GError *error, gpointer userdata)
{
+ if (error != NULL) {
+ g_warning("Unable to get application list: %s", error->message);
+ return;
+ }
+ g_ptr_array_foreach(OUT_applications, get_applications_helper, userdata);
return;
}
+
+/* A little helper that takes apart the DBus structure and calls
+ application_added on every entry in the list. */
+static void
+get_applications_helper (gpointer data, gpointer user_data)
+{
+ GValueArray * array = (GValueArray *)data;
+
+ g_return_if_fail(array->n_values == 5);
+
+ const gchar * icon_name = g_value_get_string(g_value_array_get_nth(array, 0));
+ gint position = g_value_get_int(g_value_array_get_nth(array, 1));
+ const gchar * dbus_address = g_value_get_string(g_value_array_get_nth(array, 2));
+ const gchar * dbus_object = g_value_get_boxed(g_value_array_get_nth(array, 3));
+ const gchar * icon_path = g_value_get_string(g_value_array_get_nth(array, 4));
+
+ return application_added(NULL, icon_name, position, dbus_address, dbus_object, icon_path, user_data);
+}
+
+/* Refs a theme directory, and it may add it to the search
+ path */
+static void
+theme_dir_unref(IndicatorApplication * ia, const gchar * dir)
+{
+ IndicatorApplicationPrivate * priv = INDICATOR_APPLICATION_GET_PRIVATE(ia);
+
+ /* Grab the count for this dir */
+ int count = GPOINTER_TO_INT(g_hash_table_lookup(priv->theme_dirs, dir));
+
+ /* Is this a simple deprecation, if so, we can just lower the
+ number and move on. */
+ if (count > 1) {
+ count--;
+ g_hash_table_insert(priv->theme_dirs, g_strdup(dir), GINT_TO_POINTER(count));
+ return;
+ }
+
+ /* Try to remove it from the hash table, this makes sure
+ that it existed */
+ if (!g_hash_table_remove(priv->theme_dirs, dir)) {
+ g_warning("Unref'd a directory that wasn't in the theme dir hash table.");
+ return;
+ }
+
+ GtkIconTheme * icon_theme = gtk_icon_theme_get_default();
+ gchar ** paths;
+ gint path_count;
+
+ gtk_icon_theme_get_search_path(icon_theme, &paths, &path_count);
+
+ gint i;
+ gboolean found = FALSE;
+ for (i = 0; i < path_count; i++) {
+ if (found) {
+ /* If we've already found the right entry */
+ paths[i - 1] = paths[i];
+ } else {
+ /* We're still looking, is this the one? */
+ if (!g_strcmp0(paths[i], dir)) {
+ found = TRUE;
+ /* We're freeing this here as it won't be captured by the
+ g_strfreev() below as it's out of the array. */
+ g_free(paths[i]);
+ }
+ }
+ }
+
+ /* If we found one we need to reset the path to
+ accomidate the changes */
+ if (found) {
+ paths[path_count - 1] = NULL; /* Clear the last one */
+ gtk_icon_theme_set_search_path(icon_theme, (const gchar **)paths, path_count - 1);
+ }
+
+ g_strfreev(paths);
+
+ return;
+}
+
+/* Unrefs a theme directory. This may involve removing it from
+ the search path. */
+static void
+theme_dir_ref(IndicatorApplication * ia, const gchar * dir)
+{
+ IndicatorApplicationPrivate * priv = INDICATOR_APPLICATION_GET_PRIVATE(ia);
+
+ int count = 0;
+ if ((count = GPOINTER_TO_INT(g_hash_table_lookup(priv->theme_dirs, dir))) != 0) {
+ /* It exists so what we need to do is increase the ref
+ count of this dir. */
+ count++;
+ } else {
+ /* It doesn't exist, so we need to add it to the table
+ and to the search path. */
+ gtk_icon_theme_append_search_path(gtk_icon_theme_get_default(), dir);
+ g_debug("\tAppending search path: %s", dir);
+ count = 1;
+ }
+
+ g_hash_table_insert(priv->theme_dirs, g_strdup(dir), GINT_TO_POINTER(count));
+
+ return;
+}
+
diff --git a/src/libappindicator/app-indicator-enum-types.c.in b/src/libappindicator/app-indicator-enum-types.gen.c.in
index 449f3fc..449f3fc 100644
--- a/src/libappindicator/app-indicator-enum-types.c.in
+++ b/src/libappindicator/app-indicator-enum-types.gen.c.in
diff --git a/src/libappindicator/app-indicator.c b/src/libappindicator/app-indicator.c
index bb68cb2..b0f721e 100644
--- a/src/libappindicator/app-indicator.c
+++ b/src/libappindicator/app-indicator.c
@@ -63,12 +63,17 @@ struct _AppIndicatorPrivate {
AppIndicatorStatus status;
gchar *icon_name;
gchar *attention_icon_name;
- DbusmenuServer *menuservice;
- GtkWidget *menu;
+ gchar * icon_path;
+ DbusmenuServer *menuservice;
+ GtkWidget *menu;
+
+ GtkStatusIcon * status_icon;
+ gint fallback_timer;
/* Fun stuff */
DBusGProxy *watcher_proxy;
DBusGConnection *connection;
+ DBusGProxy * dbus_proxy;
};
/* Signals Stuff */
@@ -91,6 +96,7 @@ enum {
PROP_STATUS,
PROP_ICON_NAME,
PROP_ATTENTION_ICON_NAME,
+ PROP_ICON_THEME_PATH,
PROP_MENU,
PROP_CONNECTED
};
@@ -101,6 +107,7 @@ enum {
#define PROP_STATUS_S "status"
#define PROP_ICON_NAME_S "icon-name"
#define PROP_ATTENTION_ICON_NAME_S "attention-icon-name"
+#define PROP_ICON_THEME_PATH_S "icon-theme-path"
#define PROP_MENU_S "menu"
#define PROP_CONNECTED_S "connected"
@@ -108,6 +115,13 @@ enum {
#define APP_INDICATOR_GET_PRIVATE(o) \
(G_TYPE_INSTANCE_GET_PRIVATE ((o), APP_INDICATOR_TYPE, AppIndicatorPrivate))
+/* Default Paths */
+#define DEFAULT_ITEM_PATH "/org/ayatana/NotificationItem"
+#define DEFAULT_MENU_PATH "/org/ayatana/NotificationItem/Menu"
+
+/* More constants */
+#define DEFAULT_FALLBACK_TIMER 100 /* in milliseconds */
+
/* Boiler plate */
static void app_indicator_class_init (AppIndicatorClass *klass);
static void app_indicator_init (AppIndicator *self);
@@ -119,6 +133,14 @@ static void app_indicator_get_property (GObject * object, guint prop_id, GValue
/* Other stuff */
static void check_connect (AppIndicator * self);
static void register_service_cb (DBusGProxy * proxy, GError * error, gpointer data);
+static void start_fallback_timer (AppIndicator * self, gboolean disable_timeout);
+static gboolean fallback_timer_expire (gpointer data);
+static GtkStatusIcon * fallback (AppIndicator * self);
+static void status_icon_status_wrapper (AppIndicator * self, const gchar * status, gpointer data);
+static void status_icon_changes (AppIndicator * self, gpointer data);
+static void status_icon_activate (GtkStatusIcon * icon, gpointer data);
+static void unfallback (AppIndicator * self, GtkStatusIcon * status_icon);
+static void watcher_proxy_destroyed (GObject * object, gpointer data);
/* GObject type */
G_DEFINE_TYPE (AppIndicator, app_indicator, G_TYPE_OBJECT);
@@ -138,6 +160,10 @@ app_indicator_class_init (AppIndicatorClass *klass)
object_class->set_property = app_indicator_set_property;
object_class->get_property = app_indicator_get_property;
+ /* Our own funcs */
+ klass->fallback = fallback;
+ klass->unfallback = unfallback;
+
/* Properties */
g_object_class_install_property (object_class,
PROP_ID,
@@ -179,6 +205,14 @@ app_indicator_class_init (AppIndicatorClass *klass)
NULL,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property(object_class,
+ PROP_ICON_THEME_PATH,
+ g_param_spec_string (PROP_ICON_THEME_PATH_S,
+ "An additional path for custom icons.",
+ "An additional place to look for icon names that may be installed by the application.",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY));
+
g_object_class_install_property(object_class,
PROP_MENU,
g_param_spec_string (PROP_MENU_S,
@@ -277,11 +311,16 @@ app_indicator_init (AppIndicator *self)
priv->status = APP_INDICATOR_STATUS_PASSIVE;
priv->icon_name = NULL;
priv->attention_icon_name = NULL;
+ priv->icon_path = NULL;
priv->menu = NULL;
priv->menuservice = NULL;
priv->watcher_proxy = NULL;
priv->connection = NULL;
+ priv->dbus_proxy = NULL;
+
+ priv->status_icon = NULL;
+ priv->fallback_timer = 0;
/* Put the object on DBus */
GError * error = NULL;
@@ -293,7 +332,7 @@ app_indicator_init (AppIndicator *self)
}
dbus_g_connection_register_g_object(priv->connection,
- "/need/a/path",
+ DEFAULT_ITEM_PATH,
G_OBJECT(self));
self->priv = priv;
@@ -306,20 +345,43 @@ app_indicator_init (AppIndicator *self)
static void
app_indicator_dispose (GObject *object)
{
- AppIndicator *self = APP_INDICATOR (object);
+ AppIndicator *self = APP_INDICATOR (object);
AppIndicatorPrivate *priv = self->priv;
if (priv->status != APP_INDICATOR_STATUS_PASSIVE) {
app_indicator_set_status(self, APP_INDICATOR_STATUS_PASSIVE);
}
+ if (priv->status_icon != NULL) {
+ AppIndicatorClass * class = APP_INDICATOR_GET_CLASS(object);
+ if (class->unfallback != NULL) {
+ class->unfallback(self, priv->status_icon);
+ }
+ priv->status_icon = NULL;
+ }
+
+ if (priv->fallback_timer != 0) {
+ g_source_remove(priv->fallback_timer);
+ priv->fallback_timer = 0;
+ }
+
if (priv->menu != NULL) {
g_object_unref(G_OBJECT(priv->menu));
priv->menu = NULL;
}
+ if (priv->menuservice != NULL) {
+ g_object_unref (priv->menuservice);
+ }
+
+ if (priv->dbus_proxy != NULL) {
+ g_object_unref(G_OBJECT(priv->dbus_proxy));
+ priv->dbus_proxy = NULL;
+ }
+
if (priv->watcher_proxy != NULL) {
dbus_g_connection_flush(priv->connection);
+ g_signal_handlers_disconnect_by_func(G_OBJECT(priv->watcher_proxy), watcher_proxy_destroyed, self);
g_object_unref(G_OBJECT(priv->watcher_proxy));
priv->watcher_proxy = NULL;
}
@@ -355,6 +417,11 @@ app_indicator_finalize (GObject *object)
priv->attention_icon_name = NULL;
}
+ if (priv->icon_path != NULL) {
+ g_free(priv->icon_path);
+ priv->icon_path = NULL;
+ }
+
G_OBJECT_CLASS (app_indicator_parent_class)->finalize (object);
return;
}
@@ -423,6 +490,13 @@ app_indicator_set_property (GObject * object, guint prop_id, const GValue * valu
g_value_get_string (value));
break;
+ case PROP_ICON_THEME_PATH:
+ if (priv->icon_path != NULL) {
+ g_free(priv->icon_path);
+ }
+ priv->icon_path = g_value_dup_string(value);
+ break;
+
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@@ -462,6 +536,10 @@ app_indicator_get_property (GObject * object, guint prop_id, GValue * value, GPa
g_value_set_string (value, priv->attention_icon_name);
break;
+ case PROP_ICON_THEME_PATH:
+ g_value_set_string (value, priv->icon_path);
+ break;
+
case PROP_MENU:
if (G_VALUE_HOLDS_STRING(value)) {
if (priv->menuservice != NULL) {
@@ -490,7 +568,7 @@ app_indicator_get_property (GObject * object, guint prop_id, GValue * value, GPa
static void
check_connect (AppIndicator *self)
{
- AppIndicatorPrivate *priv = self->priv;
+ AppIndicatorPrivate *priv = self->priv;
/* We're alreadying connecting or trying to connect. */
if (priv->watcher_proxy != NULL) return;
@@ -502,35 +580,67 @@ check_connect (AppIndicator *self)
GError * error = NULL;
priv->watcher_proxy = dbus_g_proxy_new_for_name_owner(priv->connection,
- INDICATOR_APPLICATION_DBUS_ADDR,
+ NOTIFICATION_WATCHER_DBUS_ADDR,
NOTIFICATION_WATCHER_DBUS_OBJ,
NOTIFICATION_WATCHER_DBUS_IFACE,
&error);
if (error != NULL) {
- g_warning("Unable to create Ayatana Watcher proxy! %s", error->message);
- /* TODO: This is where we should start looking at fallbacks */
+ /* Unable to get proxy, but we're handling that now so
+ it's not a warning anymore. */
g_error_free(error);
+ start_fallback_timer(self, FALSE);
return;
}
- org_ayatana_indicator_application_NotificationWatcher_register_service_async(priv->watcher_proxy, "/need/a/path", register_service_cb, self);
+ g_signal_connect(G_OBJECT(priv->watcher_proxy), "destroy", G_CALLBACK(watcher_proxy_destroyed), self);
+ org_freedesktop_StatusNotifierWatcher_register_status_notifier_item_async(priv->watcher_proxy, DEFAULT_ITEM_PATH, register_service_cb, self);
+
+ return;
+}
+
+/* A function that gets called when the watcher dies. Like
+ dies dies. Not our friend anymore. */
+static void
+watcher_proxy_destroyed (GObject * object, gpointer data)
+{
+ AppIndicator * self = APP_INDICATOR(data);
+ g_return_if_fail(self != NULL);
+ self->priv->watcher_proxy = NULL;
+ start_fallback_timer(self, FALSE);
return;
}
+/* Responce from the DBus command to register a service
+ with a NotificationWatcher. */
static void
register_service_cb (DBusGProxy * proxy, GError * error, gpointer data)
{
- AppIndicatorPrivate * priv = APP_INDICATOR_GET_PRIVATE(data);
+ g_return_if_fail(IS_APP_INDICATOR(data));
+ AppIndicatorPrivate * priv = APP_INDICATOR(data)->priv;
if (error != NULL) {
+ /* They didn't respond, ewww. Not sure what they could
+ be doing */
g_warning("Unable to connect to the Notification Watcher: %s", error->message);
g_object_unref(G_OBJECT(priv->watcher_proxy));
priv->watcher_proxy = NULL;
+ start_fallback_timer(APP_INDICATOR(data), TRUE);
+ }
+
+ if (priv->status_icon) {
+ AppIndicatorClass * class = APP_INDICATOR_GET_CLASS(data);
+ if (class->unfallback != NULL) {
+ class->unfallback(APP_INDICATOR(data), priv->status_icon);
+ priv->status_icon = NULL;
+ }
}
+
return;
}
+/* A helper function to get the nick out of a given
+ category enum value. */
static const gchar *
category_from_enum (AppIndicatorCategory category)
{
@@ -540,6 +650,191 @@ category_from_enum (AppIndicatorCategory category)
return value->value_nick;
}
+/* Watching the dbus owner change events to see if someone
+ we care about pops up! */
+static void
+dbus_owner_change (DBusGProxy * proxy, const gchar * name, const gchar * prev, const gchar * new, gpointer data)
+{
+ if (new == NULL || new[0] == '\0') {
+ /* We only care about folks coming on the bus. Exit quickly otherwise. */
+ return;
+ }
+
+ if (g_strcmp0(name, NOTIFICATION_WATCHER_DBUS_ADDR)) {
+ /* We only care about this address, reject all others. */
+ return;
+ }
+
+ /* Woot, there's a new notification watcher in town. */
+
+ AppIndicatorPrivate * priv = APP_INDICATOR_GET_PRIVATE(data);
+
+ if (priv->fallback_timer != 0) {
+ /* Stop a timer */
+ g_source_remove(priv->fallback_timer);
+
+ /* Stop listening to bus events */
+ g_object_unref(G_OBJECT(priv->dbus_proxy));
+ priv->dbus_proxy = NULL;
+ }
+
+ /* Let's start from the very beginning */
+ check_connect(APP_INDICATOR(data));
+
+ return;
+}
+
+/* A function that will start the fallback timer if it's not
+ already started. It sets up the DBus watcher to see if
+ there is a change. Also, provides an override mode for cases
+ where it's unlikely that a timer will help anything. */
+static void
+start_fallback_timer (AppIndicator * self, gboolean disable_timeout)
+{
+ g_return_if_fail(IS_APP_INDICATOR(self));
+ AppIndicatorPrivate * priv = APP_INDICATOR(self)->priv;
+
+ if (priv->fallback_timer != 0) {
+ /* The timer is set, let's just be happy with the one
+ we've already got running */
+ return;
+ }
+
+ if (priv->dbus_proxy == NULL) {
+ priv->dbus_proxy = dbus_g_proxy_new_for_name(priv->connection,
+ DBUS_SERVICE_DBUS,
+ DBUS_PATH_DBUS,
+ DBUS_INTERFACE_DBUS);
+ dbus_g_proxy_add_signal(priv->dbus_proxy, "NameOwnerChanged",
+ G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
+ G_TYPE_INVALID);
+ dbus_g_proxy_connect_signal(priv->dbus_proxy, "NameOwnerChanged",
+ G_CALLBACK(dbus_owner_change), self, NULL);
+ }
+
+ if (disable_timeout) {
+ fallback_timer_expire(self);
+ } else {
+ priv->fallback_timer = g_timeout_add(DEFAULT_FALLBACK_TIMER, fallback_timer_expire, self);
+ }
+
+ return;
+}
+
+/* A function that gets executed when we want to change the
+ state of the fallback. */
+static gboolean
+fallback_timer_expire (gpointer data)
+{
+ g_return_val_if_fail(IS_APP_INDICATOR(data), FALSE);
+
+ AppIndicatorPrivate * priv = APP_INDICATOR(data)->priv;
+ AppIndicatorClass * class = APP_INDICATOR_GET_CLASS(data);
+
+ if (priv->status_icon == NULL) {
+ if (class->fallback != NULL) {
+ priv->status_icon = class->fallback(APP_INDICATOR(data));
+ }
+ } else {
+ if (class->unfallback != NULL) {
+ class->unfallback(APP_INDICATOR(data), priv->status_icon);
+ priv->status_icon = NULL;
+ } else {
+ g_warning("No 'unfallback' function but the 'fallback' function returned a non-NULL result.");
+ }
+ }
+
+ priv->fallback_timer = 0;
+ return FALSE;
+}
+
+/* Creates a StatusIcon that can be used when the application
+ indicator area isn't available. */
+static GtkStatusIcon *
+fallback (AppIndicator * self)
+{
+ GtkStatusIcon * icon = gtk_status_icon_new();
+
+ gtk_status_icon_set_title(icon, app_indicator_get_id(self));
+
+ g_signal_connect(G_OBJECT(self), APP_INDICATOR_SIGNAL_NEW_STATUS,
+ G_CALLBACK(status_icon_status_wrapper), icon);
+ g_signal_connect(G_OBJECT(self), APP_INDICATOR_SIGNAL_NEW_ICON,
+ G_CALLBACK(status_icon_changes), icon);
+ g_signal_connect(G_OBJECT(self), APP_INDICATOR_SIGNAL_NEW_ATTENTION_ICON,
+ G_CALLBACK(status_icon_changes), icon);
+
+ status_icon_changes(self, icon);
+
+ g_signal_connect(G_OBJECT(icon), "activate", G_CALLBACK(status_icon_activate), self);
+
+ return icon;
+}
+
+/* A wrapper as the status update prototype is a little
+ bit different, but we want to handle it the same. */
+static void
+status_icon_status_wrapper (AppIndicator * self, const gchar * status, gpointer data)
+{
+ return status_icon_changes(self, data);
+}
+
+/* This tracks changes to either the status or the icons
+ that are associated with the app indicator */
+static void
+status_icon_changes (AppIndicator * self, gpointer data)
+{
+ GtkStatusIcon * icon = GTK_STATUS_ICON(data);
+
+ switch (app_indicator_get_status(self)) {
+ case APP_INDICATOR_STATUS_PASSIVE:
+ gtk_status_icon_set_visible(icon, FALSE);
+ gtk_status_icon_set_from_icon_name(icon, app_indicator_get_icon(self));
+ break;
+ case APP_INDICATOR_STATUS_ACTIVE:
+ gtk_status_icon_set_from_icon_name(icon, app_indicator_get_icon(self));
+ gtk_status_icon_set_visible(icon, TRUE);
+ break;
+ case APP_INDICATOR_STATUS_ATTENTION:
+ gtk_status_icon_set_from_icon_name(icon, app_indicator_get_attention_icon(self));
+ gtk_status_icon_set_visible(icon, TRUE);
+ break;
+ };
+
+ return;
+}
+
+/* Handles the activate action by the status icon by showing
+ the menu in a popup. */
+static void
+status_icon_activate (GtkStatusIcon * icon, gpointer data)
+{
+ GtkMenu * menu = app_indicator_get_menu(APP_INDICATOR(data));
+ if (menu == NULL)
+ return;
+
+ gtk_menu_popup(menu,
+ NULL, /* Parent Menu */
+ NULL, /* Parent item */
+ gtk_status_icon_position_menu,
+ icon,
+ 1, /* Button */
+ gtk_get_current_event_time());
+
+ return;
+}
+
+/* Removes the status icon as the application indicator area
+ is now up and running again. */
+static void
+unfallback (AppIndicator * self, GtkStatusIcon * status_icon)
+{
+ g_signal_handlers_disconnect_by_func(G_OBJECT(self), status_icon_status_wrapper, status_icon);
+ g_signal_handlers_disconnect_by_func(G_OBJECT(self), status_icon_changes, status_icon);
+ g_object_unref(G_OBJECT(status_icon));
+ return;
+}
+
/* ************************* */
/* Public Functions */
@@ -564,15 +859,45 @@ app_indicator_new (const gchar *id,
AppIndicatorCategory category)
{
AppIndicator *indicator = g_object_new (APP_INDICATOR_TYPE,
- "id", id,
- "category", category_from_enum (category),
- "icon-name", icon_name,
+ PROP_ID_S, id,
+ PROP_CATEGORY_S, category_from_enum (category),
+ PROP_ICON_NAME_S, icon_name,
NULL);
return indicator;
}
/**
+ app_indicator_new_with_path:
+ @id: The unique id of the indicator to create.
+ @icon_name: The icon name for this indicator
+ @category: The category of indicator.
+ @icon_path: A custom path for finding icons.
+
+ Creates a new #AppIndicator setting the properties:
+ #AppIndicator::id with @id, #AppIndicator::category
+ with @category, #AppIndicator::icon-name with
+ @icon_name and #AppIndicator::icon-theme-path with @icon_path.
+
+ Return value: A pointer to a new #AppIndicator object.
+ */
+AppIndicator *
+app_indicator_new_with_path (const gchar *id,
+ const gchar *icon_name,
+ AppIndicatorCategory category,
+ const gchar *icon_path)
+{
+ AppIndicator *indicator = g_object_new (APP_INDICATOR_TYPE,
+ PROP_ID_S, id,
+ PROP_CATEGORY_S, category_from_enum (category),
+ PROP_ICON_NAME_S, icon_name,
+ PROP_ICON_THEME_PATH_S, icon_path,
+ NULL);
+
+ return indicator;
+}
+
+/**
app_indicator_get_type:
Generates or returns the unique #GType for #AppIndicator.
@@ -640,14 +965,14 @@ app_indicator_set_icon (AppIndicator *self, const gchar *icon_name)
g_return_if_fail (IS_APP_INDICATOR (self));
g_return_if_fail (icon_name != NULL);
- if (g_strcmp0 (self->priv->attention_icon_name, icon_name) != 0)
+ if (g_strcmp0 (self->priv->icon_name, icon_name) != 0)
{
- if (self->priv->attention_icon_name)
- g_free (self->priv->attention_icon_name);
+ if (self->priv->icon_name)
+ g_free (self->priv->icon_name);
- self->priv->attention_icon_name = g_strdup (icon_name);
+ self->priv->icon_name = g_strdup (icon_name);
- g_signal_emit (self, signals[NEW_ATTENTION_ICON], 0, TRUE);
+ g_signal_emit (self, signals[NEW_ICON], 0, TRUE);
}
}
@@ -660,6 +985,14 @@ activate_menuitem (DbusmenuMenuitem *mi, gpointer user_data)
}
static void
+widget_toggled (GtkWidget *widget, DbusmenuMenuitem *mi)
+{
+ dbusmenu_menuitem_property_set (mi,
+ DBUSMENU_MENUITEM_PROP_TOGGLE_CHECKED,
+ gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget)) ? DBUSMENU_MENUITEM_TOGGLE_STATE_CHECKED : DBUSMENU_MENUITEM_TOGGLE_STATE_UNCHECKED);
+}
+
+static void
menuitem_iterate (GtkWidget *widget,
gpointer data)
{
@@ -674,6 +1007,85 @@ menuitem_iterate (GtkWidget *widget,
}
static void
+update_icon_name (DbusmenuMenuitem *menuitem,
+ GtkImage *image)
+{
+ if (gtk_image_get_storage_type (image) != GTK_IMAGE_ICON_NAME)
+ return;
+
+ dbusmenu_menuitem_property_set (menuitem,
+ DBUSMENU_MENUITEM_PROP_ICON,
+ image->data.name.icon_name);
+}
+
+/* return value specifies whether the label is set or not */
+static gboolean
+update_stock_item (DbusmenuMenuitem *menuitem,
+ GtkImage *image)
+{
+ GtkStockItem stock;
+
+ if (gtk_image_get_storage_type (image) != GTK_IMAGE_STOCK)
+ return FALSE;
+
+ gtk_stock_lookup (image->data.stock.stock_id, &stock);
+
+ dbusmenu_menuitem_property_set (menuitem,
+ DBUSMENU_MENUITEM_PROP_ICON,
+ image->data.stock.stock_id);
+
+ if (stock.label != NULL)
+ {
+ dbusmenu_menuitem_property_set (menuitem,
+ DBUSMENU_MENUITEM_PROP_LABEL,
+ stock.label);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+image_notify_cb (GtkWidget *widget,
+ GParamSpec *pspec,
+ gpointer data)
+{
+ DbusmenuMenuitem *child = (DbusmenuMenuitem *)data;
+ GtkImage *image = GTK_IMAGE (widget);
+
+ if (pspec->name == g_intern_static_string ("stock"))
+ {
+ update_stock_item (child, image);
+ }
+ else if (pspec->name == g_intern_static_string ("icon-name"))
+ {
+ update_icon_name (child, image);
+ }
+}
+
+static void
+widget_notify_cb (GtkWidget *widget,
+ GParamSpec *pspec,
+ gpointer data)
+{
+ DbusmenuMenuitem *child = (DbusmenuMenuitem *)data;
+
+ if (pspec->name == g_intern_static_string ("sensitive"))
+ {
+ dbusmenu_menuitem_property_set_bool (child,
+ DBUSMENU_MENUITEM_PROP_SENSITIVE,
+ GTK_WIDGET_IS_SENSITIVE (widget));
+ }
+ else if (pspec->name == g_intern_static_string ("label"))
+ {
+ dbusmenu_menuitem_property_set (child,
+ DBUSMENU_MENUITEM_PROP_LABEL,
+ gtk_menu_item_get_label (GTK_MENU_ITEM (widget)));
+ }
+}
+
+static void
container_iterate (GtkWidget *widget,
gpointer data)
{
@@ -692,29 +1104,52 @@ container_iterate (GtkWidget *widget,
}
else
{
- label = gtk_menu_item_get_label (GTK_MENU_ITEM (widget));
-
- if (GTK_IS_IMAGE_MENU_ITEM (widget))
+ if (GTK_IS_CHECK_MENU_ITEM (widget))
{
- GtkWidget *image = gtk_image_menu_item_get_image (GTK_IMAGE_MENU_ITEM (widget));
+ GtkCheckMenuItem *check;
- if (gtk_image_get_storage_type (GTK_IMAGE (image)) == GTK_IMAGE_STOCK)
- {
- GtkStockItem stock;
+ check = GTK_CHECK_MENU_ITEM (widget);
+ label = gtk_menu_item_get_label (GTK_MENU_ITEM (widget));
- gtk_stock_lookup (GTK_IMAGE (image)->data.stock.stock_id, &stock);
+ dbusmenu_menuitem_property_set (child,
+ DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE,
+ GTK_IS_RADIO_MENU_ITEM (widget) ? DBUSMENU_MENUITEM_TOGGLE_RADIO : DBUSMENU_MENUITEM_TOGGLE_CHECK);
- dbusmenu_menuitem_property_set (child,
- DBUSMENU_MENUITEM_PROP_ICON,
- GTK_IMAGE (image)->data.stock.stock_id);
+ dbusmenu_menuitem_property_set (child,
+ DBUSMENU_MENUITEM_PROP_LABEL,
+ label);
- if (stock.label != NULL)
- {
- dbusmenu_menuitem_property_set (child,
- DBUSMENU_MENUITEM_PROP_LABEL,
- stock.label);
- label_set = TRUE;
- }
+ label_set = TRUE;
+
+ dbusmenu_menuitem_property_set (child,
+ DBUSMENU_MENUITEM_PROP_TOGGLE_CHECKED,
+ gtk_check_menu_item_get_active (check) ? DBUSMENU_MENUITEM_TOGGLE_STATE_CHECKED : DBUSMENU_MENUITEM_TOGGLE_STATE_UNCHECKED);
+
+ g_signal_connect (widget,
+ "toggled",
+ G_CALLBACK (widget_toggled),
+ child);
+ }
+ else if (GTK_IS_IMAGE_MENU_ITEM (widget))
+ {
+ GtkWidget *image;
+ GtkImageType image_type;
+
+ image = gtk_image_menu_item_get_image (GTK_IMAGE_MENU_ITEM (widget));
+ image_type = gtk_image_get_storage_type (GTK_IMAGE (image));
+
+ g_signal_connect (image,
+ "notify",
+ G_CALLBACK (image_notify_cb),
+ child);
+
+ if (image_type == GTK_IMAGE_STOCK)
+ {
+ label_set = update_stock_item (child, GTK_IMAGE (image));
+ }
+ else if (image_type == GTK_IMAGE_ICON_NAME)
+ {
+ update_icon_name (child, GTK_IMAGE (image));
}
}
}
@@ -736,6 +1171,9 @@ container_iterate (GtkWidget *widget,
}
}
+ g_signal_connect (widget, "notify",
+ G_CALLBACK (widget_notify_cb), child);
+
g_signal_connect (G_OBJECT (child),
DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED,
G_CALLBACK (activate_menuitem), widget);
@@ -756,7 +1194,7 @@ setup_dbusmenu (AppIndicator *self)
root);
if (priv->menuservice == NULL) {
- priv->menuservice = dbusmenu_server_new ("/need/a/menu/path");
+ priv->menuservice = dbusmenu_server_new (DEFAULT_MENU_PATH);
}
dbusmenu_server_set_root (priv->menuservice, root);
@@ -875,3 +1313,23 @@ app_indicator_get_attention_icon (AppIndicator *self)
return self->priv->attention_icon_name;
}
+
+/**
+ app_indicator_get_menu:
+ @self: The #AppIndicator object to use
+
+ Gets the menu being used for this application indicator.
+
+ Return value: A menu object or #NULL if one hasn't been set.
+*/
+GtkMenu *
+app_indicator_get_menu (AppIndicator *self)
+{
+ AppIndicatorPrivate *priv;
+
+ g_return_val_if_fail (IS_APP_INDICATOR (self), NULL);
+
+ priv = self->priv;
+
+ return GTK_MENU(priv->menu);
+}
diff --git a/src/libappindicator/app-indicator.h b/src/libappindicator/app-indicator.h
index e966a49..03656ce 100644
--- a/src/libappindicator/app-indicator.h
+++ b/src/libappindicator/app-indicator.h
@@ -128,8 +128,8 @@ typedef enum { /*< prefix=APP_INDICATOR_CATEGORY >*/
These are the states that the indicator can be on in
the user's panel. The indicator by default starts
- in the state @APP_INDICATOR_STATUS_OFF and can be
- shown by setting it to @APP_INDICATOR_STATUS_ON.
+ in the state @APP_INDICATOR_STATUS_PASSIVE and can be
+ shown by setting it to @APP_INDICATOR_STATUS_ACTIVE.
*/
typedef enum { /*< prefix=APP_INDICATOR_STATUS >*/
APP_INDICATOR_STATUS_PASSIVE,
@@ -148,10 +148,12 @@ typedef struct _AppIndicatorPrivate AppIndicatorPrivate;
@new_attention_icon: Slot for #AppIndicator::new-attention-icon.
@new_status: Slot for #AppIndicator::new-status.
@connection_changed: Slot for #AppIndicator::connection-changed.
+ @fallback: Function that gets called to make a #GtkStatusIcon when
+ there is no Application Indicator area available.
+ @unfallback: The function that gets called if an Application
+ Indicator area appears after the fallback has been created.
@app_indicator_reserved_1: Reserved for future use.
@app_indicator_reserved_2: Reserved for future use.
- @app_indicator_reserved_3: Reserved for future use.
- @app_indicator_reserved_4: Reserved for future use.
The signals and external functions that make up the #AppIndicator
class object.
@@ -174,11 +176,14 @@ struct _AppIndicatorClass {
gboolean connected,
gpointer user_data);
+ /* Overridable Functions */
+ GtkStatusIcon * (*fallback) (AppIndicator * indicator);
+ void (*unfallback) (AppIndicator * indicator,
+ GtkStatusIcon * status_icon);
+
/* Reserved */
void (*app_indicator_reserved_1)(void);
void (*app_indicator_reserved_2)(void);
- void (*app_indicator_reserved_3)(void);
- void (*app_indicator_reserved_4)(void);
};
/**
@@ -202,6 +207,10 @@ GType app_indicator_get_type (void) G_GNUC_C
AppIndicator *app_indicator_new (const gchar *id,
const gchar *icon_name,
AppIndicatorCategory category);
+AppIndicator *app_indicator_new_with_path (const gchar *id,
+ const gchar *icon_name,
+ AppIndicatorCategory category,
+ const gchar *icon_path);
/* Set properties */
void app_indicator_set_status (AppIndicator *self,
@@ -219,6 +228,7 @@ AppIndicatorCategory app_indicator_get_category (AppIndicator *
AppIndicatorStatus app_indicator_get_status (AppIndicator *self);
const gchar * app_indicator_get_icon (AppIndicator *self);
const gchar * app_indicator_get_attention_icon (AppIndicator *self);
+GtkMenu * app_indicator_get_menu (AppIndicator *self);
G_END_DECLS
diff --git a/src/notification-item.xml b/src/notification-item.xml
index c28cc54..bcc3a4a 100644
--- a/src/notification-item.xml
+++ b/src/notification-item.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
-<node name="/">
- <interface name="org.ayatana.indicator.application.NotificationItem">
+<node name="/StatusNotifierItem">
+ <interface name="org.freedesktop.StatusNotifierItem">
<!-- Properties -->
<property name="Id" type="s" access="read" />
@@ -8,6 +8,9 @@
<property name="Status" type="s" access="read" />
<property name="IconName" type="s" access="read" />
<property name="AttentionIconName" type="s" access="read" />
+ <!-- An additional path to add to the theme search path
+ to find the icons specified above. -->
+ <property name="IconThemePath" type="s" access="read" />
<property name="Menu" type="o" access="read" />
<!-- Methods -->
diff --git a/src/notification-watcher.xml b/src/notification-watcher.xml
index 2ef54a0..22ada5f 100644
--- a/src/notification-watcher.xml
+++ b/src/notification-watcher.xml
@@ -1,16 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
-<node name="/">
- <interface name="org.ayatana.indicator.application.NotificationWatcher">
+<node name="/StatusNotifierWatcher">
+ <interface name="org.freedesktop.StatusNotifierWatcher">
<!-- Properties -->
<!-- None currently -->
<!-- Methods -->
- <method name="RegisterService">
+ <method name="RegisterStatusNotifierItem">
<annotation name="org.freedesktop.DBus.GLib.Async" value="true" />
<arg type="s" name="service" direction="in" />
</method>
- <method name="RegisteredServices">
+ <method name="RegisteredStatusNotifierItems">
<arg type="as" name="services" direction="out" />
</method>
<method name="ProtocolVersion">
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 845b41c..c94ebdd 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -3,6 +3,8 @@ check_PROGRAMS = \
test-libappindicator \
test-libappindicator-dbus-client \
test-libappindicator-dbus-server \
+ test-libappindicator-fallback-watcher \
+ test-libappindicator-fallback-item \
test-simple-app
TESTS =
@@ -59,6 +61,41 @@ test_libappindicator_dbus_server_LDADD = \
$(top_builddir)/src/libappindicator.la
#########################################
+## test-libappindicator-fallback
+#########################################
+
+test_libappindicator_fallback_watcher_SOURCES = \
+ test-libappindicator-fallback-watcher.c
+
+test_libappindicator_fallback_watcher_CFLAGS = \
+ $(INDICATOR_CFLAGS) \
+ -Wall -Werror \
+ -I$(top_srcdir)/src
+
+test_libappindicator_fallback_watcher_LDADD = \
+ $(INDICATOR_LIBS) \
+ $(top_builddir)/src/libappindicator.la
+
+test_libappindicator_fallback_item_SOURCES = \
+ test-libappindicator-fallback-item.c
+
+test_libappindicator_fallback_item_CFLAGS = \
+ $(INDICATOR_CFLAGS) \
+ -Wall -Werror \
+ -I$(top_srcdir)/src
+
+test_libappindicator_fallback_item_LDADD = \
+ $(INDICATOR_LIBS) \
+ $(top_builddir)/src/libappindicator.la
+
+test-libappindicator-fallback: test-libappindicator-fallback-watcher test-libappindicator-fallback-item Makefile.am
+ @echo "#!/bin/sh" > $@
+ @echo $(DBUS_RUNNER) --task ./test-libappindicator-fallback-watcher --task-name Watcher --ignore-return --task ./test-libappindicator-fallback-item --task-name Item >> $@
+ @chmod +x $@
+
+TESTS += test-libappindicator-fallback
+
+#########################################
## Actual tests
#########################################
diff --git a/tests/test-defines.h b/tests/test-defines.h
index 3b75c87..2baf728 100644
--- a/tests/test-defines.h
+++ b/tests/test-defines.h
@@ -23,8 +23,8 @@ with this program. If not, see <http://www.gnu.org/licenses/>.
#define TEST_ICON_NAME "my-icon-name"
#define TEST_ATTENTION_ICON_NAME "my-attention-icon-name"
#define TEST_STATE APP_INDICATOR_STATUS_ACTIVE
-#define TEST_STATE_S "active"
+#define TEST_STATE_S "Active"
#define TEST_CATEGORY APP_INDICATOR_CATEGORY_APPLICATION_STATUS
-#define TEST_CATEGORY_S "application-status"
+#define TEST_CATEGORY_S "ApplicationStatus"
#define TEST_OBJECT "/an/object/path/to/use"
diff --git a/tests/test-libappindicator-dbus-client.c b/tests/test-libappindicator-dbus-client.c
index 8598f9a..5a7107f 100644
--- a/tests/test-libappindicator-dbus-client.c
+++ b/tests/test-libappindicator-dbus-client.c
@@ -200,7 +200,7 @@ main (gint argc, gchar * argv[])
DBusGProxy * props = dbus_g_proxy_new_for_name_owner(session_bus,
":1.0",
- "/need/a/path",
+ "/org/ayatana/NotificationItem",
DBUS_INTERFACE_PROPERTIES,
&error);
if (error != NULL) {
diff --git a/tests/test-libappindicator-fallback-item.c b/tests/test-libappindicator-fallback-item.c
new file mode 100644
index 0000000..2c6e044
--- /dev/null
+++ b/tests/test-libappindicator-fallback-item.c
@@ -0,0 +1,130 @@
+#include <glib.h>
+#include <glib-object.h>
+#include <libappindicator/app-indicator.h>
+
+#define TEST_LIBAPPINDICATOR_FALLBACK_ITEM_TYPE (test_libappindicator_fallback_item_get_type ())
+#define TEST_LIBAPPINDICATOR_FALLBACK_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TEST_LIBAPPINDICATOR_FALLBACK_ITEM_TYPE, TestLibappindicatorFallbackItem))
+#define TEST_LIBAPPINDICATOR_FALLBACK_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TEST_LIBAPPINDICATOR_FALLBACK_ITEM_TYPE, TestLibappindicatorFallbackItemClass))
+#define IS_TEST_LIBAPPINDICATOR_FALLBACK_ITEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TEST_LIBAPPINDICATOR_FALLBACK_ITEM_TYPE))
+#define IS_TEST_LIBAPPINDICATOR_FALLBACK_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TEST_LIBAPPINDICATOR_FALLBACK_ITEM_TYPE))
+#define TEST_LIBAPPINDICATOR_FALLBACK_ITEM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TEST_LIBAPPINDICATOR_FALLBACK_ITEM_TYPE, TestLibappindicatorFallbackItemClass))
+
+typedef struct _TestLibappindicatorFallbackItem TestLibappindicatorFallbackItem;
+typedef struct _TestLibappindicatorFallbackItemClass TestLibappindicatorFallbackItemClass;
+
+struct _TestLibappindicatorFallbackItemClass {
+ AppIndicatorClass parent_class;
+
+};
+
+struct _TestLibappindicatorFallbackItem {
+ AppIndicator parent;
+
+};
+
+GType test_libappindicator_fallback_item_get_type (void);
+
+#define TEST_LIBAPPINDICATOR_FALLBACK_ITEM_GET_PRIVATE(o) \
+(G_TYPE_INSTANCE_GET_PRIVATE ((o), TEST_LIBAPPINDICATOR_FALLBACK_ITEM_TYPE, TestLibappindicatorFallbackItemPrivate))
+
+static void test_libappindicator_fallback_item_class_init (TestLibappindicatorFallbackItemClass *klass);
+static void test_libappindicator_fallback_item_init (TestLibappindicatorFallbackItem *self);
+static GtkStatusIcon * fallback (AppIndicator * indicator);
+static void unfallback (AppIndicator * indicator, GtkStatusIcon * status_icon);
+
+G_DEFINE_TYPE (TestLibappindicatorFallbackItem, test_libappindicator_fallback_item, APP_INDICATOR_TYPE);
+
+static void
+test_libappindicator_fallback_item_class_init (TestLibappindicatorFallbackItemClass *klass)
+{
+ AppIndicatorClass * aiclass = APP_INDICATOR_CLASS(klass);
+
+ aiclass->fallback = fallback;
+ aiclass->unfallback = unfallback;
+}
+
+static void
+test_libappindicator_fallback_item_init (TestLibappindicatorFallbackItem *self)
+{
+}
+
+GMainLoop * mainloop = NULL;
+gboolean passed = FALSE;
+
+enum {
+ STATE_INIT,
+ STATE_FALLBACK,
+ STATE_UNFALLBACK,
+ STATE_REFALLBACK,
+ STATE_REUNFALLBACK
+};
+
+gint state = STATE_INIT;
+
+static GtkStatusIcon *
+fallback (AppIndicator * indicator)
+{
+ g_debug("Fallback");
+ if (state == STATE_INIT) {
+ state = STATE_FALLBACK;
+ } else if (state == STATE_UNFALLBACK) {
+ state = STATE_REFALLBACK;
+ } else {
+ g_debug("Error, fallback in state: %d", state);
+ passed = FALSE;
+ }
+ return (GtkStatusIcon *)5;
+}
+
+static void
+unfallback (AppIndicator * indicator, GtkStatusIcon * status_icon)
+{
+ g_debug("Unfallback");
+ if (state == STATE_FALLBACK) {
+ state = STATE_UNFALLBACK;
+ } else if (state == STATE_REFALLBACK) {
+ state = STATE_REUNFALLBACK;
+ passed = TRUE;
+ g_main_loop_quit(mainloop);
+ } else {
+ g_debug("Error, unfallback in state: %d", state);
+ passed = FALSE;
+ }
+ return;
+}
+
+gboolean
+kill_func (gpointer data)
+{
+ g_debug("Kill Function");
+ g_main_loop_quit(mainloop);
+ return FALSE;
+}
+
+int
+main (int argc, char ** argv)
+{
+ gtk_init(&argc, &argv);
+
+ TestLibappindicatorFallbackItem * item = g_object_new(TEST_LIBAPPINDICATOR_FALLBACK_ITEM_TYPE,
+ "id", "test-id",
+ "category", "Other",
+ "icon-name", "bob",
+ NULL);
+
+ GtkWidget * menu = gtk_menu_new();
+ app_indicator_set_menu(APP_INDICATOR(item), GTK_MENU(menu));
+
+ g_timeout_add_seconds(1, kill_func, NULL);
+
+ mainloop = g_main_loop_new(NULL, FALSE);
+ g_main_loop_run(mainloop);
+
+ g_object_unref(G_OBJECT(item));
+
+ if (passed) {
+ return 0;
+ } else {
+ return 1;
+ }
+}
diff --git a/tests/test-libappindicator-fallback-watcher.c b/tests/test-libappindicator-fallback-watcher.c
new file mode 100644
index 0000000..90c7db8
--- /dev/null
+++ b/tests/test-libappindicator-fallback-watcher.c
@@ -0,0 +1,97 @@
+/*
+This puts the NotificationWatcher on the bus, kinda. Enough to
+trick the Item into unfalling back.
+
+Copyright 2010 Canonical Ltd.
+
+Authors:
+ Ted Gould <ted@canonical.com>
+
+This program is free software: you can redistribute it and/or modify it
+under the terms of the GNU General Public License version 3, as published
+by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranties of
+MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <glib.h>
+#include <dbus/dbus-glib.h>
+#include <dbus/dbus-glib-bindings.h>
+#include <dbus/dbus-glib-lowlevel.h>
+
+#include "../src/dbus-shared.h"
+
+static GMainLoop * mainloop = NULL;
+
+static DBusHandlerResult
+dbus_filter (DBusConnection * connection, DBusMessage * message, void * user_data)
+{
+ if (dbus_message_is_method_call(message, NOTIFICATION_WATCHER_DBUS_ADDR, "RegisterStatusNotifierItem")) {
+ DBusMessage * reply = dbus_message_new_method_return(message);
+ dbus_connection_send(connection, reply, NULL);
+ dbus_message_unref(reply);
+ return DBUS_HANDLER_RESULT_HANDLED;
+ }
+
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+gboolean
+kill_func (gpointer userdata)
+{
+ g_main_loop_quit(mainloop);
+ return FALSE;
+}
+
+int
+main (int argv, char ** argc)
+{
+ g_type_init();
+
+ g_debug("Waiting to init.");
+
+ /* Wait 1/4 a second, which should trigger the fallback */
+ g_usleep(250000);
+
+ g_debug("Initing");
+
+ GError * error = NULL;
+ DBusGConnection * session_bus = dbus_g_bus_get(DBUS_BUS_SESSION, &error);
+ if (error != NULL) {
+ g_error("Unable to get session bus: %s", error->message);
+ return 1;
+ }
+
+ DBusGProxy * bus_proxy = dbus_g_proxy_new_for_name(session_bus, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS);
+ guint nameret = 0;
+
+ if (!org_freedesktop_DBus_request_name(bus_proxy, NOTIFICATION_WATCHER_DBUS_ADDR, 0, &nameret, &error)) {
+ g_error("Unable to call to request name");
+ return 1;
+ }
+
+ if (nameret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
+ g_error("Unable to get name");
+ return 1;
+ }
+
+ dbus_connection_add_filter(dbus_g_connection_get_connection(session_bus), dbus_filter, NULL, NULL);
+
+ /* After we've got the name, let it unfallback, and then we'll drop again */
+ g_timeout_add(250, kill_func, NULL);
+
+ g_debug("Entering Mainloop");
+
+ mainloop = g_main_loop_new(NULL, FALSE);
+ g_main_loop_run(mainloop);
+
+ g_debug("Exiting");
+
+ return 0;
+}