diff options
-rw-r--r-- | Makefile.am | 1 | ||||
-rw-r--r-- | common/Makefile.am | 32 | ||||
-rw-r--r-- | common/com.canonical.indicator.messages.application.xml | 33 | ||||
-rw-r--r-- | common/com.canonical.indicator.messages.service.xml (renamed from src/messages-service.xml) | 0 | ||||
-rw-r--r-- | configure.ac | 1 | ||||
-rw-r--r-- | doc/reference/Makefile.am | 3 | ||||
-rw-r--r-- | libmessaging-menu/Makefile.am | 37 | ||||
-rw-r--r-- | libmessaging-menu/gtupleaction.c | 354 | ||||
-rw-r--r-- | libmessaging-menu/gtupleaction.h | 40 | ||||
-rw-r--r-- | libmessaging-menu/messaging-menu-app.c (renamed from libmessaging-menu/messaging-menu.c) | 716 | ||||
-rw-r--r-- | libmessaging-menu/messaging-menu-app.h | 160 | ||||
-rw-r--r-- | libmessaging-menu/messaging-menu-message.c | 372 | ||||
-rw-r--r-- | libmessaging-menu/messaging-menu-message.h | 64 | ||||
-rw-r--r-- | libmessaging-menu/messaging-menu.h | 125 | ||||
-rw-r--r-- | src/Makefile.am | 30 | ||||
-rw-r--r-- | src/app-section.c | 424 | ||||
-rw-r--r-- | src/dbus-data.h | 2 | ||||
-rw-r--r-- | src/gactionmuxer.c | 8 | ||||
-rw-r--r-- | src/gactionmuxer.h | 3 | ||||
-rw-r--r-- | src/messages-service.c | 552 | ||||
-rw-r--r-- | test/Makefile.am | 5 |
21 files changed, 1583 insertions, 1379 deletions
diff --git a/Makefile.am b/Makefile.am index daeb2b7..f8141a8 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,5 +1,6 @@ SUBDIRS = \ + common \ src \ libmessaging-menu \ data \ diff --git a/common/Makefile.am b/common/Makefile.am new file mode 100644 index 0000000..0b8bad4 --- /dev/null +++ b/common/Makefile.am @@ -0,0 +1,32 @@ + +noinst_LTLIBRARIES = libmessaging-common.la + +indicator-messages-service.c: com.canonical.indicator.messages.service.xml + $(AM_V_GEN) gdbus-codegen \ + --interface-prefix com.canonical.indicator.messages. \ + --generate-c-code indicator-messages-service \ + --c-namespace IndicatorMessages \ + $^ +indicator-messages-service.h: indicator-messages-service.c + +indicator-messages-application.c: com.canonical.indicator.messages.application.xml + $(AM_V_GEN) gdbus-codegen \ + --interface-prefix com.canonical.indicator.messages. \ + --generate-c-code indicator-messages-application \ + --c-namespace IndicatorMessages \ + $^ +indicator-messages-application.h: indicator-messages-application.c + +BUILT_SOURCES = \ + indicator-messages-service.c \ + indicator-messages-service.h \ + indicator-messages-application.c \ + indicator-messages-application.h + +libmessaging_common_la_SOURCES = \ + $(BUILT_SOURCES) + +libmessaging_common_la_CFLAGS = $(GIO_CFLAGS) +libmessaging_common_la_LIBADD = $(GIO_LIBS) + +CLEANFILES = $(BUILT_SOURCES) diff --git a/common/com.canonical.indicator.messages.application.xml b/common/com.canonical.indicator.messages.application.xml new file mode 100644 index 0000000..fb9f079 --- /dev/null +++ b/common/com.canonical.indicator.messages.application.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8"?> +<node name="/"> + <interface name="com.canonical.indicator.messages.application"> + <method name="ListSources"> + <arg type="a(sssuxsb)" name="sources" direction="out" /> + </method> + <method name="ListMessages"> + <arg type="a(sssssxb)" name="message" direction="out" /> + </method> + <method name="ActivateSource"> + <arg type="s" name="source_id" direction="in" /> + </method> + <method name="ActivateMessage"> + <arg type="s" name="message_id" direction="in" /> + </method> + <signal name="SourceAdded"> + <arg type="u" name="position" direction="in" /> + <arg type="(sssuxsb)" name="source" direction="in" /> + </signal> + <signal name="SourceChanged"> + <arg type="(sssuxsb)" name="source" direction="in" /> + </signal> + <signal name="SourceRemoved"> + <arg type="s" name="source_id" direction="in" /> + </signal> + <signal name="MessageAdded"> + <arg type="(sssssxb)" name="message" direction="in" /> + </signal> + <signal name="MessageRemoved"> + <arg type="s" name="message_id" direction="in" /> + </signal> + </interface> +</node> diff --git a/src/messages-service.xml b/common/com.canonical.indicator.messages.service.xml index 00ae154..00ae154 100644 --- a/src/messages-service.xml +++ b/common/com.canonical.indicator.messages.service.xml diff --git a/configure.ac b/configure.ac index 6dad74c..b020d30 100644 --- a/configure.ac +++ b/configure.ac @@ -156,6 +156,7 @@ AM_GLIB_GNU_GETTEXT AC_OUTPUT([ Makefile src/Makefile +common/Makefile data/Makefile data/icons/Makefile data/icons/16x16/Makefile diff --git a/doc/reference/Makefile.am b/doc/reference/Makefile.am index 023f1e7..3ea08f2 100644 --- a/doc/reference/Makefile.am +++ b/doc/reference/Makefile.am @@ -12,8 +12,7 @@ HFILE_GLOB = $(top_srcdir)/libmessaging-menu/*.h CFILE_GLOB = $(top_srcdir)/libmessaging-menu/*.c IGNORE_HFILES= \ - indicator-messages-service.h \ - gtupleaction.h + indicator-messages-service.h INCLUDES=-I$(top_srcdir)/libmessaging-menu $(GIO_CFLAGS) GTKDOC_LIBS=$(top_builddir)/libmessaging-menu/libmessaging-menu.la diff --git a/libmessaging-menu/Makefile.am b/libmessaging-menu/Makefile.am index 7a6ee31..d18538b 100644 --- a/libmessaging-menu/Makefile.am +++ b/libmessaging-menu/Makefile.am @@ -4,36 +4,25 @@ lib_LTLIBRARIES = libmessaging-menu.la libmessaging_menu_ladir = $(includedir)/messaging-menu libmessaging_menu_la_SOURCES = \ - messaging-menu.c \ - gtupleaction.c \ - gtupleaction.h \ - $(BUILT_SOURCES) + messaging-menu-app.c \ + messaging-menu-message.c libmessaging_menu_la_HEADERS = \ - messaging-menu.h + messaging-menu-app.h \ + messaging-menu.h \ + messaging-menu-message.h -libmessaging_menu_la_LIBADD = $(GIO_LIBS) +libmessaging_menu_la_LIBADD = \ + $(GIO_LIBS) \ + $(top_builddir)/common/libmessaging-common.la libmessaging_menu_la_CFLAGS = \ + -I$(top_builddir)/common \ $(GIO_CFLAGS) \ -Wall libmessaging_menu_la_LDFLAGS = -export-symbols-regex "^messaging_menu_.*" -BUILT_SOURCES = \ - indicator-messages-service.c \ - indicator-messages-service.h - -CLEANFILES = $(BUILT_SOURCES) - -indicator-messages-service.c: $(top_srcdir)/src/messages-service.xml - $(AM_V_GEN) gdbus-codegen \ - --interface-prefix com.canonical.indicator.messages. \ - --generate-c-code indicator-messages-service \ - --c-namespace IndicatorMessages \ - $^ -indicator-messages-service.h: indicator-messages-service.c - pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = messaging-menu.pc @@ -52,7 +41,11 @@ MessagingMenu_1_0_gir_INCLUDES = GObject-2.0 Gio-2.0 MessagingMenu_1_0_gir_CFLAGS = $(INCLUDES) $(GIO_CFLAGS) MessagingMenu_1_0_gir_SCANNERFLAGS = --c-include="messaging-menu.h" MessagingMenu_1_0_gir_LIBS = libmessaging-menu.la -MessagingMenu_1_0_gir_FILES = messaging-menu.c messaging-menu.h +MessagingMenu_1_0_gir_FILES = \ + messaging-menu-app.c \ + messaging-menu-app.h \ + messaging-menu-message.c \ + messaging-menu-message.h MessagingMenu_1_0_gir_EXPORT_PACKAGES = messaging-menu INTROSPECTION_GIRS += MessagingMenu-1.0.gir @@ -62,5 +55,5 @@ gir_DATA = $(INTROSPECTION_GIRS) typelibdir = $(libdir)/girepository-1.0 typelib_DATA = $(INTROSPECTION_GIRS:.gir=.typelib) -CLEANFILES +=$(gir_DATA) $(typelib_DATA) +CLEANFILES = $(gir_DATA) $(typelib_DATA) endif diff --git a/libmessaging-menu/gtupleaction.c b/libmessaging-menu/gtupleaction.c deleted file mode 100644 index 21bc003..0000000 --- a/libmessaging-menu/gtupleaction.c +++ /dev/null @@ -1,354 +0,0 @@ -/* - * Copyright 2012 Canonical Ltd. - * - * 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/>. - * - * Authors: - * Lars Uebernickel <lars.uebernickel@canonical.com> - */ - -#include "gtupleaction.h" - -typedef GObjectClass GTupleActionClass; - -struct _GTupleAction -{ - GObject parent; - - gchar *name; - GVariantType *type; - gboolean enabled; - - gsize n_children; - GVariant **children; -}; - -static void action_interface_init (GActionInterface *iface); - -G_DEFINE_TYPE_WITH_CODE (GTupleAction, g_tuple_action, G_TYPE_OBJECT, - G_IMPLEMENT_INTERFACE (G_TYPE_ACTION, action_interface_init)); - -enum -{ - PROP_0, - PROP_NAME, - PROP_PARAMETER_TYPE, - PROP_ENABLED, - PROP_STATE_TYPE, - PROP_STATE, - N_PROPERTIES -}; - -enum -{ - SIGNAL_ACTIVATE, - N_SIGNALS -}; - -static GParamSpec *properties[N_PROPERTIES]; -static guint signal_ids[N_SIGNALS]; - -static const gchar * -g_tuple_action_get_name (GAction *action) -{ - GTupleAction *tuple = G_TUPLE_ACTION (action); - - return tuple->name; -} - -static const GVariantType * -g_tuple_action_get_parameter_type (GAction *action) -{ - return NULL; -} - -static const GVariantType * -g_tuple_action_get_state_type (GAction *action) -{ - GTupleAction *tuple = G_TUPLE_ACTION (action); - - return tuple->type; -} - -static GVariant * -g_tuple_action_get_state_hint (GAction *action) -{ - return NULL; -} - -static gboolean -g_tuple_action_get_enabled (GAction *action) -{ - GTupleAction *tuple = G_TUPLE_ACTION (action); - - return tuple->enabled; -} - -static GVariant * -g_tuple_action_get_state (GAction *action) -{ - GTupleAction *tuple = G_TUPLE_ACTION (action); - GVariant *result; - - result = g_variant_new_tuple (tuple->children, tuple->n_children); - return g_variant_ref_sink (result); -} - -static void -g_tuple_action_set_state (GTupleAction *tuple, - GVariant *state) -{ - int i; - - g_return_if_fail (g_variant_type_is_tuple (g_variant_get_type (state))); - - if (tuple->type == NULL) - { - tuple->type = g_variant_type_copy (g_variant_get_type (state)); - tuple->n_children = g_variant_n_children (state); - tuple->children = g_new0 (GVariant *, tuple->n_children); - } - - for (i = 0; i < tuple->n_children; i++) - { - if (tuple->children[i]) - g_variant_unref (tuple->children[i]); - tuple->children[i] = g_variant_get_child_value (state, i); - } - - g_object_notify_by_pspec (G_OBJECT (tuple), properties[PROP_STATE]); -} - -static void -g_tuple_action_change_state (GAction *action, - GVariant *value) -{ - GTupleAction *tuple = G_TUPLE_ACTION (action); - - g_return_if_fail (value != NULL); - g_return_if_fail (g_variant_is_of_type (value, tuple->type)); - - g_variant_ref_sink (value); - - /* TODO add a change-state signal similar to GSimpleAction */ - g_tuple_action_set_state (tuple, value); - - g_variant_unref (value); -} - -static void -g_tuple_action_activate (GAction *action, - GVariant *parameter) -{ - GTupleAction *tuple = G_TUPLE_ACTION (action); - - g_return_if_fail (parameter == NULL); - - if (tuple->enabled) - g_signal_emit (tuple, signal_ids[SIGNAL_ACTIVATE], 0, NULL); -} - -static void -g_tuple_action_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - GAction *action = G_ACTION (object); - - switch (prop_id) - { - case PROP_NAME: - g_value_set_string (value, g_tuple_action_get_name (action)); - break; - - case PROP_PARAMETER_TYPE: - g_value_set_boxed (value, g_tuple_action_get_parameter_type (action)); - break; - - case PROP_ENABLED: - g_value_set_boolean (value, g_tuple_action_get_enabled (action)); - break; - - case PROP_STATE_TYPE: - g_value_set_boxed (value, g_tuple_action_get_state_type (action)); - break; - - case PROP_STATE: - g_value_take_variant (value, g_tuple_action_get_state (action)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -g_tuple_action_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - GTupleAction *tuple = G_TUPLE_ACTION (object); - - switch (prop_id) - { - case PROP_NAME: - tuple->name = g_value_dup_string (value); - g_object_notify_by_pspec (object, properties[PROP_NAME]); - break; - - case PROP_ENABLED: - tuple->enabled = g_value_get_boolean (value); - g_object_notify_by_pspec (object, properties[PROP_ENABLED]); - break; - - case PROP_STATE: - g_tuple_action_set_state (tuple, g_value_get_variant (value)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -g_tuple_action_finalize (GObject *object) -{ - GTupleAction *tuple = G_TUPLE_ACTION (object); - int i; - - g_free (tuple->name); - g_variant_type_free (tuple->type); - - for (i = 0; i < tuple->n_children; i++) - g_variant_unref (tuple->children[i]); - - g_free (tuple->children); - - G_OBJECT_CLASS (g_tuple_action_parent_class)->finalize (object); -} - -static void -action_interface_init (GActionInterface *iface) -{ - iface->get_name = g_tuple_action_get_name; - iface->get_parameter_type = g_tuple_action_get_parameter_type; - iface->get_state_type = g_tuple_action_get_state_type; - iface->get_state_hint = g_tuple_action_get_state_hint; - iface->get_enabled = g_tuple_action_get_enabled; - iface->get_state = g_tuple_action_get_state; - iface->change_state = g_tuple_action_change_state; - iface->activate = g_tuple_action_activate; -} - -static void -g_tuple_action_class_init (GTupleActionClass *class) -{ - GObjectClass *object_class = G_OBJECT_CLASS (class); - - object_class->get_property = g_tuple_action_get_property; - object_class->set_property = g_tuple_action_set_property; - object_class->finalize = g_tuple_action_finalize; - - properties[PROP_NAME] = g_param_spec_string ("name", - "Name", - "The name of the action", - NULL, - G_PARAM_READWRITE | - G_PARAM_CONSTRUCT_ONLY | - G_PARAM_STATIC_STRINGS); - - properties[PROP_PARAMETER_TYPE] = g_param_spec_boxed ("parameter-type", - "Parameter Type", - "The variant type passed to activate", - G_TYPE_VARIANT_TYPE, - G_PARAM_READABLE | - G_PARAM_STATIC_STRINGS); - - properties[PROP_ENABLED] = g_param_spec_boolean ("enabled", - "Enabled", - "Whether the action can be activated", - TRUE, - G_PARAM_READWRITE | - G_PARAM_STATIC_STRINGS); - - properties[PROP_STATE_TYPE] = g_param_spec_boxed ("state-type", - "State Type", - "The variant type of the state, must be a tuple", - G_TYPE_VARIANT_TYPE, - G_PARAM_READABLE | - G_PARAM_STATIC_STRINGS); - - properties[PROP_STATE] = g_param_spec_variant ("state", - "State", - "The state of the action", - G_VARIANT_TYPE_TUPLE, - NULL, - G_PARAM_READWRITE | - G_PARAM_STATIC_STRINGS); - - g_object_class_install_properties (object_class, N_PROPERTIES, properties); - - signal_ids[SIGNAL_ACTIVATE] = g_signal_new ("activate", - G_TYPE_TUPLE_ACTION, - G_SIGNAL_RUN_LAST | G_SIGNAL_MUST_COLLECT, - 0, NULL, NULL, - g_cclosure_marshal_VOID__VARIANT, - G_TYPE_NONE, 1, - G_TYPE_VARIANT); -} - -static void -g_tuple_action_init (GTupleAction *action) -{ - action->enabled = TRUE; -} - -GTupleAction * -g_tuple_action_new (const gchar *name, - GVariant *initial_state) -{ - const GVariantType *type; - - g_return_val_if_fail (name != NULL, NULL); - g_return_val_if_fail (initial_state != NULL, NULL); - - type = g_variant_get_type (initial_state); - g_return_val_if_fail (g_variant_type_is_tuple (type), NULL); - - return g_object_new (G_TYPE_TUPLE_ACTION, - "name", name, - "state", initial_state, - NULL); -} - -void -g_tuple_action_set_child (GTupleAction *action, - gsize index, - GVariant *value) -{ - const GVariantType *type; - - g_return_if_fail (G_IS_TUPLE_ACTION (action)); - g_return_if_fail (index < action->n_children); - g_return_if_fail (value != NULL); - - type = g_variant_get_type (value); - g_return_if_fail (g_variant_is_of_type (value, type)); - - g_variant_unref (action->children[index]); - action->children[index] = g_variant_ref_sink (value); - - g_object_notify_by_pspec (G_OBJECT (action), properties[PROP_STATE]); -} diff --git a/libmessaging-menu/gtupleaction.h b/libmessaging-menu/gtupleaction.h deleted file mode 100644 index c447d71..0000000 --- a/libmessaging-menu/gtupleaction.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2012 Canonical Ltd. - * - * 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/>. - * - * Authors: - * Lars Uebernickel <lars.uebernickel@canonical.com> - */ - -#ifndef __g_tuple_action_h__ -#define __g_tuple_action_h__ - -#include <gio/gio.h> - -#define G_TYPE_TUPLE_ACTION (g_tuple_action_get_type ()) -#define G_TUPLE_ACTION(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_TUPLE_ACTION, GTupleAction)) -#define G_IS_TUPLE_ACTION(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_TUPLE_ACTION)) - -typedef struct _GTupleAction GTupleAction; - -GType g_tuple_action_get_type (void) G_GNUC_CONST; - -GTupleAction * g_tuple_action_new (const gchar *name, - GVariant *initial_state); - -void g_tuple_action_set_child (GTupleAction *action, - gsize index, - GVariant *value); - -#endif diff --git a/libmessaging-menu/messaging-menu.c b/libmessaging-menu/messaging-menu-app.c index 38883f8..bc7e978 100644 --- a/libmessaging-menu/messaging-menu.c +++ b/libmessaging-menu/messaging-menu-app.c @@ -17,10 +17,12 @@ * Lars Uebernickel <lars.uebernickel@canonical.com> */ -#include "messaging-menu.h" +#include "messaging-menu-app.h" #include "indicator-messages-service.h" +#include "indicator-messages-application.h" #include <gio/gdesktopappinfo.h> +#include <string.h> /** * SECTION:messaging-menu @@ -102,14 +104,14 @@ struct _MessagingMenuApp int registered; /* -1 for unknown */ MessagingMenuStatus status; gboolean status_set; - GSimpleActionGroup *source_actions; - GMenu *menu; GDBusConnection *bus; + GHashTable *messages; + GList *sources; + IndicatorMessagesApplication *app_interface; + IndicatorMessagesService *messages_service; guint watch_id; - guint action_export_id; - guint menu_export_id; GCancellable *cancellable; }; @@ -124,6 +126,7 @@ enum { enum { ACTIVATE_SOURCE, + ACTIVATE_MESSAGE, STATUS_CHANGED, N_SIGNALS }; @@ -133,10 +136,81 @@ static guint signals[N_SIGNALS]; static const gchar *status_ids[] = { "available", "away", "busy", "invisible", "offline" }; +typedef struct +{ + gchar *id; + GIcon *icon; + gchar *label; + + guint32 count; + gint64 time; + gchar *string; + gboolean draws_attention; +} Source; + static void global_status_changed (IndicatorMessagesService *service, const gchar *status_str, gpointer user_data); +static void +source_free (gpointer data) +{ + Source *source = data; + + if (source) + { + g_free (source->id); + g_clear_object (&source->icon); + g_free (source->label); + g_free (source->string); + g_slice_free (Source, source); + } +} + +static GVariant * +source_to_variant (Source *source) +{ + GVariant *v; + gchar *iconstr; + + iconstr = source->icon ? g_icon_to_string (source->icon) : NULL; + + v = g_variant_new ("(sssuxsb)", source->id, + source->label, + iconstr ? iconstr : "", + source->count, + source->time, + source->string ? source->string : "", + source->draws_attention); + + g_free (iconstr); + + return v; +} + +static GVariant * +messaging_menu_message_to_variant (MessagingMenuMessage *message) +{ + GVariant *v; + GIcon *icon; + gchar *iconstr; + + icon = messaging_menu_message_get_icon (message); + iconstr = icon ? g_icon_to_string (icon) : NULL; + + v = g_variant_new ("(sssssxb)", messaging_menu_message_get_id (message), + iconstr ? iconstr : "", + messaging_menu_message_get_title (message), + messaging_menu_message_get_subtitle (message), + messaging_menu_message_get_body (message), + messaging_menu_message_get_time (message), + messaging_menu_message_get_draws_attention (message)); + + g_free (iconstr); + + return v; +} + static gchar * messaging_menu_app_get_dbus_object_path (MessagingMenuApp *app) { @@ -155,18 +229,14 @@ messaging_menu_app_get_dbus_object_path (MessagingMenuApp *app) } static void -export_menus_and_actions (GObject *source, - GAsyncResult *res, - gpointer user_data) +messaging_menu_app_got_bus (GObject *source, + GAsyncResult *res, + gpointer user_data) { MessagingMenuApp *app = user_data; GError *error = NULL; gchar *object_path; - object_path = messaging_menu_app_get_dbus_object_path (app); - if (!object_path) - return; - app->bus = g_bus_get_finish (res, &error); if (app->bus == NULL) { @@ -175,23 +245,13 @@ export_menus_and_actions (GObject *source, return; } - app->action_export_id = g_dbus_connection_export_action_group (app->bus, - object_path, - G_ACTION_GROUP (app->source_actions), - &error); - if (!app->action_export_id) - { - g_warning ("unable to export action group: %s", error->message); - g_clear_error (&error); - } + object_path = messaging_menu_app_get_dbus_object_path (app); - app->menu_export_id = g_dbus_connection_export_menu_model (app->bus, - object_path, - G_MENU_MODEL (app->menu), - &error); - if (!app->menu_export_id) + if (object_path && + !g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (app->app_interface), + app->bus, object_path, &error)) { - g_warning ("unable to export menu: %s", error->message); + g_warning ("unable to export application interface: %s", error->message); g_clear_error (&error); } @@ -214,7 +274,7 @@ messaging_menu_app_set_desktop_id (MessagingMenuApp *app, g_bus_get (G_BUS_TYPE_SESSION, app->cancellable, - export_menus_and_actions, + messaging_menu_app_got_bus, app); } @@ -248,20 +308,6 @@ messaging_menu_app_dispose (GObject *object) { MessagingMenuApp *app = MESSAGING_MENU_APP (object); - if (app->bus) - { - if (app->action_export_id > 0) - g_dbus_connection_unexport_action_group (app->bus, app->action_export_id); - - if (app->menu_export_id > 0) - g_dbus_connection_unexport_menu_model (app->bus, app->menu_export_id); - - app->action_export_id = 0; - app->menu_export_id = 0; - g_object_unref (app->bus); - app->bus = NULL; - } - if (app->watch_id > 0) { g_bus_unwatch_name (app->watch_id); @@ -283,9 +329,14 @@ messaging_menu_app_dispose (GObject *object) g_clear_object (&app->messages_service); } + g_clear_pointer (&app->messages, g_hash_table_unref); + + g_list_free_full (app->sources, source_free); + app->sources = NULL; + + g_clear_object (&app->app_interface); g_clear_object (&app->appinfo); - g_clear_object (&app->source_actions); - g_clear_object (&app->menu); + g_clear_object (&app->bus); G_OBJECT_CLASS (messaging_menu_app_parent_class)->dispose (object); } @@ -335,6 +386,27 @@ messaging_menu_app_class_init (MessagingMenuAppClass *class) G_TYPE_NONE, 1, G_TYPE_STRING); /** + * MessagingMenuApp::activate-message: + * @mmapp: the #MessagingMenuApp + * @message: the activated #MessagingMenuMessage + * + * Emitted when the user has activated a message. The message is + * immediately removed from the application's menu, handlers of this + * signal do not need to call messaging_menu_app_remove_message(). + * + * To get notified about the activation of a specific message, set the + * signal's detail to the message id. + */ + signals[ACTIVATE_MESSAGE] = g_signal_new ("activate-message", + MESSAGING_MENU_TYPE_APP, + G_SIGNAL_RUN_FIRST | + G_SIGNAL_DETAILED, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, MESSAGING_MENU_TYPE_MESSAGE); + + /** * MessagingMenuApp::status-changed: * @mmapp: the #MessagingMenuApp * @status: a #MessagingMenuStatus @@ -416,6 +488,125 @@ indicator_messages_vanished (GDBusConnection *bus, } } +static gboolean +messaging_menu_app_list_sources (IndicatorMessagesApplication *app_interface, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + MessagingMenuApp *app = user_data; + GVariantBuilder builder; + GList *it; + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(sssuxsb)")); + + for (it = app->sources; it; it = it->next) + g_variant_builder_add_value (&builder, source_to_variant (it->data)); + + indicator_messages_application_complete_list_sources (app_interface, + invocation, + g_variant_builder_end (&builder)); + + return TRUE; +} + +static gint +compare_source_id (gconstpointer a, + gconstpointer b) +{ + const Source *source = a; + const gchar *id = b; + + return strcmp (source->id, id); +} + +static gboolean +messaging_menu_app_remove_source_internal (MessagingMenuApp *app, + const gchar *source_id) +{ + GList *node; + + node = g_list_find_custom (app->sources, source_id, compare_source_id); + if (node) + { + source_free (node->data); + app->sources = g_list_delete_link (app->sources, node); + return TRUE; + } + + return FALSE; +} + +static gboolean +messaging_menu_app_remove_message_internal (MessagingMenuApp *app, + const gchar *message_id) +{ + return g_hash_table_remove (app->messages, message_id); +} + +static gboolean +messaging_menu_app_activate_source (IndicatorMessagesApplication *app_interface, + GDBusMethodInvocation *invocation, + const gchar *source_id, + gpointer user_data) +{ + MessagingMenuApp *app = user_data; + GQuark q = g_quark_from_string (source_id); + + /* Activate implies removing the source, no need for SourceRemoved */ + if (messaging_menu_app_remove_source_internal (app, source_id)) + g_signal_emit (app, signals[ACTIVATE_SOURCE], q, source_id); + + indicator_messages_application_complete_activate_source (app_interface, invocation); + + return TRUE; +} + +static gboolean +messaging_menu_app_list_messages (IndicatorMessagesApplication *app_interface, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + MessagingMenuApp *app = user_data; + GVariantBuilder builder; + GHashTableIter iter; + MessagingMenuMessage *message; + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(sssssxb)")); + + g_hash_table_iter_init (&iter, app->messages); + while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &message)) + g_variant_builder_add_value (&builder, messaging_menu_message_to_variant (message)); + + indicator_messages_application_complete_list_messages (app_interface, + invocation, + g_variant_builder_end (&builder)); + + return TRUE; +} + +static gboolean +messaging_menu_app_activate_message (IndicatorMessagesApplication *app_interface, + GDBusMethodInvocation *invocation, + const gchar *message_id, + gpointer user_data) +{ + MessagingMenuApp *app = user_data; + MessagingMenuMessage *msg; + + msg = g_hash_table_lookup (app->messages, message_id); + if (msg) + { + g_signal_emit (app, signals[ACTIVATE_MESSAGE], g_quark_from_string (message_id), msg); + + /* Activate implies removing the message, no need for MessageRemoved */ + messaging_menu_app_remove_message_internal (app, message_id); + } + + indicator_messages_application_complete_activate_message (app_interface, invocation); + + return TRUE; +} + static void messaging_menu_app_init (MessagingMenuApp *app) { @@ -423,15 +614,19 @@ messaging_menu_app_init (MessagingMenuApp *app) app->status_set = FALSE; app->bus = NULL; - app->action_export_id = 0; - app->menu_export_id = 0; - app->cancellable = g_cancellable_new (); - app->source_actions = g_simple_action_group_new (); - app->menu = g_menu_new (); + app->app_interface = indicator_messages_application_skeleton_new (); + g_signal_connect (app->app_interface, "handle-list-sources", + G_CALLBACK (messaging_menu_app_list_sources), app); + g_signal_connect (app->app_interface, "handle-activate-source", + G_CALLBACK (messaging_menu_app_activate_source), app); + g_signal_connect (app->app_interface, "handle-list-messages", + G_CALLBACK (messaging_menu_app_list_messages), app); + g_signal_connect (app->app_interface, "handle-activate-message", + G_CALLBACK (messaging_menu_app_activate_message), app); - app->cancellable = g_cancellable_new (); + app->messages = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); app->watch_id = g_bus_watch_name (G_BUS_TYPE_SESSION, "com.canonical.indicator.messages", @@ -604,123 +799,73 @@ global_status_changed (IndicatorMessagesService *service, g_signal_emit (app, signals[STATUS_CHANGED], 0, status); } -static void -source_action_activated (GSimpleAction *action, - GVariant *parameter, - gpointer user_data) +static Source * +messaging_menu_app_lookup_source (MessagingMenuApp *app, + const gchar *id) { - MessagingMenuApp *app = user_data; - const gchar *name = g_action_get_name (G_ACTION (action)); - GQuark q = g_quark_from_string (name); + GList *node; - messaging_menu_app_remove_source (app, name); + node = g_list_find_custom (app->sources, id, compare_source_id); - g_signal_emit (app, signals[ACTIVATE_SOURCE], q, name); + return node ? node->data : NULL; } -static void -messaging_menu_app_insert_source_action (MessagingMenuApp *app, - gint position, - const gchar *id, - GIcon *icon, - const gchar *label, - GVariant *state) +static Source * +messaging_menu_app_get_source (MessagingMenuApp *app, + const gchar *id) { - GSimpleAction *action; - GMenuItem *menuitem; - - g_return_if_fail (MESSAGING_MENU_IS_APP (app)); - g_return_if_fail (id != NULL); + Source *source; - if (g_simple_action_group_lookup (app->source_actions, id)) - { - g_warning ("a source with id '%s' already exists", id); - return; - } + source = messaging_menu_app_lookup_source (app, id); + if (!source) + g_warning ("a source with id '%s' doesn't exist", id); - action = g_simple_action_new_stateful (id, NULL, state); - g_signal_connect (action, "activate", - G_CALLBACK (source_action_activated), app); - g_simple_action_group_insert (app->source_actions, G_ACTION (action)); - g_object_unref (action); - - menuitem = g_menu_item_new (label, id); - g_menu_item_set_attribute (menuitem, "x-canonical-type", "s", "ImSourceMenuItem"); - if (icon) - { - gchar *iconstr = g_icon_to_string (icon); - g_menu_item_set_attribute (menuitem, "x-canonical-icon", "s", iconstr); - g_free (iconstr); - } - g_menu_insert_item (app->menu, position, menuitem); - g_object_unref (menuitem); -} - -static GSimpleAction * -messaging_menu_app_get_source_action (MessagingMenuApp *app, - const gchar *source_id) - -{ - GAction *action; - - g_return_val_if_fail (MESSAGING_MENU_IS_APP (app), NULL); - g_return_val_if_fail (source_id != NULL, NULL); - - action = g_simple_action_group_lookup (app->source_actions, source_id); - if (action == NULL) - g_warning ("a source with id '%s' doesn't exist", source_id); - - return G_SIMPLE_ACTION (action); + return source; } static void -messaging_menu_app_set_source_action (MessagingMenuApp *app, - const gchar *source_id, - guint count, - gint64 time, - const gchar *string) +messaging_menu_app_notify_source_changed (MessagingMenuApp *app, + Source *source) { - GSimpleAction *action; - GVariant *state; - gboolean draws_attention; - GVariant *new_state; - - action = messaging_menu_app_get_source_action (app, source_id); - if (!action) - return; - - state = g_action_get_state (G_ACTION (action)); - g_variant_get_child (state, 3, "b", &draws_attention); - - new_state = g_variant_new ("(uxsb)", count, time, string, draws_attention); - g_simple_action_set_state (action, new_state); - - g_variant_unref (state); + indicator_messages_application_emit_source_changed (app->app_interface, + source_to_variant (source)); } static void -messaging_menu_app_set_draws_attention (MessagingMenuApp *app, - const gchar *source_id, - gboolean draws_attention) +messaging_menu_app_insert_source_internal (MessagingMenuApp *app, + gint position, + const gchar *id, + GIcon *icon, + const gchar *label, + guint count, + gint64 time, + const gchar *string) { - GSimpleAction *action; - GVariant *state; - guint count; - gint64 time; - const gchar *string; - GVariant *new_state; + Source *source; - action = messaging_menu_app_get_source_action (app, source_id); - if (!action) - return; - - state = g_action_get_state (G_ACTION (action)); - g_variant_get (state, "(ux&sb)", &count, &time, &string); + g_return_if_fail (MESSAGING_MENU_IS_APP (app)); + g_return_if_fail (id != NULL); + g_return_if_fail (label != NULL); - new_state = g_variant_new ("(uxsb)", count, time, string, TRUE); - g_simple_action_set_state (action, new_state); + if (messaging_menu_app_lookup_source (app, id)) + { + g_warning ("a source with id '%s' already exists", id); + return; + } - g_variant_unref (state); + source = g_slice_new0 (Source); + source->id = g_strdup (id); + source->label = g_strdup (label); + if (icon) + source->icon = g_object_ref (icon); + source->count = count; + source->time = time; + source->string = g_strdup (string); + app->sources = g_list_insert (app->sources, source, position); + + indicator_messages_application_emit_source_added (app->app_interface, + position, + source_to_variant (source)); } /** @@ -797,8 +942,7 @@ messaging_menu_app_insert_source_with_count (MessagingMenuApp *app, const gchar *label, guint count) { - messaging_menu_app_insert_source_action (app, position, id, icon, label, - g_variant_new ("(uxsb)", count, 0, "", FALSE)); + messaging_menu_app_insert_source_internal (app, position, id, icon, label, count, 0, ""); } /** @@ -852,8 +996,7 @@ messaging_menu_app_insert_source_with_time (MessagingMenuApp *app, const gchar *label, gint64 time) { - messaging_menu_app_insert_source_action (app, position, id, icon, label, - g_variant_new ("(uxsb)", 0, time, "", FALSE)); + messaging_menu_app_insert_source_internal (app, position, id, icon, label, 0, time, ""); } /** @@ -909,8 +1052,7 @@ messaging_menu_app_insert_source_with_string (MessagingMenuApp *app, const gchar *label, const gchar *str) { - messaging_menu_app_insert_source_action (app, position, id, icon, label, - g_variant_new ("(uxsb)", 0, 0, str, FALSE)); + messaging_menu_app_insert_source_internal (app, position, id, icon, label, 0, 0, str); } /** @@ -950,34 +1092,11 @@ void messaging_menu_app_remove_source (MessagingMenuApp *app, const gchar *source_id) { - int n_items; - int i; - g_return_if_fail (MESSAGING_MENU_IS_APP (app)); g_return_if_fail (source_id != NULL); - if (g_simple_action_group_lookup (app->source_actions, source_id) == NULL) - return; - - n_items = g_menu_model_get_n_items (G_MENU_MODEL (app->menu)); - for (i = 0; i < n_items; i++) - { - gchar *action; - - if (g_menu_model_get_item_attribute (G_MENU_MODEL (app->menu), i, - "action", "s", &action)) - { - if (!g_strcmp0 (action, source_id)) - { - g_menu_remove (app->menu, i); - break; - } - - g_free (action); - } - } - - g_simple_action_group_remove (app->source_actions, source_id); + if (messaging_menu_app_remove_source_internal (app, source_id)) + indicator_messages_application_emit_source_removed (app->app_interface, source_id); } /** @@ -994,46 +1113,7 @@ messaging_menu_app_has_source (MessagingMenuApp *app, g_return_val_if_fail (MESSAGING_MENU_IS_APP (app), FALSE); g_return_val_if_fail (source_id != NULL, FALSE); - return g_simple_action_group_lookup (app->source_actions, source_id) != NULL; -} - -static GMenuItem * -g_menu_find_item_with_action (GMenu *menu, - const gchar *action, - gint *out_pos) -{ - gint i; - gint n_elements; - GMenuItem *item = NULL; - - n_elements = g_menu_model_get_n_items (G_MENU_MODEL (menu)); - - for (i = 0; i < n_elements && item == NULL; i++) - { - GVariant *attr; - - item = g_menu_item_new_from_model (G_MENU_MODEL (menu), i); - attr = g_menu_item_get_attribute_value (item, G_MENU_ATTRIBUTE_ACTION, G_VARIANT_TYPE_STRING); - - if (!g_str_equal (action, g_variant_get_string (attr, NULL))) - g_clear_object (&item); - - g_variant_unref (attr); - } - - if (item && out_pos) - *out_pos = i - 1; - - return item; -} - -static void -g_menu_replace_item (GMenu *menu, - gint pos, - GMenuItem *item) -{ - g_menu_remove (menu, pos); - g_menu_insert_item (menu, pos, item); + return messaging_menu_app_lookup_source (app, source_id) != NULL; } /** @@ -1049,21 +1129,19 @@ messaging_menu_app_set_source_label (MessagingMenuApp *app, const gchar *source_id, const gchar *label) { - gint pos; - GMenuItem *item; + Source *source; g_return_if_fail (MESSAGING_MENU_IS_APP (app)); g_return_if_fail (source_id != NULL); g_return_if_fail (label != NULL); - item = g_menu_find_item_with_action (app->menu, source_id, &pos); - if (item == NULL) - return; - - g_menu_item_set_attribute (item, G_MENU_ATTRIBUTE_LABEL, "s", label); - g_menu_replace_item (app->menu, pos, item); - - g_object_unref (item); + source = messaging_menu_app_get_source (app, source_id); + if (source) + { + g_free (source->label); + source->label = g_strdup (label); + messaging_menu_app_notify_source_changed (app, source); + } } /** @@ -1079,33 +1157,19 @@ messaging_menu_app_set_source_icon (MessagingMenuApp *app, const gchar *source_id, GIcon *icon) { - gint pos; - GMenuItem *item; + Source *source; g_return_if_fail (MESSAGING_MENU_IS_APP (app)); g_return_if_fail (source_id != NULL); - item = g_menu_find_item_with_action (app->menu, source_id, &pos); - if (item == NULL) - return; - - if (icon) + source = messaging_menu_app_get_source (app, source_id); + if (source) { - gchar *iconstr; - - iconstr = g_icon_to_string (icon); - g_menu_item_set_attribute (item, "x-canonical-icon", "s", iconstr); - - g_free (iconstr); + g_clear_object (&source->icon); + if (icon) + source->icon = g_object_ref (icon); + messaging_menu_app_notify_source_changed (app, source); } - else - { - g_menu_item_set_attribute_value (item, "x-canonical-icon", NULL); - } - - g_menu_replace_item (app->menu, pos, item); - - g_object_unref (item); } /** @@ -1120,7 +1184,17 @@ void messaging_menu_app_set_source_count (MessagingMenuApp *app, const gchar *source_id, guint count) { - messaging_menu_app_set_source_action (app, source_id, count, 0, ""); + Source *source; + + g_return_if_fail (MESSAGING_MENU_IS_APP (app)); + g_return_if_fail (source_id != NULL); + + source = messaging_menu_app_get_source (app, source_id); + if (source) + { + source->count = count; + messaging_menu_app_notify_source_changed (app, source); + } } /** @@ -1136,7 +1210,17 @@ messaging_menu_app_set_source_time (MessagingMenuApp *app, const gchar *source_id, gint64 time) { - messaging_menu_app_set_source_action (app, source_id, 0, time, ""); + Source *source; + + g_return_if_fail (MESSAGING_MENU_IS_APP (app)); + g_return_if_fail (source_id != NULL); + + source = messaging_menu_app_get_source (app, source_id); + if (source) + { + source->time = time; + messaging_menu_app_notify_source_changed (app, source); + } } /** @@ -1152,7 +1236,18 @@ messaging_menu_app_set_source_string (MessagingMenuApp *app, const gchar *source_id, const gchar *str) { - messaging_menu_app_set_source_action (app, source_id, 0, 0, str); + Source *source; + + g_return_if_fail (MESSAGING_MENU_IS_APP (app)); + g_return_if_fail (source_id != NULL); + + source = messaging_menu_app_get_source (app, source_id); + if (source) + { + g_free (source->string); + source->string = g_strdup (str); + messaging_menu_app_notify_source_changed (app, source); + } } /** @@ -1170,7 +1265,17 @@ void messaging_menu_app_draw_attention (MessagingMenuApp *app, const gchar *source_id) { - messaging_menu_app_set_draws_attention (app, source_id, TRUE); + Source *source; + + g_return_if_fail (MESSAGING_MENU_IS_APP (app)); + g_return_if_fail (source_id != NULL); + + source = messaging_menu_app_get_source (app, source_id); + if (source) + { + source->draws_attention = TRUE; + messaging_menu_app_notify_source_changed (app, source); + } } /** @@ -1191,5 +1296,106 @@ void messaging_menu_app_remove_attention (MessagingMenuApp *app, const gchar *source_id) { - messaging_menu_app_set_draws_attention (app, source_id, TRUE); + Source *source; + + g_return_if_fail (MESSAGING_MENU_IS_APP (app)); + g_return_if_fail (source_id != NULL); + + source = messaging_menu_app_get_source (app, source_id); + if (source) + { + source->draws_attention = FALSE; + messaging_menu_app_notify_source_changed (app, source); + } +} + +/** + * messaging_menu_app_append_message: + * @app: a #MessagingMenuApp + * @msg: the #MessagingMenuMessage to append + * @source_id: (allow-none): the source id to which @msg is added, or NULL + * @notify: whether a notification bubble should be shown for this + * message + * + * Appends @msg to the source with id @source_id of @app. The messaging + * menu might not display this message immediately if other messages are + * queued before this one. + * + * If @source_id has a count associated with it, that count will be + * increased by one. + * + * If @source_id is %NULL, @msg won't be associated with a source. + */ +void +messaging_menu_app_append_message (MessagingMenuApp *app, + MessagingMenuMessage *msg, + const gchar *source_id, + gboolean notify) +{ + const gchar *id; + + g_return_if_fail (MESSAGING_MENU_IS_APP (app)); + g_return_if_fail (MESSAGING_MENU_IS_MESSAGE (msg)); + + id = messaging_menu_message_get_id (msg); + + if (g_hash_table_lookup (app->messages, id)) + { + g_warning ("a message with id '%s' already exists", id); + return; + } + + g_hash_table_insert (app->messages, g_strdup (id), g_object_ref (msg)); + indicator_messages_application_emit_message_added (app->app_interface, + messaging_menu_message_to_variant (msg)); + + if (source_id) + { + Source *source; + + source = messaging_menu_app_get_source (app, source_id); + if (source && source->count >= 0) + { + source->count++; + messaging_menu_app_notify_source_changed (app, source); + } + } +} + +/** + * messaging_menu_app_remove_message: + * @app: a #MessagingMenuApp + * @msg: the #MessagingMenuMessage to remove + * + * Removes @msg from @app. + * + * If @source_id has a count associated with it, that count will be + * decreased by one. + */ +void +messaging_menu_app_remove_message (MessagingMenuApp *app, + MessagingMenuMessage *msg) +{ + messaging_menu_app_remove_message_by_id (app, messaging_menu_message_get_id (msg)); +} + +/** + * messaging_menu_app_remove_message_by_id: + * @app: a #MessagingMenuApp + * @id: the unique id of @msg + * + * Removes the message with the id @id from @app. + * + * If @source_id has a count associated with it, that count will be + * decreased by one. + */ +void +messaging_menu_app_remove_message_by_id (MessagingMenuApp *app, + const gchar *id) +{ + g_return_if_fail (MESSAGING_MENU_IS_APP (app)); + g_return_if_fail (id != NULL); + + if (messaging_menu_app_remove_message_internal (app, id)) + indicator_messages_application_emit_source_removed (app->app_interface, id); } diff --git a/libmessaging-menu/messaging-menu-app.h b/libmessaging-menu/messaging-menu-app.h new file mode 100644 index 0000000..a2d27bc --- /dev/null +++ b/libmessaging-menu/messaging-menu-app.h @@ -0,0 +1,160 @@ +/* + * Copyright 2012 Canonical Ltd. + * + * 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/>. + * + * Authors: + * Lars Uebernickel <lars.uebernickel@canonical.com> + */ + +#ifndef __messaging_menu_app_h__ +#define __messaging_menu_app_h__ + +#include <gio/gio.h> +#include "messaging-menu-message.h" + +G_BEGIN_DECLS + +#define MESSAGING_MENU_TYPE_APP messaging_menu_app_get_type() +#define MESSAGING_MENU_APP(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), MESSAGING_MENU_TYPE_APP, MessagingMenuApp)) +#define MESSAGING_MENU_APP_CLASS(c) (G_TYPE_CHECK_CLASS_CAST ((c), MESSAGING_MENU_TYPE_APP, MessagingMenuAppClass)) +#define MESSAGING_MENU_IS_APP(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), MESSAGING_MENU_TYPE_APP)) + +/** + * MessagingMenuStatus: + * @MESSAGING_MENU_STATUS_AVAILABLE: available + * @MESSAGING_MENU_STATUS_AWAY: away + * @MESSAGING_MENU_STATUS_BUSY: busy + * @MESSAGING_MENU_STATUS_INVISIBLE: invisible + * @MESSAGING_MENU_STATUS_OFFLINE: offline + * + * An enumeration for the possible chat statuses the messaging menu can be in. + */ +typedef enum { + MESSAGING_MENU_STATUS_AVAILABLE, + MESSAGING_MENU_STATUS_AWAY, + MESSAGING_MENU_STATUS_BUSY, + MESSAGING_MENU_STATUS_INVISIBLE, + MESSAGING_MENU_STATUS_OFFLINE +} MessagingMenuStatus; + + +typedef GObjectClass MessagingMenuAppClass; +typedef struct _MessagingMenuApp MessagingMenuApp; + +GType messaging_menu_app_get_type (void) G_GNUC_CONST; + +MessagingMenuApp * messaging_menu_app_new (const gchar *desktop_id); + +void messaging_menu_app_register (MessagingMenuApp *app); +void messaging_menu_app_unregister (MessagingMenuApp *app); + +void messaging_menu_app_set_status (MessagingMenuApp *app, + MessagingMenuStatus status); + +void messaging_menu_app_insert_source (MessagingMenuApp *app, + gint position, + const gchar *id, + GIcon *icon, + const gchar *label); + +void messaging_menu_app_append_source (MessagingMenuApp *app, + const gchar *id, + GIcon *icon, + const gchar *label); + +void messaging_menu_app_insert_source_with_count (MessagingMenuApp *app, + gint position, + const gchar *id, + GIcon *icon, + const gchar *label, + guint count); + +void messaging_menu_app_append_source_with_count (MessagingMenuApp *app, + const gchar *id, + GIcon *icon, + const gchar *label, + guint count); + +void messaging_menu_app_insert_source_with_time (MessagingMenuApp *app, + gint position, + const gchar *id, + GIcon *icon, + const gchar *label, + gint64 time); + +void messaging_menu_app_append_source_with_time (MessagingMenuApp *app, + const gchar *id, + GIcon *icon, + const gchar *label, + gint64 time); + +void messaging_menu_app_append_source_with_string (MessagingMenuApp *app, + const gchar *id, + GIcon *icon, + const gchar *label, + const gchar *str); + +void messaging_menu_app_insert_source_with_string (MessagingMenuApp *app, + gint position, + const gchar *id, + GIcon *icon, + const gchar *label, + const gchar *str); + +void messaging_menu_app_remove_source (MessagingMenuApp *app, + const gchar *source_id); + +gboolean messaging_menu_app_has_source (MessagingMenuApp *app, + const gchar *source_id); + +void messaging_menu_app_set_source_label (MessagingMenuApp *app, + const gchar *source_id, + const gchar *label); + +void messaging_menu_app_set_source_icon (MessagingMenuApp *app, + const gchar *source_id, + GIcon *icon); + +void messaging_menu_app_set_source_count (MessagingMenuApp *app, + const gchar *source_id, + guint count); + +void messaging_menu_app_set_source_time (MessagingMenuApp *app, + const gchar *source_id, + gint64 time); + +void messaging_menu_app_set_source_string (MessagingMenuApp *app, + const gchar *source_id, + const gchar *str); + +void messaging_menu_app_draw_attention (MessagingMenuApp *app, + const gchar *source_id); + +void messaging_menu_app_remove_attention (MessagingMenuApp *app, + const gchar *source_id); + +void messaging_menu_app_append_message (MessagingMenuApp *app, + MessagingMenuMessage *msg, + const gchar *source_id, + gboolean notify); + +void messaging_menu_app_remove_message (MessagingMenuApp *app, + MessagingMenuMessage *msg); + +void messaging_menu_app_remove_message_by_id (MessagingMenuApp *app, + const gchar *id); + +G_END_DECLS + +#endif diff --git a/libmessaging-menu/messaging-menu-message.c b/libmessaging-menu/messaging-menu-message.c new file mode 100644 index 0000000..f5cb18c --- /dev/null +++ b/libmessaging-menu/messaging-menu-message.c @@ -0,0 +1,372 @@ +/* + * Copyright 2012 Canonical Ltd. + * + * 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/>. + * + * Authors: + * Lars Uebernickel <lars.uebernickel@canonical.com> + */ + +#include "messaging-menu-message.h" + +typedef GObjectClass MessagingMenuMessageClass; + +struct _MessagingMenuMessage +{ + GObject parent; + + gchar *id; + GIcon *icon; + gchar *title; + gchar *subtitle; + gchar *body; + gint64 time; + gboolean draws_attention; +}; + +G_DEFINE_TYPE (MessagingMenuMessage, messaging_menu_message, G_TYPE_OBJECT); + +enum +{ + PROP_0, + PROP_ID, + PROP_ICON, + PROP_TITLE, + PROP_SUBTITLE, + PROP_BODY, + PROP_TIME, + PROP_DRAWS_ATTENTION, + NUM_PROPERTIES +}; + +static GParamSpec *properties[NUM_PROPERTIES]; + +static void +messaging_menu_message_dispose (GObject *object) +{ + MessagingMenuMessage *msg = MESSAGING_MENU_MESSAGE (object); + + g_clear_object (&msg->icon); + + G_OBJECT_CLASS (messaging_menu_message_parent_class)->dispose (object); +} + +static void +messaging_menu_message_finalize (GObject *object) +{ + MessagingMenuMessage *msg = MESSAGING_MENU_MESSAGE (object); + + g_free (msg->id); + g_free (msg->title); + g_free (msg->subtitle); + g_free (msg->body); + + G_OBJECT_CLASS (messaging_menu_message_parent_class)->finalize (object); +} + +static void +messaging_menu_message_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + MessagingMenuMessage *msg = MESSAGING_MENU_MESSAGE (object); + + switch (property_id) + { + case PROP_ID: + g_value_set_string (value, msg->id); + break; + + case PROP_ICON: + g_value_set_object (value, msg->icon); + break; + + case PROP_TITLE: + g_value_set_string (value, msg->title); + break; + + case PROP_SUBTITLE: + g_value_set_string (value, msg->subtitle); + break; + + case PROP_BODY: + g_value_set_string (value, msg->body); + + case PROP_TIME: + g_value_set_int64 (value, msg->time); + break; + + case PROP_DRAWS_ATTENTION: + g_value_set_boolean (value, msg->draws_attention); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +messaging_menu_message_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + MessagingMenuMessage *msg = MESSAGING_MENU_MESSAGE (object); + + switch (property_id) + { + case PROP_ID: + msg->id = g_value_dup_string (value); + break; + + case PROP_ICON: + msg->icon = g_value_dup_object (value); + break; + + case PROP_TITLE: + msg->title = g_value_dup_string (value); + break; + + case PROP_SUBTITLE: + msg->subtitle = g_value_dup_string (value); + break; + + case PROP_BODY: + msg->body = g_value_dup_string (value); + + case PROP_TIME: + msg->time = g_value_get_int64 (value); + break; + + case PROP_DRAWS_ATTENTION: + messaging_menu_message_set_draws_attention (msg, g_value_get_boolean (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +messaging_menu_message_class_init (MessagingMenuMessageClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = messaging_menu_message_dispose; + object_class->finalize = messaging_menu_message_finalize; + object_class->get_property = messaging_menu_message_get_property; + object_class->set_property = messaging_menu_message_set_property; + + properties[PROP_ID] = g_param_spec_string ("id", "Id", + "Unique id of the message", + NULL, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + + properties[PROP_ICON] = g_param_spec_object ("icon", "Icon", + "Icon of the message", + G_TYPE_ICON, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + + properties[PROP_TITLE] = g_param_spec_string ("title", "Title", + "Title of the message", + NULL, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + + properties[PROP_SUBTITLE] = g_param_spec_string ("subtitle", "Subtitle", + "Subtitle of the message", + NULL, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + + properties[PROP_BODY] = g_param_spec_string ("body", "Body", + "First lines of the body of the message", + NULL, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + + properties[PROP_TIME] = g_param_spec_int64 ("time", "Time", + "Time the message was sent, in microseconds", 0, G_MAXINT64, 0, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + + properties[PROP_DRAWS_ATTENTION] = g_param_spec_boolean ("draws-attention", "Draws attention", + "Whether the message should draw attention", + FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (klass, NUM_PROPERTIES, properties); +} + +static void +messaging_menu_message_init (MessagingMenuMessage *self) +{ +} + +/** + * messaging_menu_message_new: + * @id: unique id of the message + * @icon: (transfer full) (allow-none): a #GIcon representing the message + * @title: the title of the message + * @subtitle: (allow-none): the subtitle of the message + * @body: (allow-none): the message body + * @time: the time the message was received + * + * Creates a new #MessagingMenuMessage. + * + * Returns: (transfer full): a new #MessagingMenuMessage + */ +MessagingMenuMessage * +messaging_menu_message_new (const gchar *id, + GIcon *icon, + const gchar *title, + const gchar *subtitle, + const gchar *body, + gint64 time) +{ + g_return_val_if_fail (id != NULL, NULL); + g_return_val_if_fail (title != NULL, NULL); + + return g_object_new (MESSAGING_MENU_TYPE_MESSAGE, + "id", id, + "icon", icon, + "title", title, + "subtitle", subtitle, + "body", body, + "time", time, + NULL); +} + +/** + * messaging_menu_message_get_id: + * @msg: a #MessagingMenuMessage + * + * Returns: the unique id of @msg + */ +const gchar * +messaging_menu_message_get_id (MessagingMenuMessage *msg) +{ + g_return_val_if_fail (MESSAGING_MENU_IS_MESSAGE (msg), NULL); + + return msg->id; +} + +/** + * messaging_menu_message_get_icon: + * @msg: a #MessagingMenuMessage + * + * Returns: (transfer none): the icon of @msg + */ +GIcon * +messaging_menu_message_get_icon (MessagingMenuMessage *msg) +{ + g_return_val_if_fail (MESSAGING_MENU_IS_MESSAGE (msg), NULL); + + return msg->icon; +} + +/** + * messaging_menu_message_get_title: + * @msg: a #MessagingMenuMessage + * + * Returns: the title of @msg + */ +const gchar * +messaging_menu_message_get_title (MessagingMenuMessage *msg) +{ + g_return_val_if_fail (MESSAGING_MENU_IS_MESSAGE (msg), NULL); + + return msg->title; +} + +/** + * messaging_menu_message_get_subtitle: + * @msg: a #MessagingMenuMessage + * + * Returns: the subtitle of @msg + */ +const gchar * +messaging_menu_message_get_subtitle (MessagingMenuMessage *msg) +{ + g_return_val_if_fail (MESSAGING_MENU_IS_MESSAGE (msg), NULL); + + return msg->subtitle; +} + +/** + * messaging_menu_message_get_body: + * @msg: a #MessagingMenuMessage + * + * Returns: the body of @msg + */ +const gchar * +messaging_menu_message_get_body (MessagingMenuMessage *msg) +{ + g_return_val_if_fail (MESSAGING_MENU_IS_MESSAGE (msg), NULL); + + return msg->body; +} + +/** + * messaging_menu_message_get_time: + * @msg: a #MessagingMenuMessage + * + * Returns: the time at which @msg was received + */ +gint64 +messaging_menu_message_get_time (MessagingMenuMessage *msg) +{ + g_return_val_if_fail (MESSAGING_MENU_IS_MESSAGE (msg), 0); + + return msg->time; +} + +/** + * messaging_menu_message_get_draws_attention: + * @msg: a #MessagingMenuMessage + * + * Returns: whether @msg is drawing attention + */ +gboolean +messaging_menu_message_get_draws_attention (MessagingMenuMessage *msg) +{ + g_return_val_if_fail (MESSAGING_MENU_IS_MESSAGE (msg), FALSE); + + return msg->draws_attention; +} + +/** + * messaging_menu_message_set_draws_attention: + * @msg: a #MessagingMenuMessage + * @draws_attention: whether @msg should draw attention + * + * Sets whether @msg is drawing attention. + */ +void +messaging_menu_message_set_draws_attention (MessagingMenuMessage *msg, + gboolean draws_attention) +{ + g_return_if_fail (MESSAGING_MENU_IS_MESSAGE (msg)); + + msg->draws_attention = draws_attention; + g_object_notify_by_pspec (G_OBJECT (msg), properties[PROP_DRAWS_ATTENTION]); +} diff --git a/libmessaging-menu/messaging-menu-message.h b/libmessaging-menu/messaging-menu-message.h new file mode 100644 index 0000000..068247b --- /dev/null +++ b/libmessaging-menu/messaging-menu-message.h @@ -0,0 +1,64 @@ +/* + * Copyright 2012 Canonical Ltd. + * + * 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/>. + * + * Authors: + * Lars Uebernickel <lars.uebernickel@canonical.com> + */ + +#ifndef __messaging_menu_message_h__ +#define __messaging_menu_message_h__ + +#include <gio/gio.h> + +G_BEGIN_DECLS + +#define MESSAGING_MENU_TYPE_MESSAGE (messaging_menu_message_get_type ()) +#define MESSAGING_MENU_MESSAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MESSAGING_MENU_TYPE_MESSAGE, MessagingMenuMessage)) +#define MESSAGING_MENU_MESSAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MESSAGING_MENU_TYPE_MESSAGE, MessagingMenuMessageClass)) +#define MESSAGING_MENU_IS_MESSAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MESSAGING_MENU_TYPE_MESSAGE)) +#define MESSAGING_MENU_IS_MESSAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MESSAGING_MENU_TYPE_MESSAGE)) +#define MESSAGING_MENU_MESSAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MESSAGING_MENU_TYPE_MESSAGE, MessagingMenuMessageClass)) + +typedef struct _MessagingMenuMessage MessagingMenuMessage; + +GType messaging_menu_message_get_type (void) G_GNUC_CONST; + +MessagingMenuMessage * messaging_menu_message_new (const gchar *id, + GIcon *icon, + const gchar *title, + const gchar *subtitle, + const gchar *body, + gint64 time); + +const gchar * messaging_menu_message_get_id (MessagingMenuMessage *msg); + +GIcon * messaging_menu_message_get_icon (MessagingMenuMessage *msg); + +const gchar * messaging_menu_message_get_title (MessagingMenuMessage *msg); + +const gchar * messaging_menu_message_get_subtitle (MessagingMenuMessage *msg); + +const gchar * messaging_menu_message_get_body (MessagingMenuMessage *msg); + +gint64 messaging_menu_message_get_time (MessagingMenuMessage *msg); + +gboolean messaging_menu_message_get_draws_attention (MessagingMenuMessage *msg); + +void messaging_menu_message_set_draws_attention (MessagingMenuMessage *msg, + gboolean draws_attention); + +G_END_DECLS + +#endif diff --git a/libmessaging-menu/messaging-menu.h b/libmessaging-menu/messaging-menu.h index 6c405c7..929b229 100644 --- a/libmessaging-menu/messaging-menu.h +++ b/libmessaging-menu/messaging-menu.h @@ -20,129 +20,6 @@ #ifndef __messaging_menu_h__ #define __messaging_menu_h__ -#include <gio/gio.h> - -G_BEGIN_DECLS - -#define MESSAGING_MENU_TYPE_APP messaging_menu_app_get_type() -#define MESSAGING_MENU_APP(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), MESSAGING_MENU_TYPE_APP, MessagingMenuApp)) -#define MESSAGING_MENU_APP_CLASS(c) (G_TYPE_CHECK_CLASS_CAST ((c), MESSAGING_MENU_TYPE_APP, MessagingMenuAppClass)) -#define MESSAGING_MENU_IS_APP(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), MESSAGING_MENU_TYPE_APP)) - -/** - * MessagingMenuStatus: - * @MESSAGING_MENU_STATUS_AVAILABLE: available - * @MESSAGING_MENU_STATUS_AWAY: away - * @MESSAGING_MENU_STATUS_BUSY: busy - * @MESSAGING_MENU_STATUS_INVISIBLE: invisible - * @MESSAGING_MENU_STATUS_OFFLINE: offline - * - * An enumeration for the possible chat statuses the messaging menu can be in. - */ -typedef enum { - MESSAGING_MENU_STATUS_AVAILABLE, - MESSAGING_MENU_STATUS_AWAY, - MESSAGING_MENU_STATUS_BUSY, - MESSAGING_MENU_STATUS_INVISIBLE, - MESSAGING_MENU_STATUS_OFFLINE -} MessagingMenuStatus; - - -typedef GObjectClass MessagingMenuAppClass; -typedef struct _MessagingMenuApp MessagingMenuApp; - -GType messaging_menu_app_get_type (void) G_GNUC_CONST; - -MessagingMenuApp * messaging_menu_app_new (const gchar *desktop_id); - -void messaging_menu_app_register (MessagingMenuApp *app); -void messaging_menu_app_unregister (MessagingMenuApp *app); - -void messaging_menu_app_set_status (MessagingMenuApp *app, - MessagingMenuStatus status); - -void messaging_menu_app_insert_source (MessagingMenuApp *app, - gint position, - const gchar *id, - GIcon *icon, - const gchar *label); - -void messaging_menu_app_append_source (MessagingMenuApp *app, - const gchar *id, - GIcon *icon, - const gchar *label); - -void messaging_menu_app_insert_source_with_count (MessagingMenuApp *app, - gint position, - const gchar *id, - GIcon *icon, - const gchar *label, - guint count); - -void messaging_menu_app_append_source_with_count (MessagingMenuApp *app, - const gchar *id, - GIcon *icon, - const gchar *label, - guint count); - -void messaging_menu_app_insert_source_with_time (MessagingMenuApp *app, - gint position, - const gchar *id, - GIcon *icon, - const gchar *label, - gint64 time); - -void messaging_menu_app_append_source_with_time (MessagingMenuApp *app, - const gchar *id, - GIcon *icon, - const gchar *label, - gint64 time); - -void messaging_menu_app_append_source_with_string (MessagingMenuApp *app, - const gchar *id, - GIcon *icon, - const gchar *label, - const gchar *str); - -void messaging_menu_app_insert_source_with_string (MessagingMenuApp *app, - gint position, - const gchar *id, - GIcon *icon, - const gchar *label, - const gchar *str); - -void messaging_menu_app_remove_source (MessagingMenuApp *app, - const gchar *source_id); - -gboolean messaging_menu_app_has_source (MessagingMenuApp *app, - const gchar *source_id); - -void messaging_menu_app_set_source_label (MessagingMenuApp *app, - const gchar *source_id, - const gchar *label); - -void messaging_menu_app_set_source_icon (MessagingMenuApp *app, - const gchar *source_id, - GIcon *icon); - -void messaging_menu_app_set_source_count (MessagingMenuApp *app, - const gchar *source_id, - guint count); - -void messaging_menu_app_set_source_time (MessagingMenuApp *app, - const gchar *source_id, - gint64 time); - -void messaging_menu_app_set_source_string (MessagingMenuApp *app, - const gchar *source_id, - const gchar *str); - -void messaging_menu_app_draw_attention (MessagingMenuApp *app, - const gchar *source_id); - -void messaging_menu_app_remove_attention (MessagingMenuApp *app, - const gchar *source_id); - -G_END_DECLS +#include "messaging-menu-app.h" #endif diff --git a/src/Makefile.am b/src/Makefile.am index 1df80e5..a7bfe66 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,8 +1,5 @@ -BUILT_SOURCES = EXTRA_DIST = -CLEANFILES = -DISTCLEANFILES = libexec_PROGRAMS = indicator-messages-service @@ -23,19 +20,20 @@ libmessaging_la_SOURCES = \ im-source-menu-item.h \ ido-detail-label.c \ ido-detail-label.h \ - indicator-messages-service.c \ - indicator-messages-service.h dbus-data.h libmessaging_la_CFLAGS = \ $(APPLET_CFLAGS) \ $(COVERAGE_CFLAGS) \ + -I$(top_builddir)/common \ -Wall \ -Wl,-Bsymbolic-functions \ -Wl,-z,defs \ -Wl,--as-needed \ -Werror \ -DG_LOG_DOMAIN=\"Indicator-Messages\" -libmessaging_la_LIBADD = $(APPLET_LIBS) -lm +libmessaging_la_LIBADD = \ + $(top_builddir)/common/libmessaging-common.la \ + $(APPLET_LIBS) -lm libmessaging_la_LDFLAGS = \ $(COVERAGE_LDFLAGS) \ -module -avoid-version @@ -46,8 +44,6 @@ libmessaging_la_LDFLAGS = \ indicator_messages_service_SOURCES = \ messages-service.c \ - indicator-messages-service.c \ - indicator-messages-service.h \ app-section.c \ app-section.h \ dbus-data.h \ @@ -61,6 +57,7 @@ indicator_messages_service_SOURCES = \ indicator_messages_service_CFLAGS = \ $(APPLET_CFLAGS) \ $(COVERAGE_CFLAGS) \ + -I$(top_builddir)/common \ -Wall \ -Wl,-Bsymbolic-functions \ -Wl,-z,defs \ @@ -69,26 +66,11 @@ indicator_messages_service_CFLAGS = \ -DG_LOG_DOMAIN=\"Indicator-Messages\" indicator_messages_service_LDADD = \ + $(top_builddir)/common/libmessaging-common.la \ $(APPLET_LIBS) indicator_messages_service_LDFLAGS = \ $(COVERAGE_LDFLAGS) -indicator-messages-service.c: $(top_srcdir)/src/messages-service.xml - $(AM_V_GEN) gdbus-codegen \ - --interface-prefix com.canonical.indicator.messages. \ - --generate-c-code indicator-messages-service \ - --c-namespace IndicatorMessages \ - $^ -indicator-messages-service.h: indicator-messages-service.c - -BUILT_SOURCES += \ - indicator-messages-service.c \ - indicator-messages-service.h - EXTRA_DIST += \ messages-service.xml - -CLEANFILES += \ - $(BUILT_SOURCES) - diff --git a/src/app-section.c b/src/app-section.c index 6aac52a..19532f2 100644 --- a/src/app-section.c +++ b/src/app-section.c @@ -33,6 +33,7 @@ with this program. If not, see <http://www.gnu.org/licenses/>. #include "dbus-data.h" #include "gmenuutils.h" #include "gactionmuxer.h" +#include "indicator-messages-application.h" struct _AppSectionPrivate { @@ -41,11 +42,14 @@ struct _AppSectionPrivate IndicatorDesktopShortcuts * ids; + GCancellable *app_proxy_cancellable; + IndicatorMessagesApplication *app_proxy; + GMenu *menu; - GMenuModel *source_menu; + GMenu *source_menu; GSimpleActionGroup *static_shortcuts; - GActionGroup *source_actions; + GSimpleActionGroup *source_actions; GActionMuxer *muxer; gboolean draws_attention; @@ -89,19 +93,6 @@ static void launch_action_change_state (GSimpleAction *action, gpointer user_data); static void app_section_set_app_info (AppSection *self, GDesktopAppInfo *appinfo); -static gboolean any_action_draws_attention (GActionGroup *group, - const gchar *ignored_action); -static void action_added (GActionGroup *group, - const gchar *action_name, - gpointer user_data); -static void action_state_changed (GActionGroup *group, - const gchar *action_name, - GVariant *value, - gpointer user_data); -static void action_removed (GActionGroup *group, - const gchar *action_name, - gpointer user_data); -static gboolean action_draws_attention (GVariant *state); static void desktop_file_changed_cb (GFileMonitor *monitor, GFile *file, GFile *other_file, @@ -169,6 +160,7 @@ static void app_section_init (AppSection *self) { AppSectionPrivate *priv; + GMenuItem *item; self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, APP_SECTION_TYPE, @@ -178,10 +170,19 @@ app_section_init (AppSection *self) priv->appinfo = NULL; priv->menu = g_menu_new (); + + priv->source_menu = g_menu_new (); + item = g_menu_item_new_section (NULL, G_MENU_MODEL (priv->source_menu)); + g_menu_item_set_attribute (item, "action-namespace", "s", "source"); + g_menu_append_item (priv->menu, item); + g_object_unref (item); + priv->static_shortcuts = g_simple_action_group_new (); + priv->source_actions = g_simple_action_group_new (); priv->muxer = g_action_muxer_new (); g_action_muxer_insert (priv->muxer, NULL, G_ACTION_GROUP (priv->static_shortcuts)); + g_action_muxer_insert (priv->muxer, "source", G_ACTION_GROUP (priv->source_actions)); priv->draws_attention = FALSE; @@ -248,32 +249,30 @@ app_section_dispose (GObject *object) AppSection * self = APP_SECTION(object); AppSectionPrivate * priv = self->priv; + if (priv->app_proxy_cancellable) { + g_cancellable_cancel (priv->app_proxy_cancellable); + g_clear_object (&priv->app_proxy_cancellable); + } + if (priv->desktop_file_monitor) { g_signal_handlers_disconnect_by_func (priv->desktop_file_monitor, desktop_file_changed_cb, self); g_clear_object (&priv->desktop_file_monitor); } + g_clear_object (&priv->app_proxy); + g_clear_object (&priv->menu); + g_clear_object (&priv->source_menu); g_clear_object (&priv->static_shortcuts); + g_clear_object (&priv->source_actions); if (priv->name_watch_id) { g_bus_unwatch_name (priv->name_watch_id); priv->name_watch_id = 0; } - if (priv->source_actions) { - g_action_muxer_remove (priv->muxer, "source"); - g_object_disconnect (priv->source_actions, - "any_signal::action-added", action_added, self, - "any_signal::action-state-changed", action_state_changed, self, - "any_signal::action-removed", action_removed, self, - NULL); - g_clear_object (&priv->source_actions); - } - g_clear_object (&priv->muxer); - g_clear_object (&priv->source_menu); g_clear_object (&priv->ids); g_clear_object (&priv->appinfo); @@ -429,6 +428,11 @@ app_section_update_menu (AppSection *self) g_free(name); } + item = g_menu_item_new_section (NULL, G_MENU_MODEL (priv->source_menu)); + g_menu_item_set_attribute (item, "action-namespace", "s", "source"); + g_menu_append_item (priv->menu, item); + g_object_unref (item); + keyfile = g_file_new_for_path (g_desktop_app_info_get_filename (priv->appinfo)); g_file_load_contents_async (keyfile, NULL, keyfile_loaded, self); @@ -564,49 +568,242 @@ app_section_get_draws_attention (AppSection *self) void app_section_clear_draws_attention (AppSection *self) { - AppSectionPrivate * priv = self->priv; - gchar **action_names; + self->priv->draws_attention = FALSE; + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DRAWS_ATTENTION]); +} + +static void +application_vanished (GDBusConnection *bus, + const gchar *name, + gpointer user_data) +{ + AppSection *self = user_data; + + app_section_unset_object_path (self); +} + +static void +update_draws_attention (AppSection *self) +{ + AppSectionPrivate *priv = self->priv; + gchar **actions; gchar **it; + gboolean draws_attention = FALSE; + + actions = g_action_group_list_actions (G_ACTION_GROUP (priv->source_actions)); + + for (it = actions; *it; it++) { + GVariant *state; + + state = g_action_group_get_action_state (G_ACTION_GROUP (priv->source_actions), *it); + if (state) { + gboolean b; + g_variant_get (state, "(uxsb)", NULL, NULL, NULL, &b); + draws_attention = b || draws_attention; + g_variant_unref (state); + } + + if (draws_attention) + break; + } + + if (draws_attention != priv->draws_attention) { + priv->draws_attention = draws_attention; + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DRAWS_ATTENTION]); + } + + g_strfreev (actions); +} + +static void +remove_source (AppSection *self, + const gchar *id) +{ + AppSectionPrivate *priv = self->priv; + guint n_items; + guint i; + + n_items = g_menu_model_get_n_items (G_MENU_MODEL (priv->source_menu)); + for (i = 0; i < n_items; i++) { + gchar *action; + gboolean found = FALSE; + + if (g_menu_model_get_item_attribute (G_MENU_MODEL (priv->source_menu), i, + G_MENU_ATTRIBUTE_ACTION, "s", &action)) { + found = g_str_equal (action, id); + g_free (action); + } + + if (found) { + g_menu_remove (priv->source_menu, i); + break; + } + } + + g_simple_action_group_remove (priv->source_actions, id); + update_draws_attention (self); +} + +static void +source_action_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + AppSection *self = APP_SECTION (user_data); + AppSectionPrivate *priv = APP_SECTION (user_data)->priv; + + g_return_if_fail (priv->app_proxy != NULL); + + indicator_messages_application_call_activate_source (priv->app_proxy, + g_action_get_name (G_ACTION (action)), + priv->app_proxy_cancellable, + NULL, NULL); + + remove_source (self, g_action_get_name (G_ACTION (action))); +} + +static void +sources_listed (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + AppSection *self = user_data; + AppSectionPrivate *priv = self->priv; + GVariant *sources = NULL; + GError *error = NULL; + GVariantIter iter; + const gchar *id; + const gchar *label; + const gchar *iconstr; + guint32 count; + gint64 time; + const gchar *string; + gboolean draws_attention; - if (priv->source_actions == NULL) + if (!indicator_messages_application_call_list_sources_finish (INDICATOR_MESSAGES_APPLICATION (source_object), + &sources, result, &error)) + { + g_warning ("could not fetch the list of sources: %s", error->message); + g_error_free (error); return; + } - action_names = g_action_group_list_actions (priv->source_actions); + g_menu_clear (priv->source_menu); + g_simple_action_group_clear (priv->source_actions); + priv->draws_attention = FALSE; - for (it = action_names; *it; it++) { + g_variant_iter_init (&iter, sources); + while (g_variant_iter_next (&iter, "(&s&s&sux&sb)", &id, &label, &iconstr, + &count, &time, &string, &draws_attention)) + { GVariant *state; + GSimpleAction *action; + GMenuItem *item; - state = g_action_group_get_action_state (priv->source_actions, *it); - if (!state) - continue; + state = g_variant_new ("(uxsb)", count, time, string, draws_attention); + action = g_simple_action_new_stateful (id, NULL, state); + g_signal_connect (action, "activate", G_CALLBACK (source_action_activated), self); + g_simple_action_group_insert (priv->source_actions, G_ACTION (action)); - /* clear draws-attention while preserving other state */ - if (action_draws_attention (state)) { - guint32 count; - gint64 time; - const gchar *str; - GVariant *new_state; + item = g_menu_item_new (label, id); + g_menu_item_set_attribute (item, "x-canonical-type", "s", "ImSourceMenuItem"); + g_menu_append_item (priv->source_menu, item); - g_variant_get (state, "(ux&sb)", &count, &time, &str, NULL); + priv->draws_attention = priv->draws_attention || draws_attention; - new_state = g_variant_new ("(uxsb)", count, time, str, FALSE); - g_action_group_change_action_state (priv->source_actions, *it, new_state); - } + g_object_unref (item); + g_object_unref (action); + } + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DRAWS_ATTENTION]); + + g_variant_unref (sources); +} + +static void +source_added (IndicatorMessagesApplication *app, + const gchar *id, + const gchar *label, + const gchar *iconstr, + guint count, + gint64 time, + const gchar *string, + gboolean draws_attention, + gpointer user_data) +{ + AppSection *self = user_data; + AppSectionPrivate *priv = self->priv; + GVariant *state; + GSimpleAction *action; + + /* TODO put label and icon into the action as well */ - g_variant_unref (state); + state = g_variant_new ("(uxsb)", count, time, string, draws_attention); + action = g_simple_action_new_stateful (id, NULL, state); + + g_simple_action_group_insert (priv->source_actions, G_ACTION (action)); + + if (draws_attention && !priv->draws_attention) { + priv->draws_attention = TRUE; + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DRAWS_ATTENTION]); } - g_strfreev (action_names); + g_object_unref (action); +} +static void +source_changed (IndicatorMessagesApplication *app, + const gchar *id, + const gchar *label, + const gchar *iconstr, + guint count, + gint64 time, + const gchar *string, + gboolean draws_attention, + gpointer user_data) +{ + AppSection *self = user_data; + AppSectionPrivate *priv = self->priv; + GVariant *state; + + /* TODO put label and icon into the action as well */ + + state = g_variant_new ("(uxsb)", count, time, string, draws_attention); + g_action_group_change_action_state (G_ACTION_GROUP (priv->source_actions), id, state); + + update_draws_attention (self); } static void -application_vanished (GDBusConnection *bus, - const gchar *name, - gpointer user_data) +source_removed (IndicatorMessagesApplication *app, + const gchar *id, + gpointer user_data) { AppSection *self = user_data; - app_section_unset_object_path (self); + remove_source (self, id); +} + +static void +app_proxy_created (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + AppSectionPrivate *priv = APP_SECTION (user_data)->priv; + GError *error = NULL; + + priv->app_proxy = indicator_messages_application_proxy_new_finish (result, &error); + if (!priv->app_proxy) { + g_warning ("could not create application proxy: %s", error->message); + g_error_free (error); + return; + } + + indicator_messages_application_call_list_sources (priv->app_proxy, priv->app_proxy_cancellable, + sources_listed, user_data); + + g_signal_connect (priv->app_proxy, "source-added", G_CALLBACK (source_added), user_data); + g_signal_connect (priv->app_proxy, "source-changed", G_CALLBACK (source_changed), user_data); + g_signal_connect (priv->app_proxy, "source-removed", G_CALLBACK (source_removed), user_data); } /* @@ -627,27 +824,20 @@ app_section_set_object_path (AppSection *self, const gchar *object_path) { AppSectionPrivate *priv = self->priv; - GMenuItem *item; g_object_freeze_notify (G_OBJECT (self)); app_section_unset_object_path (self); - priv->source_actions = G_ACTION_GROUP (g_dbus_action_group_get (bus, bus_name, object_path)); - g_action_muxer_insert (priv->muxer, "source", priv->source_actions); - - priv->draws_attention = any_action_draws_attention (priv->source_actions, NULL); - g_object_connect (priv->source_actions, - "signal::action-added", action_added, self, - "signal::action-state-changed", action_state_changed, self, - "signal::action-removed", action_removed, self, - NULL); + priv->app_proxy_cancellable = g_cancellable_new (); + indicator_messages_application_proxy_new (bus, + G_DBUS_PROXY_FLAGS_NONE, + bus_name, + object_path, + priv->app_proxy_cancellable, + app_proxy_created, + self); - priv->source_menu = G_MENU_MODEL (g_dbus_menu_model_get (bus, bus_name, object_path)); - - item = g_menu_item_new_section (NULL, priv->source_menu); - g_menu_item_set_attribute (item, "action-namespace", "s", "source"); - g_menu_append_item (priv->menu, item); - g_object_unref (item); + priv->draws_attention = FALSE; priv->name_watch_id = g_bus_watch_name_on_connection (bus, bus_name, 0, NULL, application_vanished, @@ -675,26 +865,19 @@ app_section_unset_object_path (AppSection *self) { AppSectionPrivate *priv = self->priv; + if (priv->app_proxy_cancellable) { + g_cancellable_cancel (priv->app_proxy_cancellable); + g_clear_object (&priv->app_proxy_cancellable); + } + g_clear_object (&priv->app_proxy); + if (priv->name_watch_id) { g_bus_unwatch_name (priv->name_watch_id); priv->name_watch_id = 0; } - if (priv->source_actions) { - g_object_disconnect (priv->source_actions, - "any_signal::action-added", action_added, self, - "any_signal::action-state-changed", action_state_changed, self, - "any_signal::action-removed", action_removed, self, - NULL); - g_clear_object (&priv->source_actions); - } - - if (priv->source_menu) { - /* the last menu item points is linked to the app's menumodel */ - gint n_items = g_menu_model_get_n_items (G_MENU_MODEL (priv->menu)); - g_menu_remove (priv->menu, n_items -1); - g_clear_object (&priv->source_menu); - } + g_simple_action_group_clear (priv->source_actions); + g_menu_clear (priv->source_menu); priv->draws_attention = FALSE; g_clear_pointer (&priv->chat_status, g_free); @@ -708,85 +891,6 @@ app_section_unset_object_path (AppSection *self) "launch", g_variant_new_boolean (FALSE)); } -static gboolean -action_draws_attention (GVariant *state) -{ - gboolean attention; - - if (state && g_variant_is_of_type (state, G_VARIANT_TYPE ("(uxsb)"))) - g_variant_get_child (state, 3, "b", &attention); - else - attention = FALSE; - - return attention; -} - -static gboolean -any_action_draws_attention (GActionGroup *group, - const gchar *ignored_action) -{ - gchar **actions; - gchar **it; - gboolean attention = FALSE; - - actions = g_action_group_list_actions (group); - - for (it = actions; *it && !attention; it++) { - GVariant *state; - - if (ignored_action && g_str_equal (ignored_action, *it)) - continue; - - state = g_action_group_get_action_state (group, *it); - if (state) { - attention = action_draws_attention (state); - g_variant_unref (state); - } - } - - g_strfreev (actions); - return attention; -} - -static void -action_added (GActionGroup *group, - const gchar *action_name, - gpointer user_data) -{ - AppSection *self = user_data; - GVariant *state; - - state = g_action_group_get_action_state (group, action_name); - if (state) { - self->priv->draws_attention |= action_draws_attention (state); - g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DRAWS_ATTENTION]); - g_variant_unref (state); - } -} - -static void -action_state_changed (GActionGroup *group, - const gchar *action_name, - GVariant *value, - gpointer user_data) -{ - AppSection *self = user_data; - - self->priv->draws_attention = any_action_draws_attention (group, NULL); - g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DRAWS_ATTENTION]); -} - -static void -action_removed (GActionGroup *group, - const gchar *action_name, - gpointer user_data) -{ - AppSection *self = user_data; - - self->priv->draws_attention = any_action_draws_attention (group, action_name); - g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DRAWS_ATTENTION]); -} - gboolean app_section_get_uses_chat_status (AppSection *self) { diff --git a/src/dbus-data.h b/src/dbus-data.h index 64747a9..ceeb546 100644 --- a/src/dbus-data.h +++ b/src/dbus-data.h @@ -3,7 +3,7 @@ #define __DBUS_DATA_H__ 1 #define INDICATOR_MESSAGES_DBUS_NAME "com.canonical.indicator.messages" -#define INDICATOR_MESSAGES_DBUS_OBJECT "/com/canonical/indicator/messages/menu" +#define INDICATOR_MESSAGES_DBUS_OBJECT "/com/canonical/indicator/messages" #define INDICATOR_MESSAGES_DBUS_SERVICE_OBJECT "/com/canonical/indicator/messages/service" #define INDICATOR_MESSAGES_DBUS_SERVICE_INTERFACE "com.canonical.indicator.messages.service" diff --git a/src/gactionmuxer.c b/src/gactionmuxer.c index 2b1d11a..0f3cda4 100644 --- a/src/gactionmuxer.c +++ b/src/gactionmuxer.c @@ -483,3 +483,11 @@ g_action_muxer_remove (GActionMuxer *muxer, g_clear_object (&muxer->global_actions); } +GActionGroup * +g_action_muxer_get_group (GActionMuxer *muxer, + const gchar *prefix) +{ + g_return_val_if_fail (G_IS_ACTION_MUXER (muxer), NULL); + + return prefix ? g_hash_table_lookup (muxer->groups, prefix) : muxer->global_actions; +} diff --git a/src/gactionmuxer.h b/src/gactionmuxer.h index 5c5e839..caf9ec7 100644 --- a/src/gactionmuxer.h +++ b/src/gactionmuxer.h @@ -40,5 +40,8 @@ void g_action_muxer_insert (GActionMuxer *muxer, void g_action_muxer_remove (GActionMuxer *muxer, const gchar *prefix); +GActionGroup * g_action_muxer_get_group (GActionMuxer *muxer, + const gchar *prefix); + #endif diff --git a/src/messages-service.c b/src/messages-service.c index b36a0a2..81b7f0b 100644 --- a/src/messages-service.c +++ b/src/messages-service.c @@ -28,12 +28,12 @@ with this program. If not, see <http://www.gnu.org/licenses/>. #include <gio/gdesktopappinfo.h> #include <glib/gi18n.h> -#include "app-section.h" #include "dbus-data.h" #include "gactionmuxer.h" #include "gsettingsstrv.h" #include "gmenuutils.h" #include "indicator-messages-service.h" +#include "indicator-messages-application.h" #define NUM_STATUSES 5 @@ -44,7 +44,8 @@ static GSimpleActionGroup *actions; static GActionMuxer *action_muxer; static GMenu *toplevel_menu; static GMenu *menu; -static GMenuModel *chat_section; +static GMenu *messages_section; +static GMenu *sources_section; static GSettings *settings; static gboolean draws_attention; static const gchar *global_status[6]; /* max 5: available, away, busy, invisible, offline */ @@ -82,358 +83,172 @@ indicator_messages_get_icon_name () return iconstr; } -static void -indicator_messages_update_icon () -{ - GSimpleAction *messages; - gchar *icon; - - messages = G_SIMPLE_ACTION (g_simple_action_group_lookup (actions, "messages")); - g_return_if_fail (messages != NULL); - - icon = indicator_messages_get_icon_name (); - g_simple_action_set_state (messages, g_variant_new_string (icon)); - - g_free (icon); -} - -static gchar * -g_app_info_get_simple_id (GAppInfo *appinfo) -{ - const gchar *id; - - id = g_app_info_get_id (appinfo); - if (!id) - return NULL; - - if (g_str_has_suffix (id, ".desktop")) - return g_strndup (id, strlen (id) - 8); - else - return g_strdup (id); -} - -static void -actions_changed (GObject *object, - GParamSpec *pspec, - gpointer user_data) +static void +service_shutdown (IndicatorService * service, gpointer user_data) { - AppSection *section = APP_SECTION (object); - gchar *id; - GActionGroup *actions; - - id = g_app_info_get_simple_id (app_section_get_app_info (section)); - actions = app_section_get_actions (section); - - g_action_muxer_insert (action_muxer, id, actions); - g_free (id); -} - + GMainLoop *mainloop = user_data; -static gboolean -app_section_draws_attention (gpointer key, - gpointer value, - gpointer user_data) -{ - AppSection *section = value; - return app_section_get_draws_attention (section); + g_warning("Shutting down service!"); + g_main_loop_quit(mainloop); } static void -draws_attention_changed (GObject *object, - GParamSpec *pspec, - gpointer user_data) -{ - GSimpleAction *clear; - - clear = G_SIMPLE_ACTION (g_simple_action_group_lookup (actions, "clear")); - g_return_if_fail (clear != NULL); - - draws_attention = g_hash_table_find (applications, app_section_draws_attention, NULL) != NULL; - - g_simple_action_set_enabled (clear, draws_attention); - - indicator_messages_update_icon (); -} - -static gboolean -app_section_uses_chat (gpointer key, - gpointer value, +clear_action_activate (GSimpleAction *simple, + GVariant *param, gpointer user_data) { - AppSection *section = value; - return app_section_get_uses_chat_status (section); + /* TODO */ } static void -update_chat_section () +status_action_activate (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) { - gboolean show_chat; - GMenuModel *first_section; - - show_chat = g_hash_table_find (applications, app_section_uses_chat, NULL) != NULL; - - first_section = g_menu_model_get_item_link (G_MENU_MODEL (menu), 0, G_MENU_LINK_SECTION); - if (first_section == chat_section) { - if (!show_chat) - g_menu_remove (menu, 0); - } - else { - if (show_chat) - g_menu_insert_section (menu, 0, NULL, chat_section); - } + const gchar *status; - if (first_section != NULL) - g_object_unref (first_section); + status = g_variant_get_string (parameter, NULL); - indicator_messages_update_icon (); + indicator_messages_service_emit_status_changed (messages_service, status); } static void -uses_chat_status_changed (GObject *object, - GParamSpec *pspec, - gpointer user_data) +sources_listed (GObject *source_object, + GAsyncResult *result, + gpointer user_data) { - update_chat_section (); -} - -static gboolean -strv_contains (const gchar **strv, - const gchar *needle) -{ - const gchar **it; - - it = strv; - while (*it != NULL && !g_str_equal (*it, needle)) - it++; - - return *it != NULL; -} + gchar *app_id = user_data; + GVariant *sources; + GVariantIter iter; + const gchar *id; + const gchar *label; + const gchar *iconstr; + guint32 count; + gint64 time; + const gchar *string; + gboolean draws_attention; + GError *error = NULL; -static void -update_chat_status () -{ - GHashTableIter iter; - AppSection *section; - int pos; - GAction *status; - - for (pos = 0; pos < G_N_ELEMENTS (global_status); pos++) - global_status[pos] = NULL; - - pos = 0; - g_hash_table_iter_init (&iter, applications); - while (g_hash_table_iter_next (&iter, NULL, (gpointer) §ion) && - pos < G_N_ELEMENTS (global_status)) + if (!indicator_messages_application_call_list_sources_finish (INDICATOR_MESSAGES_APPLICATION (source_object), + &sources, result, &error)) { - const gchar *status_str = NULL; - - status_str = app_section_get_status (section); - if (status_str != NULL && !strv_contains (global_status, status_str)) - global_status[pos++] = status_str; - } - - if (pos == 0) - global_status[0] = "offline"; - - status = g_simple_action_group_lookup (actions, "status"); - g_return_if_fail (status != NULL); - - g_simple_action_set_state (G_SIMPLE_ACTION (status), g_variant_new_strv (global_status, -1)); - - indicator_messages_update_icon (); -} - -static void -chat_status_changed (GObject *object, - GParamSpec *pspec, - gpointer user_data) -{ - update_chat_status (); -} - -static void -remove_section (AppSection *section, - const gchar *id) -{ - int pos = g_menu_find_section (menu, app_section_get_menu (section)); - if (pos >= 0) - g_menu_remove (menu, pos); - g_action_muxer_remove (action_muxer, id); - - g_signal_handlers_disconnect_by_func (section, actions_changed, NULL); - g_signal_handlers_disconnect_by_func (section, draws_attention_changed, NULL); - g_signal_handlers_disconnect_by_func (section, uses_chat_status_changed, NULL); - g_signal_handlers_disconnect_by_func (section, chat_status_changed, NULL); - g_signal_handlers_disconnect_by_func (section, remove_section, NULL); - - g_hash_table_remove (applications, id); - - if (g_hash_table_size (applications) == 0 && - g_menu_model_get_n_items (G_MENU_MODEL (toplevel_menu)) == 1) { - g_menu_remove (toplevel_menu, 0); - } - - update_chat_status (); - update_chat_section (); -} - -static AppSection * -add_application (const gchar *desktop_id) -{ - GDesktopAppInfo *appinfo; - gchar *id; - AppSection *section; - - appinfo = g_desktop_app_info_new (desktop_id); - if (!appinfo) { - g_warning ("could not add '%s', there's no desktop file with that id", desktop_id); - return NULL; - } - - id = g_app_info_get_simple_id (G_APP_INFO (appinfo)); - section = g_hash_table_lookup (applications, id); - - if (!section) { - GMenuItem *menuitem; - - section = app_section_new(appinfo); - g_hash_table_insert (applications, g_strdup (id), section); - - g_action_muxer_insert (action_muxer, id, app_section_get_actions (section)); - g_signal_connect (section, "notify::actions", - G_CALLBACK (actions_changed), NULL); - g_signal_connect (section, "notify::draws-attention", - G_CALLBACK (draws_attention_changed), NULL); - g_signal_connect (section, "notify::uses-chat-status", - G_CALLBACK (uses_chat_status_changed), NULL); - g_signal_connect (section, "notify::chat-status", - G_CALLBACK (chat_status_changed), NULL); - g_signal_connect_data (section, "destroy", - G_CALLBACK (remove_section), - g_strdup (id), - (GClosureNotify) g_free, - 0); - - /* TODO insert it at the right position (alphabetically by application name) */ - menuitem = g_menu_item_new_section (NULL, app_section_get_menu (section)); - g_menu_item_set_attribute (menuitem, "action-namespace", "s", id); - g_menu_insert_item (menu, g_menu_model_get_n_items (G_MENU_MODEL (menu)) -1, menuitem); - g_object_unref (menuitem); + g_warning ("could not fetch the list of sources: %s", error->message); + g_error_free (error); + g_free (app_id); + return; } - if (g_menu_model_get_n_items (G_MENU_MODEL (toplevel_menu)) == 0) { - GMenuItem *header; - - header = g_menu_item_new (NULL, "messages"); - g_menu_item_set_submenu (header, G_MENU_MODEL (menu)); - g_menu_item_set_attribute (header, "x-canonical-accessible-description", "s", _("Messages")); - g_menu_append_item (toplevel_menu, header); - - g_object_unref (header); + g_variant_iter_init (&iter, sources); + while (g_variant_iter_next (&iter, "(&s&s&sux&sb)", &id, &label, &iconstr, &count, + &time, &string, &draws_attention)) + { + GMenuItem *item; + GActionGroup *app_actions; + GSimpleAction *action; + gchar *action_name; + + app_actions = g_action_muxer_get_group (action_muxer, app_id); + g_assert (app_actions); + action = g_simple_action_new_stateful (id, NULL, g_variant_new_uint32 (count)); + g_simple_action_group_insert (G_SIMPLE_ACTION_GROUP (app_actions), G_ACTION (action)); + + action_name = g_strconcat (app_id, ".", id, NULL); + item = g_menu_item_new (label, action_name); + g_menu_item_set_attribute (item, "x-canonical-type", "s", "com.canonical.indicator.messages.sourceitem"); + if (iconstr && *iconstr) + g_menu_item_set_attribute (item, "x-canonical-icon", "s", iconstr); + g_menu_append_item (sources_section, item); + + g_object_unref (action); + g_free (action_name); + g_object_unref (item); } - g_free (id); - g_object_unref (appinfo); - return section; + g_variant_unref (sources); + g_free (app_id); } static void -remove_application (const char *desktop_id) +messages_listed (GObject *source_object, + GAsyncResult *result, + gpointer user_data) { - GDesktopAppInfo *appinfo; - gchar *id; - AppSection *section; - - appinfo = g_desktop_app_info_new (desktop_id); - if (!appinfo) { - g_warning ("could not remove '%s', there's no desktop file with that id", desktop_id); + gchar *app_id = user_data; + GVariant *messages; + GError *error = NULL; + GVariantIter iter; + const gchar *id; + const gchar *iconstr; + const gchar *title; + const gchar *subtitle; + const gchar *body; + gint64 time; + gboolean draws_attention; + + if (!indicator_messages_application_call_list_messages_finish (INDICATOR_MESSAGES_APPLICATION (source_object), + &messages, result, &error)) + { + g_warning ("could not fetch the list of messages: %s", error->message); + g_error_free (error); + g_free (app_id); return; } - id = g_app_info_get_simple_id (G_APP_INFO (appinfo)); - - section = g_hash_table_lookup (applications, id); - if (section) { - remove_section (section, id); - } - else { - g_warning ("could not remove '%s', it's not registered", desktop_id); - } - - g_free (id); - g_object_unref (appinfo); -} - -/* This function turns a specific desktop id into a menu - item and registers it appropriately with everyone */ -static gboolean -build_launcher (gpointer data) -{ - gchar *desktop_id = data; - - add_application (desktop_id); - - g_free (desktop_id); - return FALSE; -} - -/* This function goes through all the launchers that we're - supposed to be grabbing and decides to show turn them - into menu items or not. It doens't do the work, but it - makes the decision. */ -static gboolean -build_launchers (gpointer data) -{ - gchar **applications = g_settings_get_strv (settings, "applications"); - gchar **app; - - g_return_val_if_fail (applications != NULL, FALSE); - - for (app = applications; *app; app++) + g_variant_iter_init (&iter, messages); + while (g_variant_iter_next (&iter, "(&s&s&s&s&sxb)", &id, &iconstr, &title, &subtitle, &body, + &time, &draws_attention)) { - g_idle_add(build_launcher, g_strdup (*app)); + GMenuItem *item; + GActionGroup *app_actions; + GSimpleAction *action; + gchar *action_name; + + app_actions = g_action_muxer_get_group (action_muxer, app_id); + g_assert (app_actions); + action = g_simple_action_new (id, NULL); + g_simple_action_group_insert (G_SIMPLE_ACTION_GROUP (app_actions), G_ACTION (action)); + + action_name = g_strconcat (app_id, ".", id, NULL); + item = g_menu_item_new (title, action_name); + g_menu_item_set_attribute (item, "x-canonical-type", "s", "com.canonical.indicator.messages.messageitem"); + g_menu_item_set_attribute (item, "x-canonical-message-id", "s", id); + g_menu_item_set_attribute (item, "x-canonical-subtitle", "s", subtitle); + g_menu_item_set_attribute (item, "x-canonical-text", "s", body); + g_menu_item_set_attribute (item, "x-canonical-time", "x", time); + if (iconstr && *iconstr) + g_menu_item_set_attribute (item, "x-canonical-icon", "s", iconstr); + g_menu_append_item (messages_section, item); + + g_object_unref (action); + g_free (action_name); + g_object_unref (item); } - g_strfreev (applications); - return FALSE; -} - -static void -service_shutdown (IndicatorService * service, gpointer user_data) -{ - GMainLoop *mainloop = user_data; - - g_warning("Shutting down service!"); - g_main_loop_quit(mainloop); + g_variant_unref (messages); + g_free (app_id); } static void -app_section_remove_attention (gpointer key, - gpointer value, - gpointer user_data) +app_proxy_created (GObject *source_object, + GAsyncResult *result, + gpointer user_data) { - AppSection *section = value; - app_section_clear_draws_attention (section); -} - -static void -clear_action_activate (GSimpleAction *simple, - GVariant *param, - gpointer user_data) -{ - g_hash_table_foreach (applications, app_section_remove_attention, NULL); -} + gchar *desktop_id = user_data; + IndicatorMessagesApplication *app_proxy; + GError *error = NULL; -static void -status_action_activate (GSimpleAction *action, - GVariant *parameter, - gpointer user_data) -{ - const gchar *status; + app_proxy = indicator_messages_application_proxy_new_finish (result, &error); + if (!app_proxy) { + g_warning ("could not create application proxy: %s", error->message); + g_error_free (error); + return; + } - status = g_variant_get_string (parameter, NULL); + /* hash table takes ownership of desktop_id and app_proxy */ + g_hash_table_insert (applications, desktop_id, app_proxy); - indicator_messages_service_emit_status_changed (messages_service, status); + indicator_messages_application_call_list_sources (app_proxy, NULL, sources_listed, g_strdup (desktop_id)); + indicator_messages_application_call_list_messages (app_proxy, NULL, messages_listed, g_strdup (desktop_id)); } static void @@ -443,21 +258,33 @@ register_application (IndicatorMessagesService *service, const gchar *menu_path, gpointer user_data) { - AppSection *section; GDBusConnection *bus; const gchar *sender; + GSimpleActionGroup *app_actions; - section = add_application (desktop_id); - if (!section) + if (g_hash_table_lookup (applications, desktop_id)) { + g_warning ("application with id '%s' already exists", desktop_id); + g_dbus_method_invocation_return_dbus_error (invocation, + "com.canonical.indicator.messages.ApplicationAlreadyRegistered", + "another application is already registered with this id"); return; + } bus = g_dbus_interface_skeleton_get_connection (G_DBUS_INTERFACE_SKELETON (service)); sender = g_dbus_method_invocation_get_sender (invocation); - app_section_set_object_path (section, bus, sender, menu_path); + indicator_messages_application_proxy_new (bus, G_DBUS_PROXY_FLAGS_NONE, + sender, menu_path, NULL, + app_proxy_created, g_strdup (desktop_id)); + + app_actions = g_simple_action_group_new (); + g_action_muxer_insert (action_muxer, desktop_id, G_ACTION_GROUP (app_actions)); + g_settings_strv_append_unique (settings, "applications", desktop_id); indicator_messages_service_complete_register_application (service, invocation); + + g_object_unref (app_actions); } static void @@ -466,44 +293,15 @@ unregister_application (IndicatorMessagesService *service, const gchar *desktop_id, gpointer user_data) { - remove_application (desktop_id); - g_settings_strv_remove (settings, "applications", desktop_id); + if (g_hash_table_remove (applications, desktop_id)) { - indicator_messages_service_complete_unregister_application (service, invocation); -} + /* TODO remove menu items that refer to this application */ + g_action_muxer_remove (action_muxer, desktop_id); -static void -set_status (IndicatorMessagesService *service, - GDBusMethodInvocation *invocation, - const gchar *desktop_id, - const gchar *status_str, - gpointer user_data) -{ - GDesktopAppInfo *appinfo; - gchar *id; - AppSection *section; - - g_return_if_fail (g_str_equal (status_str, "available") || - g_str_equal (status_str, "away")|| - g_str_equal (status_str, "busy") || - g_str_equal (status_str, "invisible") || - g_str_equal (status_str, "offline")); - - appinfo = g_desktop_app_info_new (desktop_id); - if (!appinfo) { - g_warning ("could not set status for '%s', there's no desktop file with that id", desktop_id); - return; + g_settings_strv_remove (settings, "applications", desktop_id); } - id = g_app_info_get_simple_id (G_APP_INFO (appinfo)); - section = g_hash_table_lookup (applications, id); - if (section != NULL) - app_section_set_status (section, status_str); - - indicator_messages_service_complete_set_status (service, invocation); - - g_free (id); - g_object_unref (appinfo); + indicator_messages_service_complete_unregister_application (service, invocation); } static GSimpleActionGroup * @@ -539,40 +337,6 @@ create_action_group (void) return actions; } -static GMenuModel * -create_status_section (void) -{ - GMenu *menu; - GMenuItem *item; - struct status_item { - gchar *label; - gchar *action; - gchar *icon_name; - } status_items[] = { - { _("Available"), "status::available", "user-available" }, - { _("Away"), "status::away", "user-away" }, - { _("Busy"), "status::busy", "user-busy" }, - { _("Invisible"), "status::invisible", "user-invisible" }, - { _("Offline"), "status::offline", "user-offline" } - }; - int i; - - menu = g_menu_new (); - - item = g_menu_item_new (NULL, NULL); - g_menu_item_set_attribute (item, "x-canonical-type", "s", "IdoMenuItem"); - - for (i = 0; i < G_N_ELEMENTS (status_items); i++) { - g_menu_item_set_label (item, status_items[i].label); - g_menu_item_set_detailed_action (item, status_items[i].action); - g_menu_item_set_attribute (item, "x-canonical-icon", "s", status_items[i].icon_name); - g_menu_append_item (menu, item); - } - - g_object_unref (item); - return G_MENU_MODEL (menu); -} - static void got_bus (GObject *object, GAsyncResult * res, @@ -596,8 +360,8 @@ got_bus (GObject *object, return; } - g_dbus_connection_export_menu_model (bus, INDICATOR_MESSAGES_DBUS_OBJECT, - G_MENU_MODEL (toplevel_menu), &error); + g_dbus_connection_export_menu_model (bus, INDICATOR_MESSAGES_DBUS_OBJECT "/phone", + G_MENU_MODEL (menu), &error); if (error) { g_warning ("unable to export menu on dbus: %s", error->message); g_error_free (error); @@ -651,26 +415,24 @@ main (int argc, char ** argv) G_CALLBACK (register_application), NULL); g_signal_connect (messages_service, "handle-unregister-application", G_CALLBACK (unregister_application), NULL); - g_signal_connect (messages_service, "handle-set-status", - G_CALLBACK (set_status), NULL); menu = g_menu_new (); - chat_section = create_status_section (); - g_menu_append (menu, _("Clear"), "clear"); + sources_section = g_menu_new (); + messages_section = g_menu_new (); + g_menu_append_section (menu, NULL, G_MENU_MODEL (sources_section)); + g_menu_append_section (menu, NULL, G_MENU_MODEL (messages_section)); toplevel_menu = g_menu_new (); + g_menu_append_submenu (toplevel_menu, NULL, G_MENU_MODEL (menu)); settings = g_settings_new ("com.canonical.indicator.messages"); applications = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); - g_idle_add(build_launchers, NULL); - g_main_loop_run(mainloop); /* Clean up */ g_object_unref (messages_service); - g_object_unref (chat_section); g_object_unref (settings); g_hash_table_unref (applications); return 0; diff --git a/test/Makefile.am b/test/Makefile.am index 4671446..8c8c160 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -38,8 +38,8 @@ noinst_LTLIBRARIES = \ libindicator-messages-service.la libindicator_messages_service_la_SOURCES = \ - $(top_builddir)/src/indicator-messages-service.c \ - $(top_builddir)/src/indicator-messages-service.h \ + $(top_builddir)/common/indicator-messages-service.c \ + $(top_builddir)/common/indicator-messages-service.h \ $(top_srcdir)/src/app-section.c \ $(top_srcdir)/src/app-section.h \ $(top_srcdir)/src/gactionmuxer.c \ @@ -53,6 +53,7 @@ libindicator_messages_service_la_CFLAGS = \ $(APPLET_CFLAGS) \ $(COVERAGE_CFLAGS) \ -I$(top_builddir)/src \ + -I$(top_builddir)/common \ -Wall \ -Wl,-Bsymbolic-functions \ -Wl,-z,defs \ |