aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Uebernickel <lars.uebernickel@canonical.com>2012-11-21 14:50:49 +0000
committerTarmac <Unknown>2012-11-21 14:50:49 +0000
commit4a6fa0a418b899667988d25b7f8922a031f72f38 (patch)
tree23afb2bc49433bd8d9e238079fec9a95272ace1c
parent12fa6e3a84895570e2413a984e3e40320dcf9281 (diff)
parente8eda4af8432d36d82b1124a4bad0f5eee876188 (diff)
downloadayatana-indicator-messages-4a6fa0a418b899667988d25b7f8922a031f72f38.tar.gz
ayatana-indicator-messages-4a6fa0a418b899667988d25b7f8922a031f72f38.tar.bz2
ayatana-indicator-messages-4a6fa0a418b899667988d25b7f8922a031f72f38.zip
Move menu and application handling out of messages-service.c
As a side effect, changes from applications (message/source adds and removals) are propagated to the menu properly. Approved by Renato Araujo Oliveira Filho, PS Jenkins bot, Olivier Tilloy.
-rw-r--r--libmessaging-menu/messaging-menu-app.c2
-rw-r--r--src/Makefile.am6
-rw-r--r--src/im-application-list.c509
-rw-r--r--src/im-application-list.h52
-rw-r--r--src/im-phone-menu.c256
-rw-r--r--src/im-phone-menu.h67
-rw-r--r--src/messages-service.c208
7 files changed, 915 insertions, 185 deletions
diff --git a/libmessaging-menu/messaging-menu-app.c b/libmessaging-menu/messaging-menu-app.c
index bc7e978..61547c5 100644
--- a/libmessaging-menu/messaging-menu-app.c
+++ b/libmessaging-menu/messaging-menu-app.c
@@ -1397,5 +1397,5 @@ messaging_menu_app_remove_message_by_id (MessagingMenuApp *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);
+ indicator_messages_application_emit_message_removed (app->app_interface, id);
}
diff --git a/src/Makefile.am b/src/Makefile.am
index a7bfe66..5d8bad8 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -52,7 +52,11 @@ indicator_messages_service_SOURCES = \
gsettingsstrv.c \
gsettingsstrv.h \
gmenuutils.c \
- gmenuutils.h
+ gmenuutils.h \
+ im-phone-menu.c \
+ im-phone-menu.h \
+ im-application-list.c \
+ im-application-list.h
indicator_messages_service_CFLAGS = \
$(APPLET_CFLAGS) \
diff --git a/src/im-application-list.c b/src/im-application-list.c
new file mode 100644
index 0000000..732a117
--- /dev/null
+++ b/src/im-application-list.c
@@ -0,0 +1,509 @@
+/*
+ * Copyright 2012 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Lars Uebernickel <lars.uebernickel@canonical.com>
+ */
+
+#include "im-application-list.h"
+
+#include "indicator-messages-application.h"
+#include "gactionmuxer.h"
+
+#include <gio/gdesktopappinfo.h>
+
+typedef GObjectClass ImApplicationListClass;
+
+struct _ImApplicationList
+{
+ GObject parent;
+
+ GHashTable *applications;
+ GActionMuxer *muxer;
+};
+
+G_DEFINE_TYPE (ImApplicationList, im_application_list, G_TYPE_OBJECT);
+
+enum
+{
+ SOURCE_ADDED,
+ SOURCE_CHANGED,
+ SOURCE_REMOVED,
+ MESSAGE_ADDED,
+ MESSAGE_REMOVED,
+ APP_STOPPED,
+ N_SIGNALS
+};
+
+static guint signals[N_SIGNALS];
+
+typedef struct
+{
+ ImApplicationList *list;
+ GDesktopAppInfo *info;
+ IndicatorMessagesApplication *proxy;
+ GSimpleActionGroup *actions;
+ GCancellable *cancellable;
+} Application;
+
+static void
+application_free (gpointer data)
+{
+ Application *app = data;
+
+ if (!app)
+ return;
+
+ g_clear_object (&app->info);
+
+ if (app->cancellable)
+ {
+ g_cancellable_cancel (app->cancellable);
+ g_clear_object (&app->cancellable);
+ }
+
+ if (app->proxy)
+ g_object_unref (app->proxy);
+
+ if (app->actions)
+ g_object_unref (app->actions);
+
+ g_slice_free (Application, app);
+}
+
+static void
+im_application_list_dispose (GObject *object)
+{
+ ImApplicationList *list = IM_APPLICATION_LIST (object);
+
+ g_clear_pointer (&list->applications, g_hash_table_unref);
+ g_clear_object (&list->muxer);
+
+ G_OBJECT_CLASS (im_application_list_parent_class)->dispose (object);
+}
+
+static void
+im_application_list_finalize (GObject *object)
+{
+ G_OBJECT_CLASS (im_application_list_parent_class)->finalize (object);
+}
+
+static void
+im_application_list_class_init (ImApplicationListClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = im_application_list_dispose;
+ object_class->finalize = im_application_list_finalize;
+
+ signals[SOURCE_ADDED] = g_signal_new ("source-added",
+ IM_TYPE_APPLICATION_LIST,
+ G_SIGNAL_RUN_FIRST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE,
+ 4,
+ G_TYPE_DESKTOP_APP_INFO,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ G_TYPE_STRING);
+
+ signals[SOURCE_CHANGED] = g_signal_new ("source-changed",
+ IM_TYPE_APPLICATION_LIST,
+ G_SIGNAL_RUN_FIRST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE,
+ 4,
+ G_TYPE_DESKTOP_APP_INFO,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ G_TYPE_STRING);
+
+ signals[SOURCE_REMOVED] = g_signal_new ("source-removed",
+ IM_TYPE_APPLICATION_LIST,
+ G_SIGNAL_RUN_FIRST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE,
+ 2,
+ G_TYPE_DESKTOP_APP_INFO,
+ G_TYPE_STRING);
+
+ signals[MESSAGE_ADDED] = g_signal_new ("message-added",
+ IM_TYPE_APPLICATION_LIST,
+ G_SIGNAL_RUN_FIRST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE,
+ 8,
+ G_TYPE_DESKTOP_APP_INFO,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ G_TYPE_INT64,
+ G_TYPE_BOOLEAN);
+
+ signals[MESSAGE_REMOVED] = g_signal_new ("message-removed",
+ IM_TYPE_APPLICATION_LIST,
+ G_SIGNAL_RUN_FIRST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE,
+ 2,
+ G_TYPE_DESKTOP_APP_INFO,
+ G_TYPE_STRING);
+
+ signals[APP_STOPPED] = g_signal_new ("app-stopped",
+ IM_TYPE_APPLICATION_LIST,
+ G_SIGNAL_RUN_FIRST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_DESKTOP_APP_INFO);
+}
+
+static void
+im_application_list_init (ImApplicationList *list)
+{
+ list->applications = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, application_free);
+ list->muxer = g_action_muxer_new ();
+}
+
+ImApplicationList *
+im_application_list_new (void)
+{
+ return g_object_new (IM_TYPE_APPLICATION_LIST, NULL);
+}
+
+void
+im_application_list_add (ImApplicationList *list,
+ const gchar *desktop_id)
+{
+ GDesktopAppInfo *info;
+ Application *app;
+ const gchar *id;
+
+ g_return_if_fail (IM_IS_APPLICATION_LIST (list));
+ g_return_if_fail (desktop_id != NULL);
+
+ app = g_hash_table_lookup (list->applications, desktop_id);
+ if (app)
+ return;
+
+ info = g_desktop_app_info_new (desktop_id);
+ if (!info)
+ {
+ g_warning ("an application with id '%s' is not installed", desktop_id);
+ return;
+ }
+
+ id = g_app_info_get_id (G_APP_INFO (info));
+
+ app = g_slice_new0 (Application);
+ app->list = list;
+ app->info = info;
+ app->actions = g_simple_action_group_new ();
+
+ g_hash_table_insert (list->applications, (gpointer) id, app);
+ g_action_muxer_insert (list->muxer, id, G_ACTION_GROUP (app->actions));
+}
+
+void
+im_application_list_remove (ImApplicationList *list,
+ const gchar *id)
+{
+ Application *app;
+
+ g_return_if_fail (IM_IS_APPLICATION_LIST (list));
+
+ app = g_hash_table_lookup (list->applications, id);
+ if (app)
+ {
+ if (app->proxy || app->cancellable)
+ g_signal_emit (app->list, signals[APP_STOPPED], 0, app->info);
+
+ g_hash_table_remove (list->applications, id);
+ g_action_muxer_remove (list->muxer, id);
+ }
+}
+
+static void
+im_application_list_source_added (Application *app,
+ guint position,
+ GVariant *source)
+{
+ const gchar *id;
+ const gchar *label;
+ const gchar *iconstr;
+ guint32 count;
+ gint64 time;
+ const gchar *string;
+ gboolean draws_attention;
+ GSimpleAction *action;
+
+ g_variant_get (source, "(&s&s&sux&sb)",
+ &id, &label, &iconstr, &count, &time, &string, &draws_attention);
+
+ action = g_simple_action_new_stateful (id, NULL, g_variant_new_uint32 (count));
+ g_simple_action_group_insert (app->actions, G_ACTION (action));
+
+ g_signal_emit (app->list, signals[SOURCE_ADDED], 0, app->info, id, label, iconstr);
+
+ g_object_unref (action);
+}
+
+static void
+im_application_list_source_changed (Application *app,
+ GVariant *source)
+{
+ const gchar *id;
+ const gchar *label;
+ const gchar *iconstr;
+ guint32 count;
+ gint64 time;
+ const gchar *string;
+ gboolean draws_attention;
+
+ g_variant_get (source, "(&s&s&sux&sb)",
+ &id, &label, &iconstr, &count, &time, &string, &draws_attention);
+
+ g_action_group_change_action_state (G_ACTION_GROUP (app->actions), id,
+ g_variant_new_uint32 (count));
+
+ g_signal_emit (app->list, signals[SOURCE_CHANGED], 0, app->info, id, label, iconstr);
+}
+
+static void
+im_application_list_source_removed (Application *app,
+ const gchar *id)
+{
+ g_simple_action_group_remove (app->actions, id);
+
+ g_signal_emit (app->list, signals[SOURCE_REMOVED], 0, app->info, id);
+}
+
+static void
+im_application_list_sources_listed (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ Application *app = user_data;
+ GVariant *sources;
+ GError *error = NULL;
+
+ if (indicator_messages_application_call_list_sources_finish (app->proxy, &sources, result, &error))
+ {
+ GVariantIter iter;
+ GVariant *source;
+ guint i = 0;
+
+ g_variant_iter_init (&iter, sources);
+ while ((source = g_variant_iter_next_value (&iter)))
+ {
+ im_application_list_source_added (app, i++, source);
+ g_variant_unref (source);
+ }
+
+ g_variant_unref (sources);
+ }
+ else
+ {
+ g_warning ("could not fetch the list of sources: %s", error->message);
+ g_error_free (error);
+ }
+}
+
+static void
+im_application_list_message_added (Application *app,
+ GVariant *message)
+{
+ const gchar *id;
+ const gchar *iconstr;
+ const gchar *title;
+ const gchar *subtitle;
+ const gchar *body;
+ gint64 time;
+ gboolean draws_attention;
+ GSimpleAction *action;
+
+ g_variant_get (message, "(&s&s&s&s&sxb)",
+ &id, &iconstr, &title, &subtitle, &body, &time, &draws_attention);
+
+ action = g_simple_action_new (id, NULL);
+ g_simple_action_group_insert (G_SIMPLE_ACTION_GROUP (app->actions), G_ACTION (action));
+
+ g_signal_emit (app->list, signals[MESSAGE_ADDED], 0,
+ app->info, id, iconstr, title, subtitle, body, time, draws_attention);
+
+ g_object_unref (action);
+}
+
+static void
+im_application_list_message_removed (Application *app,
+ const gchar *id)
+{
+ g_simple_action_group_remove (app->actions, id);
+
+ g_signal_emit (app->list, signals[MESSAGE_REMOVED], 0, app->info, id);
+}
+
+static void
+im_application_list_messages_listed (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ Application *app = user_data;
+ GVariant *messages;
+ GError *error = NULL;
+
+ if (indicator_messages_application_call_list_messages_finish (app->proxy, &messages, result, &error))
+ {
+ GVariantIter iter;
+ GVariant *message;
+
+ g_variant_iter_init (&iter, messages);
+ while ((message = g_variant_iter_next_value (&iter)))
+ {
+ im_application_list_message_added (app, message);
+ g_variant_unref (message);
+ }
+
+ g_variant_unref (messages);
+ }
+ else
+ {
+ g_warning ("could not fetch the list of messages: %s", error->message);
+ g_error_free (error);
+ }
+}
+
+static void
+im_application_list_unset_remote (Application *app)
+{
+ gboolean was_running;
+
+ was_running = app->proxy || app->cancellable;
+
+ if (app->cancellable)
+ {
+ g_cancellable_cancel (app->cancellable);
+ g_clear_object (&app->cancellable);
+ }
+ g_clear_object (&app->proxy);
+
+ /* clear actions by creating a new action group and overriding it in
+ * the muxer */
+ g_object_unref (app->actions);
+ app->actions = g_simple_action_group_new ();
+ g_action_muxer_insert (app->list->muxer,
+ g_app_info_get_id (G_APP_INFO (app->info)),
+ G_ACTION_GROUP (app->actions));
+
+ if (was_running)
+ g_signal_emit (app->list, signals[APP_STOPPED], 0, app->info);
+}
+
+static void
+im_application_list_app_vanished (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ Application *app = user_data;
+
+ im_application_list_unset_remote (app);
+}
+
+static void
+im_application_list_proxy_created (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ Application *app = user_data;
+ GError *error = NULL;
+
+ app->proxy = indicator_messages_application_proxy_new_finish (result, &error);
+ if (!app)
+ {
+ g_warning ("could not create application proxy: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ indicator_messages_application_call_list_sources (app->proxy, app->cancellable,
+ im_application_list_sources_listed, app);
+ indicator_messages_application_call_list_messages (app->proxy, app->cancellable,
+ im_application_list_messages_listed, app);
+
+ g_signal_connect_swapped (app->proxy, "source-added", G_CALLBACK (im_application_list_source_added), app);
+ g_signal_connect_swapped (app->proxy, "source-changed", G_CALLBACK (im_application_list_source_changed), app);
+ g_signal_connect_swapped (app->proxy, "source-removed", G_CALLBACK (im_application_list_source_removed), app);
+ g_signal_connect_swapped (app->proxy, "message-added", G_CALLBACK (im_application_list_message_added), app);
+ g_signal_connect_swapped (app->proxy, "message-removed", G_CALLBACK (im_application_list_message_removed), app);
+
+ g_bus_watch_name_on_connection (g_dbus_proxy_get_connection (G_DBUS_PROXY (app->proxy)),
+ g_dbus_proxy_get_name (G_DBUS_PROXY (app->proxy)),
+ G_BUS_NAME_WATCHER_FLAGS_NONE,
+ NULL, im_application_list_app_vanished,
+ app, NULL);
+}
+
+void
+im_application_list_set_remote (ImApplicationList *list,
+ const gchar *id,
+ GDBusConnection *connection,
+ const gchar *unique_bus_name,
+ const gchar *object_path)
+{
+ Application *app;
+
+ g_return_if_fail (IM_IS_APPLICATION_LIST (list));
+
+ app = g_hash_table_lookup (list->applications, id);
+ if (!app)
+ {
+ g_warning ("'%s' is not a registered application", id);
+ return;
+ }
+
+ if (app->proxy || app->cancellable)
+ {
+ g_warning ("replacing '%s' at %s with %s", id, unique_bus_name,
+ g_dbus_proxy_get_name_owner (G_DBUS_PROXY (app->proxy)));
+ im_application_list_unset_remote (app);
+ }
+
+ app->cancellable = g_cancellable_new ();
+ indicator_messages_application_proxy_new (connection, G_DBUS_PROXY_FLAGS_NONE,
+ unique_bus_name, object_path, app->cancellable,
+ im_application_list_proxy_created, app);
+}
+
+GActionGroup *
+im_application_list_get_action_group (ImApplicationList *list)
+{
+ g_return_val_if_fail (IM_IS_APPLICATION_LIST (list), NULL);
+
+ return G_ACTION_GROUP (list->muxer);
+}
diff --git a/src/im-application-list.h b/src/im-application-list.h
new file mode 100644
index 0000000..ecba312
--- /dev/null
+++ b/src/im-application-list.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2012 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Lars Uebernickel <lars.uebernickel@canonical.com>
+ */
+
+#ifndef __IM_APPLICATION_LIST_H__
+#define __IM_APPLICATION_LIST_H__
+
+#include <gio/gio.h>
+
+#define IM_TYPE_APPLICATION_LIST (im_application_list_get_type ())
+#define IM_APPLICATION_LIST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), IM_TYPE_APPLICATION_LIST, ImApplicationList))
+#define IM_APPLICATION_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), IM_TYPE_APPLICATION_LIST, ImApplicationListClass))
+#define IM_IS_APPLICATION_LIST(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IM_TYPE_APPLICATION_LIST))
+#define IM_IS_APPLICATION_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), IM_TYPE_APPLICATION_LIST))
+#define IM_APPLICATION_LIST_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), IM_TYPE_APPLICATION_LIST, ImApplicationListClass))
+
+typedef struct _ImApplicationList ImApplicationList;
+
+GType im_application_list_get_type (void);
+
+ImApplicationList * im_application_list_new (void);
+
+void im_application_list_add (ImApplicationList *list,
+ const gchar *desktop_id);
+
+void im_application_list_remove (ImApplicationList *list,
+ const gchar *id);
+
+void im_application_list_set_remote (ImApplicationList *list,
+ const gchar *id,
+ GDBusConnection *connection,
+ const gchar *unique_bus_name,
+ const gchar *object_path);
+
+GActionGroup * im_application_list_get_action_group (ImApplicationList *list);
+
+#endif
diff --git a/src/im-phone-menu.c b/src/im-phone-menu.c
new file mode 100644
index 0000000..0ba41c4
--- /dev/null
+++ b/src/im-phone-menu.c
@@ -0,0 +1,256 @@
+/*
+ * Copyright 2012 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Lars Uebernickel <lars.uebernickel@canonical.com>
+ */
+
+#include "im-phone-menu.h"
+
+#include <string.h>
+
+typedef GObjectClass ImPhoneMenuClass;
+
+struct _ImPhoneMenu
+{
+ GObject parent;
+
+ GMenu *toplevel_menu;
+ GMenu *message_section;
+ GMenu *source_section;
+
+};
+
+G_DEFINE_TYPE (ImPhoneMenu, im_phone_menu, G_TYPE_OBJECT);
+
+typedef void (*ImMenuForeachFunc) (GMenuModel *menu, gint pos);
+
+static void
+im_phone_menu_foreach_item_with_action (GMenuModel *menu,
+ const gchar *action,
+ ImMenuForeachFunc func)
+{
+ gint n_items;
+ gint i;
+
+ n_items = g_menu_model_get_n_items (menu);
+ for (i = 0; i < n_items; i++)
+ {
+ gchar *item_action;
+
+ g_menu_model_get_item_attribute (menu, i, G_MENU_ATTRIBUTE_ACTION, "s", &item_action);
+
+ if (g_str_equal (action, item_action))
+ func (menu, i);
+
+ g_free (item_action);
+ }
+}
+
+static void
+im_phone_menu_dispose (GObject *object)
+{
+ ImPhoneMenu *menu = IM_PHONE_MENU (object);
+
+ g_clear_object (&menu->toplevel_menu);
+ g_clear_object (&menu->message_section);
+ g_clear_object (&menu->source_section);
+
+ G_OBJECT_CLASS (im_phone_menu_parent_class)->dispose (object);
+}
+
+static void
+im_phone_menu_finalize (GObject *object)
+{
+ G_OBJECT_CLASS (im_phone_menu_parent_class)->finalize (object);
+}
+
+static void
+im_phone_menu_class_init (ImPhoneMenuClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = im_phone_menu_dispose;
+ object_class->finalize = im_phone_menu_finalize;
+}
+
+static void
+im_phone_menu_init (ImPhoneMenu *menu)
+{
+ menu->message_section = g_menu_new ();
+ menu->source_section = g_menu_new ();
+
+ menu->toplevel_menu = g_menu_new ();
+ g_menu_append_section (menu->toplevel_menu, NULL, G_MENU_MODEL (menu->message_section));
+ g_menu_append_section (menu->toplevel_menu, NULL, G_MENU_MODEL (menu->source_section));
+}
+
+ImPhoneMenu *
+im_phone_menu_new (void)
+{
+ return g_object_new (IM_TYPE_PHONE_MENU, NULL);
+}
+
+GMenuModel *
+im_phone_menu_get_model (ImPhoneMenu *menu)
+{
+ g_return_val_if_fail (IM_IS_PHONE_MENU (menu), NULL);
+
+ return G_MENU_MODEL (menu->toplevel_menu);
+}
+
+void
+im_phone_menu_add_message (ImPhoneMenu *menu,
+ GDesktopAppInfo *app,
+ const gchar *id,
+ const gchar *iconstr,
+ const gchar *title,
+ const gchar *subtitle,
+ const gchar *body,
+ gint64 time)
+{
+ const gchar *app_id;
+ GMenuItem *item;
+ gchar *action_name;
+
+ g_return_if_fail (IM_IS_PHONE_MENU (menu));
+ g_return_if_fail (G_IS_DESKTOP_APP_INFO (app));
+
+ app_id = g_app_info_get_id (G_APP_INFO (app));
+ g_return_if_fail (app_id);
+
+ action_name = g_strconcat (app_id, ".", id, NULL);
+
+ item = g_menu_item_new (title, action_name);
+ g_menu_item_set_attribute (item, "x-canonical-type", "s", "com.canonical.indicator.messages.messageitem");
+ g_menu_item_set_attribute (item, "x-canonical-message-id", "s", id);
+ g_menu_item_set_attribute (item, "x-canonical-subtitle", "s", subtitle);
+ g_menu_item_set_attribute (item, "x-canonical-text", "s", body);
+ g_menu_item_set_attribute (item, "x-canonical-time", "x", time);
+
+ if (iconstr)
+ g_menu_item_set_attribute (item, "x-canonical-icon", "s", iconstr);
+
+ g_menu_append_item (menu->message_section, item);
+
+ g_free (action_name);
+ g_object_unref (item);
+}
+
+void
+im_phone_menu_remove_message (ImPhoneMenu *menu,
+ GDesktopAppInfo *app,
+ const gchar *id)
+{
+ gchar *action_name;
+
+ g_return_if_fail (IM_IS_PHONE_MENU (menu));
+ g_return_if_fail (G_IS_DESKTOP_APP_INFO (app));
+
+ action_name = g_strconcat (g_app_info_get_id (G_APP_INFO (app)), ".", id, NULL);
+ im_phone_menu_foreach_item_with_action (G_MENU_MODEL (menu->message_section),
+ action_name,
+ (ImMenuForeachFunc) g_menu_remove);
+
+ g_free (action_name);
+}
+
+void
+im_phone_menu_add_source (ImPhoneMenu *menu,
+ GDesktopAppInfo *app,
+ const gchar *id,
+ const gchar *label,
+ const gchar *iconstr)
+{
+ GMenuItem *item;
+ gchar *action_name;
+
+ g_return_if_fail (IM_IS_PHONE_MENU (menu));
+ g_return_if_fail (G_IS_DESKTOP_APP_INFO (app));
+
+ action_name = g_strconcat (g_app_info_get_id (G_APP_INFO (app)), ".", 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)
+ g_menu_item_set_attribute (item, "x-canonical-icon", "s", iconstr);
+
+ g_menu_append_item (menu->source_section, item);
+
+ g_free (action_name);
+ g_object_unref (item);
+}
+
+void
+im_phone_menu_remove_source (ImPhoneMenu *menu,
+ GDesktopAppInfo *app,
+ const gchar *id)
+{
+ gchar *action_name;
+
+ g_return_if_fail (IM_IS_PHONE_MENU (menu));
+ g_return_if_fail (G_IS_DESKTOP_APP_INFO (app));
+
+ action_name = g_strconcat (g_app_info_get_id (G_APP_INFO (app)), ".", id, NULL);
+ im_phone_menu_foreach_item_with_action (G_MENU_MODEL (menu->source_section),
+ action_name,
+ (ImMenuForeachFunc) g_menu_remove);
+
+ g_free (action_name);
+}
+
+static void
+im_phone_menu_remove_all_for_app (GMenu *menu,
+ GDesktopAppInfo *app)
+{
+ gchar *prefix;
+ gint n_items;
+ gint i = 0;
+
+ prefix = g_strconcat (g_app_info_get_id (G_APP_INFO (app)), ".", NULL);
+
+ n_items = g_menu_model_get_n_items (G_MENU_MODEL (menu));
+ while (i < n_items)
+ {
+ gchar *action;
+
+ g_menu_model_get_item_attribute (G_MENU_MODEL (menu), i, G_MENU_ATTRIBUTE_ACTION, "s", &action);
+ if (g_str_has_prefix (action, prefix))
+ {
+ g_menu_remove (menu, i);
+ n_items--;
+ }
+ else
+ {
+ i++;
+ }
+
+ g_free (action);
+ }
+
+ g_free (prefix);
+}
+
+void
+im_phone_menu_remove_application (ImPhoneMenu *menu,
+ GDesktopAppInfo *app)
+{
+ g_return_if_fail (IM_IS_PHONE_MENU (menu));
+ g_return_if_fail (G_IS_DESKTOP_APP_INFO (app));
+
+ im_phone_menu_remove_all_for_app (menu->source_section, app);
+ im_phone_menu_remove_all_for_app (menu->message_section, app);
+}
diff --git a/src/im-phone-menu.h b/src/im-phone-menu.h
new file mode 100644
index 0000000..b0b6b4f
--- /dev/null
+++ b/src/im-phone-menu.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2012 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Lars Uebernickel <lars.uebernickel@canonical.com>
+ */
+
+#ifndef __IM_PHONE_MENU_H__
+#define __IM_PHONE_MENU_H__
+
+#include <gio/gio.h>
+#include <gio/gdesktopappinfo.h>
+
+#define IM_TYPE_PHONE_MENU (im_phone_menu_get_type ())
+#define IM_PHONE_MENU(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), IM_TYPE_PHONE_MENU, ImPhoneMenu))
+#define IM_PHONE_MENU_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), IM_TYPE_PHONE_MENU, ImPhoneMenuClass))
+#define IM_IS_PHONE_MENU(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IM_TYPE_PHONE_MENU))
+#define IM_IS_PHONE_MENU_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), IM_TYPE_PHONE_MENU))
+#define IM_PHONE_MENU_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), IM_TYPE_PHONE_MENU, ImPhoneMenuClass))
+
+typedef struct _ImPhoneMenu ImPhoneMenu;
+
+GType im_phone_menu_get_type (void);
+
+ImPhoneMenu * im_phone_menu_new (void);
+
+GMenuModel * im_phone_menu_get_model (ImPhoneMenu *menu);
+
+void im_phone_menu_add_message (ImPhoneMenu *menu,
+ GDesktopAppInfo *app,
+ const gchar *id,
+ const gchar *iconstr,
+ const gchar *title,
+ const gchar *subtitle,
+ const gchar *body,
+ gint64 time);
+
+void im_phone_menu_remove_message (ImPhoneMenu *menu,
+ GDesktopAppInfo *app,
+ const gchar *id);
+
+void im_phone_menu_add_source (ImPhoneMenu *menu,
+ GDesktopAppInfo *app,
+ const gchar *id,
+ const gchar *label,
+ const gchar *iconstr);
+
+void im_phone_menu_remove_source (ImPhoneMenu *menu,
+ GDesktopAppInfo *app,
+ const gchar *id);
+
+void im_phone_menu_remove_application (ImPhoneMenu *menu,
+ GDesktopAppInfo *app);
+
+#endif
diff --git a/src/messages-service.c b/src/messages-service.c
index 81b7f0b..b3731a8 100644
--- a/src/messages-service.c
+++ b/src/messages-service.c
@@ -25,27 +25,24 @@ with this program. If not, see <http://www.gnu.org/licenses/>.
#include <locale.h>
#include <libindicator/indicator-service.h>
#include <gio/gio.h>
-#include <gio/gdesktopappinfo.h>
#include <glib/gi18n.h>
#include "dbus-data.h"
-#include "gactionmuxer.h"
#include "gsettingsstrv.h"
#include "gmenuutils.h"
#include "indicator-messages-service.h"
#include "indicator-messages-application.h"
+#include "im-phone-menu.h"
+#include "im-application-list.h"
#define NUM_STATUSES 5
-static GHashTable *applications;
+static ImApplicationList *applications;
static IndicatorMessagesService *messages_service;
static GSimpleActionGroup *actions;
-static GActionMuxer *action_muxer;
static GMenu *toplevel_menu;
-static GMenu *menu;
-static GMenu *messages_section;
-static GMenu *sources_section;
+static ImPhoneMenu *menu;
static GSettings *settings;
static gboolean draws_attention;
static const gchar *global_status[6]; /* max 5: available, away, busy, invisible, offline */
@@ -113,145 +110,6 @@ status_action_activate (GSimpleAction *action,
}
static void
-sources_listed (GObject *source_object,
- GAsyncResult *result,
- gpointer user_data)
-{
- 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;
-
- 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);
- g_free (app_id);
- return;
- }
-
- 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_variant_unref (sources);
- g_free (app_id);
-}
-
-static void
-messages_listed (GObject *source_object,
- GAsyncResult *result,
- gpointer user_data)
-{
- 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;
- }
-
- 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))
- {
- GMenuItem *item;
- GActionGroup *app_actions;
- GSimpleAction *action;
- gchar *action_name;
-
- app_actions = g_action_muxer_get_group (action_muxer, app_id);
- g_assert (app_actions);
- action = g_simple_action_new (id, NULL);
- g_simple_action_group_insert (G_SIMPLE_ACTION_GROUP (app_actions), G_ACTION (action));
-
- action_name = g_strconcat (app_id, ".", id, NULL);
- item = g_menu_item_new (title, action_name);
- g_menu_item_set_attribute (item, "x-canonical-type", "s", "com.canonical.indicator.messages.messageitem");
- g_menu_item_set_attribute (item, "x-canonical-message-id", "s", id);
- g_menu_item_set_attribute (item, "x-canonical-subtitle", "s", subtitle);
- g_menu_item_set_attribute (item, "x-canonical-text", "s", body);
- g_menu_item_set_attribute (item, "x-canonical-time", "x", time);
- if (iconstr && *iconstr)
- g_menu_item_set_attribute (item, "x-canonical-icon", "s", iconstr);
- g_menu_append_item (messages_section, item);
-
- g_object_unref (action);
- g_free (action_name);
- g_object_unref (item);
- }
-
- g_variant_unref (messages);
- g_free (app_id);
-}
-
-static void
-app_proxy_created (GObject *source_object,
- GAsyncResult *result,
- gpointer user_data)
-{
- gchar *desktop_id = user_data;
- IndicatorMessagesApplication *app_proxy;
- GError *error = NULL;
-
- 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;
- }
-
- /* hash table takes ownership of desktop_id and app_proxy */
- g_hash_table_insert (applications, desktop_id, app_proxy);
-
- 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
register_application (IndicatorMessagesService *service,
GDBusMethodInvocation *invocation,
const gchar *desktop_id,
@@ -260,31 +118,16 @@ register_application (IndicatorMessagesService *service,
{
GDBusConnection *bus;
const gchar *sender;
- GSimpleActionGroup *app_actions;
- 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;
- }
+ im_application_list_add (applications, desktop_id);
bus = g_dbus_interface_skeleton_get_connection (G_DBUS_INTERFACE_SKELETON (service));
sender = g_dbus_method_invocation_get_sender (invocation);
- 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));
-
+ im_application_list_set_remote (applications, desktop_id, bus, sender, menu_path);
g_settings_strv_append_unique (settings, "applications", desktop_id);
indicator_messages_service_complete_register_application (service, invocation);
-
- g_object_unref (app_actions);
}
static void
@@ -293,13 +136,8 @@ unregister_application (IndicatorMessagesService *service,
const gchar *desktop_id,
gpointer user_data)
{
- if (g_hash_table_remove (applications, desktop_id)) {
-
- /* TODO remove menu items that refer to this application */
- g_action_muxer_remove (action_muxer, desktop_id);
-
- g_settings_strv_remove (settings, "applications", desktop_id);
- }
+ im_application_list_remove (applications, desktop_id);
+ g_settings_strv_remove (settings, "applications", desktop_id);
indicator_messages_service_complete_unregister_application (service, invocation);
}
@@ -353,7 +191,8 @@ got_bus (GObject *object,
}
g_dbus_connection_export_action_group (bus, INDICATOR_MESSAGES_DBUS_OBJECT,
- G_ACTION_GROUP (action_muxer), &error);
+ im_application_list_get_action_group (applications),
+ &error);
if (error) {
g_warning ("unable to export action group on dbus: %s", error->message);
g_error_free (error);
@@ -361,7 +200,7 @@ got_bus (GObject *object,
}
g_dbus_connection_export_menu_model (bus, INDICATOR_MESSAGES_DBUS_OBJECT "/phone",
- G_MENU_MODEL (menu), &error);
+ im_phone_menu_get_model (menu), &error);
if (error) {
g_warning ("unable to export menu on dbus: %s", error->message);
g_error_free (error);
@@ -408,32 +247,35 @@ main (int argc, char ** argv)
actions = create_action_group ();
- action_muxer = g_action_muxer_new ();
- g_action_muxer_insert (action_muxer, NULL, G_ACTION_GROUP (actions));
-
g_signal_connect (messages_service, "handle-register-application",
G_CALLBACK (register_application), NULL);
g_signal_connect (messages_service, "handle-unregister-application",
G_CALLBACK (unregister_application), NULL);
- menu = g_menu_new ();
- 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));
+ menu = im_phone_menu_new ();
toplevel_menu = g_menu_new ();
- g_menu_append_submenu (toplevel_menu, NULL, G_MENU_MODEL (menu));
+ g_menu_append_submenu (toplevel_menu, NULL, im_phone_menu_get_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);
+ applications = im_application_list_new ();
+ g_signal_connect_swapped (applications, "source-added",
+ G_CALLBACK (im_phone_menu_add_source), menu);
+ g_signal_connect_swapped (applications, "source-removed",
+ G_CALLBACK (im_phone_menu_remove_source), menu);
+ g_signal_connect_swapped (applications, "message-added",
+ G_CALLBACK (im_phone_menu_add_message), menu);
+ g_signal_connect_swapped (applications, "message-removed",
+ G_CALLBACK (im_phone_menu_remove_message), menu);
+ g_signal_connect_swapped (applications, "app-stopped",
+ G_CALLBACK (im_phone_menu_remove_application), menu);
g_main_loop_run(mainloop);
/* Clean up */
g_object_unref (messages_service);
g_object_unref (settings);
- g_hash_table_unref (applications);
+ g_object_unref (applications);
return 0;
}