From ef6b3b8daaef62a502c60e0faa22c3a4b10c0399 Mon Sep 17 00:00:00 2001 From: Lars Uebernickel Date: Tue, 13 Nov 2012 13:32:15 +0100 Subject: Move service .xml into a common directory Both the service (in src/) and the client library (in libmessaging-menu/) need access to the dbus interface description file. Until now, it resided in src, with both Makefiles calling gdbus-codegen on it. This patch moves the file to common/ and builds a convenience library that contains only the generated code. --- common/Makefile.am | 22 ++++++++++++++++++++ .../com.canonical.indicator.messages.service.xml | 24 ++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 common/Makefile.am create mode 100644 common/com.canonical.indicator.messages.service.xml (limited to 'common') diff --git a/common/Makefile.am b/common/Makefile.am new file mode 100644 index 0000000..5bd7e20 --- /dev/null +++ b/common/Makefile.am @@ -0,0 +1,22 @@ + +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 + +BUILT_SOURCES = \ + indicator-messages-service.c \ + indicator-messages-service.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.service.xml b/common/com.canonical.indicator.messages.service.xml new file mode 100644 index 0000000..00ae154 --- /dev/null +++ b/common/com.canonical.indicator.messages.service.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + -- cgit v1.2.3 From 5a9434c6652ef1117a50a9226f8609cea2ee53c0 Mon Sep 17 00:00:00 2001 From: Lars Uebernickel Date: Wed, 14 Nov 2012 17:57:54 +0100 Subject: Change application to service d-bus protocol Previously, the protocol was simply a menu model and an action group of the currently active sources. The service inserted the menu as a section into the indicator menu. This doesn't work anymore, because applications can (soon) expose individual messages, and the messaging menu doesn't always display all of those at once. This patch introduces a more specific d-bus API. That API is still considered private: applications have to use libmessaging-menu. --- common/Makefile.am | 12 +- ...om.canonical.indicator.messages.application.xml | 21 + libmessaging-menu/messaging-menu-app.c | 511 +++++++++++---------- src/app-section.c | 424 ++++++++++------- test/Makefile.am | 1 + 5 files changed, 555 insertions(+), 414 deletions(-) create mode 100644 common/com.canonical.indicator.messages.application.xml (limited to 'common') diff --git a/common/Makefile.am b/common/Makefile.am index 5bd7e20..0b8bad4 100644 --- a/common/Makefile.am +++ b/common/Makefile.am @@ -9,9 +9,19 @@ indicator-messages-service.c: com.canonical.indicator.messages.service.xml $^ 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-service.h \ + indicator-messages-application.c \ + indicator-messages-application.h libmessaging_common_la_SOURCES = \ $(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..ec095dd --- /dev/null +++ b/common/com.canonical.indicator.messages.application.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/libmessaging-menu/messaging-menu-app.c b/libmessaging-menu/messaging-menu-app.c index 377aea0..20d9474 100644 --- a/libmessaging-menu/messaging-menu-app.c +++ b/libmessaging-menu/messaging-menu-app.c @@ -19,8 +19,10 @@ #include "messaging-menu-app.h" #include "indicator-messages-service.h" +#include "indicator-messages-application.h" #include +#include /** * SECTION:messaging-menu @@ -102,14 +104,13 @@ struct _MessagingMenuApp int registered; /* -1 for unknown */ MessagingMenuStatus status; gboolean status_set; - GSimpleActionGroup *source_actions; - GMenu *menu; GDBusConnection *bus; + GList *sources; + IndicatorMessagesApplication *app_interface; + IndicatorMessagesService *messages_service; guint watch_id; - guint action_export_id; - guint menu_export_id; GCancellable *cancellable; }; @@ -133,10 +134,56 @@ 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 (Source *source) +{ + 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 gchar * messaging_menu_app_get_dbus_object_path (MessagingMenuApp *app) { @@ -155,18 +202,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 +218,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 +247,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 +281,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 +302,9 @@ messaging_menu_app_dispose (GObject *object) g_clear_object (&app->messages_service); } + 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); } @@ -416,6 +435,72 @@ 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_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 SourcesChanged */ + 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 void messaging_menu_app_init (MessagingMenuApp *app) { @@ -423,13 +508,13 @@ 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); app->cancellable = g_cancellable_new (); @@ -604,123 +689,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; + Source *source; - g_return_if_fail (MESSAGING_MENU_IS_APP (app)); - g_return_if_fail (id != NULL); - - if (g_simple_action_group_lookup (app->source_actions, id)) - { - g_warning ("a source with id '%s' already exists", id); - return; - } - - 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); + source = messaging_menu_app_lookup_source (app, id); + if (!source) + g_warning ("a source with id '%s' doesn't exist", 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 +832,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 +886,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 +942,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 +982,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 +1003,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 +1019,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 +1047,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); - } - else - { - g_menu_item_set_attribute_value (item, "x-canonical-icon", NULL); + g_clear_object (&source->icon); + if (icon) + source->icon = g_object_ref (icon); + messaging_menu_app_notify_source_changed (app, source); } - - g_menu_replace_item (app->menu, pos, item); - - g_object_unref (item); } /** @@ -1120,7 +1074,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 +1100,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 +1126,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 +1155,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,7 +1186,17 @@ 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); + } } /** 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 . #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/test/Makefile.am b/test/Makefile.am index ee7cb3e..8c8c160 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -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 \ -- cgit v1.2.3 From a44f4a7f0d1918809452da175c5b0585ecd6d951 Mon Sep 17 00:00:00 2001 From: Lars Uebernickel Date: Thu, 15 Nov 2012 14:00:14 +0100 Subject: Add support for individual messages to MessagingMenuApp This is not exposed in the indicator menu yet. --- ...om.canonical.indicator.messages.application.xml | 12 ++ libmessaging-menu/messaging-menu-app.c | 144 ++++++++++++++++++++- libmessaging-menu/messaging-menu-message.c | 2 +- 3 files changed, 152 insertions(+), 6 deletions(-) (limited to 'common') diff --git a/common/com.canonical.indicator.messages.application.xml b/common/com.canonical.indicator.messages.application.xml index ec095dd..33aed7c 100644 --- a/common/com.canonical.indicator.messages.application.xml +++ b/common/com.canonical.indicator.messages.application.xml @@ -4,9 +4,15 @@ + + + + + + @@ -17,5 +23,11 @@ + + + + + + diff --git a/libmessaging-menu/messaging-menu-app.c b/libmessaging-menu/messaging-menu-app.c index d793d6b..bc7e978 100644 --- a/libmessaging-menu/messaging-menu-app.c +++ b/libmessaging-menu/messaging-menu-app.c @@ -106,6 +106,7 @@ struct _MessagingMenuApp gboolean status_set; GDBusConnection *bus; + GHashTable *messages; GList *sources; IndicatorMessagesApplication *app_interface; @@ -125,6 +126,7 @@ enum { enum { ACTIVATE_SOURCE, + ACTIVATE_MESSAGE, STATUS_CHANGED, N_SIGNALS }; @@ -186,6 +188,29 @@ source_to_variant (Source *source) 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) { @@ -304,7 +329,10 @@ 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); @@ -357,6 +385,27 @@ messaging_menu_app_class_init (MessagingMenuAppClass *class) g_cclosure_marshal_VOID__STRING, 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 @@ -487,6 +536,13 @@ messaging_menu_app_remove_source_internal (MessagingMenuApp *app, 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, @@ -496,7 +552,7 @@ messaging_menu_app_activate_source (IndicatorMessagesApplication *app_interface, MessagingMenuApp *app = user_data; GQuark q = g_quark_from_string (source_id); - /* Activate implies removing the source, no need for SourcesChanged */ + /* 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); @@ -505,6 +561,52 @@ messaging_menu_app_activate_source (IndicatorMessagesApplication *app_interface, 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) { @@ -519,8 +621,12 @@ messaging_menu_app_init (MessagingMenuApp *app) 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", @@ -1226,8 +1332,34 @@ messaging_menu_app_append_message (MessagingMenuApp *app, 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 (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); + } + } } /** @@ -1244,8 +1376,7 @@ void messaging_menu_app_remove_message (MessagingMenuApp *app, MessagingMenuMessage *msg) { - g_return_if_fail (MESSAGING_MENU_IS_APP (app)); - g_return_if_fail (MESSAGING_MENU_IS_MESSAGE (app)); + messaging_menu_app_remove_message_by_id (app, messaging_menu_message_get_id (msg)); } /** @@ -1264,4 +1395,7 @@ messaging_menu_app_remove_message_by_id (MessagingMenuApp *app, { 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-message.c b/libmessaging-menu/messaging-menu-message.c index 631786a..f5cb18c 100644 --- a/libmessaging-menu/messaging-menu-message.c +++ b/libmessaging-menu/messaging-menu-message.c @@ -225,7 +225,7 @@ messaging_menu_message_init (MessagingMenuMessage *self) /** * messaging_menu_message_new: * @id: unique id of the message - * @icon: (transfer full): a #GIcon representing 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 -- cgit v1.2.3 From b55e637321b0c3fdcb5e8aafab9614f80a3228d3 Mon Sep 17 00:00:00 2001 From: Lars Uebernickel Date: Fri, 16 Nov 2012 14:41:50 +0100 Subject: Export menu for the phone client This is a rough first draft: It only fetched the sources and messages from applications once and doesn't listen for further changes. This removes all code for the desktop menu for now. --- ...om.canonical.indicator.messages.application.xml | 2 +- src/dbus-data.h | 2 +- src/gactionmuxer.c | 8 + src/gactionmuxer.h | 3 + src/messages-service.c | 551 ++++++--------------- 5 files changed, 170 insertions(+), 396 deletions(-) (limited to 'common') diff --git a/common/com.canonical.indicator.messages.application.xml b/common/com.canonical.indicator.messages.application.xml index 33aed7c..fb9f079 100644 --- a/common/com.canonical.indicator.messages.application.xml +++ b/common/com.canonical.indicator.messages.application.xml @@ -27,7 +27,7 @@ - + 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..9d64412 100644 --- a/src/messages-service.c +++ b/src/messages-service.c @@ -28,12 +28,12 @@ with this program. If not, see . #include #include -#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,173 @@ 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 (NULL, 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-sender", "s", title); + g_menu_item_set_attribute (item, "x-canonical-subject", "s", subtitle); + g_menu_item_set_attribute (item, "x-canonical-body", "s", body); + g_menu_item_set_attribute (item, "x-canonical-time", "x", time); + if (iconstr && *iconstr) + g_menu_item_set_attribute (item, "x-canonical-avatar", "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 +259,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 +294,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 +338,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,7 +361,7 @@ got_bus (GObject *object, return; } - g_dbus_connection_export_menu_model (bus, INDICATOR_MESSAGES_DBUS_OBJECT, + g_dbus_connection_export_menu_model (bus, INDICATOR_MESSAGES_DBUS_OBJECT "/phone", G_MENU_MODEL (toplevel_menu), &error); if (error) { g_warning ("unable to export menu on dbus: %s", error->message); @@ -651,26 +416,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; -- cgit v1.2.3 From 9287c2e6577e71d8f68d9c9e7d393a4f8524ab10 Mon Sep 17 00:00:00 2001 From: Lars Uebernickel Date: Mon, 26 Nov 2012 20:12:13 +0100 Subject: Tell applications when messages and sources are dismissed Right now, this is only used to clean up internal data structures in libmessaging-menu. It's not exposed to the application itself. --- ...om.canonical.indicator.messages.application.xml | 4 +++ libmessaging-menu/messaging-menu-app.c | 21 +++++++++++++ src/im-application-list.c | 35 +++++++++++++++++----- 3 files changed, 53 insertions(+), 7 deletions(-) (limited to 'common') diff --git a/common/com.canonical.indicator.messages.application.xml b/common/com.canonical.indicator.messages.application.xml index fb9f079..552b6a4 100644 --- a/common/com.canonical.indicator.messages.application.xml +++ b/common/com.canonical.indicator.messages.application.xml @@ -13,6 +13,10 @@ + + + + diff --git a/libmessaging-menu/messaging-menu-app.c b/libmessaging-menu/messaging-menu-app.c index 61547c5..d037da9 100644 --- a/libmessaging-menu/messaging-menu-app.c +++ b/libmessaging-menu/messaging-menu-app.c @@ -607,6 +607,25 @@ messaging_menu_app_activate_message (IndicatorMessagesApplication *app_interface return TRUE; } +static gboolean +messaging_menu_app_dismiss (IndicatorMessagesApplication *app_interface, + GDBusMethodInvocation *invocation, + const gchar * const *sources, + const gchar * const *messages, + gpointer user_data) +{ + MessagingMenuApp *app = user_data; + const gchar * const *it; + + for (it = sources; *it; it++) + messaging_menu_app_remove_source_internal (app, *it); + + for (it = messages; *it; it++) + messaging_menu_app_remove_message_internal (app, *it); + + return TRUE; +} + static void messaging_menu_app_init (MessagingMenuApp *app) { @@ -625,6 +644,8 @@ messaging_menu_app_init (MessagingMenuApp *app) 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); + g_signal_connect (app->app_interface, "handle-dismiss", + G_CALLBACK (messaging_menu_app_dismiss), app); app->messages = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); diff --git a/src/im-application-list.c b/src/im-application-list.c index c3be647..d766f24 100644 --- a/src/im-application-list.c +++ b/src/im-application-list.c @@ -118,6 +118,13 @@ im_application_list_source_activated (GSimpleAction *action, app->cancellable, NULL, NULL); } + else + { + const gchar *sources[] = { source_id, NULL }; + const gchar *messages[] = { NULL }; + indicator_messages_application_call_dismiss (app->proxy, sources, messages, + app->cancellable, NULL, NULL); + } im_application_list_source_removed (app, source_id); } @@ -148,6 +155,13 @@ im_application_list_message_activated (GSimpleAction *action, app->cancellable, NULL, NULL); } + else + { + const gchar *sources[] = { NULL }; + const gchar *messages[] = { message_id, NULL }; + indicator_messages_application_call_dismiss (app->proxy, sources, messages, + app->cancellable, NULL, NULL); + } im_application_list_message_removed (app, message_id); } @@ -164,18 +178,25 @@ im_application_list_remove_all (GSimpleAction *action, g_hash_table_iter_init (&iter, list->applications); while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &app)) { - gchar **actions; + gchar **source_actions; + gchar **message_actions; gchar **it; - actions = g_action_group_list_actions (G_ACTION_GROUP (app->source_actions)); - for (it = actions; *it; it++) + source_actions = g_action_group_list_actions (G_ACTION_GROUP (app->source_actions)); + for (it = source_actions; *it; it++) im_application_list_source_removed (app, *it); - g_strfreev (actions); - actions = g_action_group_list_actions (G_ACTION_GROUP (app->message_actions)); - for (it = actions; *it; it++) + message_actions = g_action_group_list_actions (G_ACTION_GROUP (app->message_actions)); + for (it = message_actions; *it; it++) im_application_list_message_removed (app, *it); - g_strfreev (actions); + + indicator_messages_application_call_dismiss (app->proxy, + (const gchar * const *) source_actions, + (const gchar * const *) message_actions, + app->cancellable, NULL, NULL); + + g_strfreev (source_actions); + g_strfreev (message_actions); } } -- cgit v1.2.3 From e8e99703ef0c565f07a9d1eba03c1003d82ff697 Mon Sep 17 00:00:00 2001 From: Lars Uebernickel Date: Tue, 27 Nov 2012 15:14:26 +0100 Subject: Allow adding actions to MessagingMenuMessage --- ...om.canonical.indicator.messages.application.xml | 4 +- libmessaging-menu/messaging-menu-app.c | 28 +--- libmessaging-menu/messaging-menu-message.c | 160 +++++++++++++++++++++ libmessaging-menu/messaging-menu-message.h | 6 + src/im-application-list.c | 6 +- 5 files changed, 176 insertions(+), 28 deletions(-) (limited to 'common') diff --git a/common/com.canonical.indicator.messages.application.xml b/common/com.canonical.indicator.messages.application.xml index 552b6a4..f37cd25 100644 --- a/common/com.canonical.indicator.messages.application.xml +++ b/common/com.canonical.indicator.messages.application.xml @@ -5,7 +5,7 @@ - + @@ -28,7 +28,7 @@ - + diff --git a/libmessaging-menu/messaging-menu-app.c b/libmessaging-menu/messaging-menu-app.c index d037da9..491c123 100644 --- a/libmessaging-menu/messaging-menu-app.c +++ b/libmessaging-menu/messaging-menu-app.c @@ -152,6 +152,9 @@ static void global_status_changed (IndicatorMessagesService *service, const gchar *status_str, gpointer user_data); +/* in messaging-menu-message.c */ +GVariant * messaging_menu_message_to_variant (MessagingMenuMessage *msg); + static void source_free (gpointer data) { @@ -188,29 +191,6 @@ source_to_variant (Source *source) 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) { @@ -571,7 +551,7 @@ messaging_menu_app_list_messages (IndicatorMessagesApplication *app_interface, GHashTableIter iter; MessagingMenuMessage *message; - g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(sssssxb)")); + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(sssssxa(ssgav)b)")); g_hash_table_iter_init (&iter, app->messages); while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &message)) diff --git a/libmessaging-menu/messaging-menu-message.c b/libmessaging-menu/messaging-menu-message.c index f5cb18c..cf050d3 100644 --- a/libmessaging-menu/messaging-menu-message.c +++ b/libmessaging-menu/messaging-menu-message.c @@ -32,6 +32,8 @@ struct _MessagingMenuMessage gchar *body; gint64 time; gboolean draws_attention; + + GSList *actions; }; G_DEFINE_TYPE (MessagingMenuMessage, messaging_menu_message, G_TYPE_OBJECT); @@ -51,6 +53,31 @@ enum static GParamSpec *properties[NUM_PROPERTIES]; +typedef struct +{ + gchar *id; + gchar *label; + GVariantType *parameter_type; + GVariant *parameter_hint; +} Action; + +static void +action_free (gpointer data) +{ + Action *action = data; + + g_free (action->id); + g_free (action->label); + + if (action->parameter_type) + g_variant_type_free (action->parameter_type); + + if (action->parameter_hint) + g_variant_unref (action->parameter_hint); + + g_slice_free (Action, action); +} + static void messaging_menu_message_dispose (GObject *object) { @@ -71,6 +98,9 @@ messaging_menu_message_finalize (GObject *object) g_free (msg->subtitle); g_free (msg->body); + g_slist_free_full (msg->actions, action_free); + msg->actions = NULL; + G_OBJECT_CLASS (messaging_menu_message_parent_class)->finalize (object); } @@ -370,3 +400,133 @@ messaging_menu_message_set_draws_attention (MessagingMenuMessage *msg, msg->draws_attention = draws_attention; g_object_notify_by_pspec (G_OBJECT (msg), properties[PROP_DRAWS_ATTENTION]); } + +/** + * messaging_menu_message_add_action: + * @msg: a #MessagingMenuMessage + * @id: unique id of the action + * @label: (allow-none): label of the action + * @parameter_type: (allow-none): a #GVariantType + * @parameter_hint: (allow-none): a #GVariant suggesting a valid range + * for parameters + * + * Adds an action with @id and @label to @message. Actions are an + * alternative way for users to activate a message. Note that messages + * can still be activated without an action. + * + * If @parameter_type is non-%NULL, the action is able to receive user + * input in addition to simply activating the action. Currently, only + * string parameters are supported. + * + * A list of predefined parameters can be supplied as a #GVariant array + * of @parameter_type in @parameter_hint. If @parameter_hint is + * floating, it will be consumed. + * + * It is recommended to add at most two actions to a message. + */ +void +messaging_menu_message_add_action (MessagingMenuMessage *msg, + const gchar *id, + const gchar *label, + const GVariantType *parameter_type, + GVariant *parameter_hint) +{ + Action *action; + + g_return_if_fail (MESSAGING_MENU_IS_MESSAGE (msg)); + g_return_if_fail (id != NULL); + + action = g_slice_new (Action); + action->id = g_strdup (id); + action->label = g_strdup (label); + action->parameter_type = parameter_type ? g_variant_type_copy (parameter_type) : NULL; + action->parameter_hint = parameter_hint ? g_variant_ref_sink (parameter_hint) : NULL; + + msg->actions = g_slist_append (msg->actions, action); +} + +static GVariant * +action_to_variant (Action *action) +{ + GVariantBuilder builder; + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("(ssgav)")); + + g_variant_builder_add (&builder, "s", action->id); + g_variant_builder_add (&builder, "s", action->label ? action->label : ""); + + if (action->parameter_type) + { + gchar *type = g_variant_type_dup_string (action->parameter_type); + g_variant_builder_add (&builder, "g", type); + g_free (type); + } + else + g_variant_builder_add (&builder, "g", ""); + + g_variant_builder_open (&builder, G_VARIANT_TYPE ("av")); + if (action->parameter_hint) + g_variant_builder_add (&builder, "v", action->parameter_hint); + g_variant_builder_close (&builder); + + return g_variant_builder_end (&builder); +} + +/* + * messaging_menu_message_to_variant: + * @msg: a #MessagingMenuMessage + * + * Serializes @msg to a #GVariant of the form (sssssxa(ssgav)b): + * + * id + * icon + * title + * subtitle + * body + * time + * array of actions: id + * label + * parameter_type + * parameter_hint (0 or 1 elements) + * draws_attention + * + * Returns: a new floating #GVariant instance + */ +GVariant * +messaging_menu_message_to_variant (MessagingMenuMessage *msg) +{ + GVariantBuilder builder; + GSList *it; + + g_return_val_if_fail (MESSAGING_MENU_IS_MESSAGE (msg), NULL); + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("(sssssxa(ssgav)b)")); + + g_variant_builder_add (&builder, "s", msg->id); + + if (msg->icon) + { + gchar *iconstr; + + iconstr = g_icon_to_string (msg->icon); + g_variant_builder_add (&builder, "s", iconstr); + + g_free (iconstr); + } + else + g_variant_builder_add (&builder, "s", ""); + + g_variant_builder_add (&builder, "s", msg->title ? msg->title : ""); + g_variant_builder_add (&builder, "s", msg->subtitle ? msg->subtitle : ""); + g_variant_builder_add (&builder, "s", msg->body ? msg->body : ""); + g_variant_builder_add (&builder, "x", msg->time); + + g_variant_builder_open (&builder, G_VARIANT_TYPE ("a(ssgav)")); + for (it = msg->actions; it; it = it->next) + g_variant_builder_add_value (&builder, action_to_variant (it->data)); + g_variant_builder_close (&builder); + + g_variant_builder_add (&builder, "b", msg->draws_attention); + + return g_variant_builder_end (&builder); +} diff --git a/libmessaging-menu/messaging-menu-message.h b/libmessaging-menu/messaging-menu-message.h index 068247b..4708246 100644 --- a/libmessaging-menu/messaging-menu-message.h +++ b/libmessaging-menu/messaging-menu-message.h @@ -59,6 +59,12 @@ gboolean messaging_menu_message_get_draws_attention (MessagingMe void messaging_menu_message_set_draws_attention (MessagingMenuMessage *msg, gboolean draws_attention); +void messaging_menu_message_add_action (MessagingMenuMessage *msg, + const gchar *id, + const gchar *label, + const GVariantType *parameter_type, + GVariant *parameter_hint); + G_END_DECLS #endif diff --git a/src/im-application-list.c b/src/im-application-list.c index d766f24..4eaaed0 100644 --- a/src/im-application-list.c +++ b/src/im-application-list.c @@ -509,13 +509,14 @@ im_application_list_message_added (Application *app, const gchar *subtitle; const gchar *body; gint64 time; + GVariantIter *action_iter; gboolean draws_attention; GSimpleAction *action; GIcon *app_icon; gchar *app_iconstr; - g_variant_get (message, "(&s&s&s&s&sxb)", - &id, &iconstr, &title, &subtitle, &body, &time, &draws_attention); + g_variant_get (message, "(&s&s&s&s&sxa(ssgav)b)", + &id, &iconstr, &title, &subtitle, &body, &time, &action_iter, &draws_attention); app_icon = g_app_info_get_icon (G_APP_INFO (app->info)); app_iconstr = app_icon ? g_icon_to_string (app_icon) : NULL; @@ -528,6 +529,7 @@ im_application_list_message_added (Application *app, g_signal_emit (app->list, signals[MESSAGE_ADDED], 0, app->id, app_iconstr, id, iconstr, title, subtitle, body, time, draws_attention); + g_variant_iter_free (action_iter); g_free (app_iconstr); g_object_unref (action); } -- cgit v1.2.3 From ced173aa7ea78c76922e07c84743d5b7f76c265e Mon Sep 17 00:00:00 2001 From: Lars Uebernickel Date: Tue, 27 Nov 2012 16:40:38 +0100 Subject: Pass message action and its parameter through to the application --- ...om.canonical.indicator.messages.application.xml | 2 ++ libmessaging-menu/messaging-menu-app.c | 23 +++++++++++++++++++++- src/im-application-list.c | 2 ++ 3 files changed, 26 insertions(+), 1 deletion(-) (limited to 'common') diff --git a/common/com.canonical.indicator.messages.application.xml b/common/com.canonical.indicator.messages.application.xml index f37cd25..5e99780 100644 --- a/common/com.canonical.indicator.messages.application.xml +++ b/common/com.canonical.indicator.messages.application.xml @@ -12,6 +12,8 @@ + + diff --git a/libmessaging-menu/messaging-menu-app.c b/libmessaging-menu/messaging-menu-app.c index cea0eb9..8b9f76b 100644 --- a/libmessaging-menu/messaging-menu-app.c +++ b/libmessaging-menu/messaging-menu-app.c @@ -546,6 +546,8 @@ static gboolean messaging_menu_app_activate_message (IndicatorMessagesApplication *app_interface, GDBusMethodInvocation *invocation, const gchar *message_id, + const gchar *action_id, + GVariant *params, gpointer user_data) { MessagingMenuApp *app = user_data; @@ -554,7 +556,26 @@ messaging_menu_app_activate_message (IndicatorMessagesApplication *app_interface msg = g_hash_table_lookup (app->messages, message_id); if (msg) { - g_signal_emit_by_name (msg, "activate", NULL, NULL); + if (*action_id) + { + gchar *signal; + + signal = g_strconcat ("activate::", action_id, NULL); + + if (g_variant_n_children (params)) + { + GVariant *param = g_variant_get_child_value (params, 0); + g_signal_emit_by_name (msg, signal, action_id, param); + g_variant_unref (param); + } + else + g_signal_emit_by_name (msg, signal, action_id, NULL); + + g_free (signal); + } + else + g_signal_emit_by_name (msg, "activate", NULL, NULL); + /* Activate implies removing the message, no need for MessageRemoved */ messaging_menu_app_remove_message_internal (app, message_id); diff --git a/src/im-application-list.c b/src/im-application-list.c index 4eaaed0..7c53e69 100644 --- a/src/im-application-list.c +++ b/src/im-application-list.c @@ -152,6 +152,8 @@ im_application_list_message_activated (GSimpleAction *action, { indicator_messages_application_call_activate_message (app->proxy, message_id, + "", + g_variant_new_array (G_VARIANT_TYPE_VARIANT, NULL, 0), app->cancellable, NULL, NULL); } -- cgit v1.2.3 From 235a7edc5c2fc2435c837f8a3d427dd0755f966d Mon Sep 17 00:00:00 2001 From: Lars Uebernickel Date: Wed, 28 Nov 2012 17:24:31 +0100 Subject: Use aa{sv} instead of a(ssgav) to communicate actions to the service --- ...om.canonical.indicator.messages.application.xml | 4 ++-- libmessaging-menu/messaging-menu-app.c | 2 +- libmessaging-menu/messaging-menu-message.c | 27 +++++++++------------- src/im-application-list.c | 8 +++---- 4 files changed, 18 insertions(+), 23 deletions(-) (limited to 'common') diff --git a/common/com.canonical.indicator.messages.application.xml b/common/com.canonical.indicator.messages.application.xml index 5e99780..6f038e6 100644 --- a/common/com.canonical.indicator.messages.application.xml +++ b/common/com.canonical.indicator.messages.application.xml @@ -5,7 +5,7 @@ - + @@ -30,7 +30,7 @@ - + diff --git a/libmessaging-menu/messaging-menu-app.c b/libmessaging-menu/messaging-menu-app.c index 8b9f76b..3747705 100644 --- a/libmessaging-menu/messaging-menu-app.c +++ b/libmessaging-menu/messaging-menu-app.c @@ -529,7 +529,7 @@ messaging_menu_app_list_messages (IndicatorMessagesApplication *app_interface, GHashTableIter iter; MessagingMenuMessage *message; - g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(sssssxa(ssgav)b)")); + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(sssssxaa{sv}b)")); g_hash_table_iter_init (&iter, app->messages); while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &message)) diff --git a/libmessaging-menu/messaging-menu-message.c b/libmessaging-menu/messaging-menu-message.c index a17cdb6..886c552 100644 --- a/libmessaging-menu/messaging-menu-message.c +++ b/libmessaging-menu/messaging-menu-message.c @@ -470,24 +470,22 @@ action_to_variant (Action *action) { GVariantBuilder builder; - g_variant_builder_init (&builder, G_VARIANT_TYPE ("(ssgav)")); + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}")); - g_variant_builder_add (&builder, "s", action->id); - g_variant_builder_add (&builder, "s", action->label ? action->label : ""); + g_variant_builder_add (&builder, "{sv}", "name", g_variant_new_string (action->id)); + + if (action->label) + g_variant_builder_add (&builder, "{sv}", "label", g_variant_new_string (action->label)); if (action->parameter_type) { gchar *type = g_variant_type_dup_string (action->parameter_type); - g_variant_builder_add (&builder, "g", type); + g_variant_builder_add (&builder, "{sv}", "parameter-type", g_variant_new_signature (type)); g_free (type); } - else - g_variant_builder_add (&builder, "g", ""); - g_variant_builder_open (&builder, G_VARIANT_TYPE ("av")); if (action->parameter_hint) - g_variant_builder_add (&builder, "v", action->parameter_hint); - g_variant_builder_close (&builder); + g_variant_builder_add (&builder, "{sv}", "parameter-hint", action->parameter_hint); return g_variant_builder_end (&builder); } @@ -496,7 +494,7 @@ action_to_variant (Action *action) * messaging_menu_message_to_variant: * @msg: a #MessagingMenuMessage * - * Serializes @msg to a #GVariant of the form (sssssxa(ssgav)b): + * Serializes @msg to a #GVariant of the form (sssssxaa{sv}b): * * id * icon @@ -504,10 +502,7 @@ action_to_variant (Action *action) * subtitle * body * time - * array of actions: id - * label - * parameter_type - * parameter_hint (0 or 1 elements) + * array of action dictionaries * draws_attention * * Returns: a new floating #GVariant instance @@ -520,7 +515,7 @@ messaging_menu_message_to_variant (MessagingMenuMessage *msg) g_return_val_if_fail (MESSAGING_MENU_IS_MESSAGE (msg), NULL); - g_variant_builder_init (&builder, G_VARIANT_TYPE ("(sssssxa(ssgav)b)")); + g_variant_builder_init (&builder, G_VARIANT_TYPE ("(sssssxaa{sv}b)")); g_variant_builder_add (&builder, "s", msg->id); @@ -541,7 +536,7 @@ messaging_menu_message_to_variant (MessagingMenuMessage *msg) g_variant_builder_add (&builder, "s", msg->body ? msg->body : ""); g_variant_builder_add (&builder, "x", msg->time); - g_variant_builder_open (&builder, G_VARIANT_TYPE ("a(ssgav)")); + g_variant_builder_open (&builder, G_VARIANT_TYPE ("aa{sv}")); for (it = msg->actions; it; it = it->next) g_variant_builder_add_value (&builder, action_to_variant (it->data)); g_variant_builder_close (&builder); diff --git a/src/im-application-list.c b/src/im-application-list.c index 7c53e69..1f901ec 100644 --- a/src/im-application-list.c +++ b/src/im-application-list.c @@ -511,14 +511,14 @@ im_application_list_message_added (Application *app, const gchar *subtitle; const gchar *body; gint64 time; - GVariantIter *action_iter; + GVariant *actions; gboolean draws_attention; GSimpleAction *action; GIcon *app_icon; gchar *app_iconstr; - g_variant_get (message, "(&s&s&s&s&sxa(ssgav)b)", - &id, &iconstr, &title, &subtitle, &body, &time, &action_iter, &draws_attention); + g_variant_get (message, "(&s&s&s&s&sx@aa{sv}b)", + &id, &iconstr, &title, &subtitle, &body, &time, &actions, &draws_attention); app_icon = g_app_info_get_icon (G_APP_INFO (app->info)); app_iconstr = app_icon ? g_icon_to_string (app_icon) : NULL; @@ -531,7 +531,7 @@ im_application_list_message_added (Application *app, g_signal_emit (app->list, signals[MESSAGE_ADDED], 0, app->id, app_iconstr, id, iconstr, title, subtitle, body, time, draws_attention); - g_variant_iter_free (action_iter); + g_variant_unref (actions); g_free (app_iconstr); g_object_unref (action); } -- cgit v1.2.3