diff options
30 files changed, 2381 insertions, 138 deletions
@@ -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(¤ttime); + GDate * currentdate = g_date_new(); + g_date_set_time_val(currentdate, ¤ttime); + + 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; +} |