aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Uebernickel <lars.uebernickel@canonical.com>2012-11-14 17:57:54 +0100
committerLars Uebernickel <lars.uebernickel@canonical.com>2012-11-14 17:57:54 +0100
commit5a9434c6652ef1117a50a9226f8609cea2ee53c0 (patch)
tree61f0c88e1439a04375e27e0cdf26acb1e60f46de
parentebba9b86ef73743af028a82d53afa0a556017fe9 (diff)
downloadayatana-indicator-messages-5a9434c6652ef1117a50a9226f8609cea2ee53c0.tar.gz
ayatana-indicator-messages-5a9434c6652ef1117a50a9226f8609cea2ee53c0.tar.bz2
ayatana-indicator-messages-5a9434c6652ef1117a50a9226f8609cea2ee53c0.zip
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.
-rw-r--r--common/Makefile.am12
-rw-r--r--common/com.canonical.indicator.messages.application.xml21
-rw-r--r--libmessaging-menu/messaging-menu-app.c511
-rw-r--r--src/app-section.c424
-rw-r--r--test/Makefile.am1
5 files changed, 555 insertions, 414 deletions
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 @@
+<?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="ActivateSource">
+ <arg type="s" name="source_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>
+ </interface>
+</node>
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 <gio/gdesktopappinfo.h>
+#include <string.h>
/**
* 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 <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/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 \