aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am36
-rw-r--r--src/app-section.c424
-rw-r--r--src/dbus-data.h17
-rw-r--r--src/gactionmuxer.c8
-rw-r--r--src/gactionmuxer.h3
-rw-r--r--src/im-application-list.c880
-rw-r--r--src/im-application-list.h52
-rw-r--r--src/im-phone-menu.c331
-rw-r--r--src/im-phone-menu.h70
-rw-r--r--src/messages-service.c590
-rw-r--r--src/messages-service.xml24
11 files changed, 1687 insertions, 748 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 1df80e5..5d8bad8 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,8 +1,5 @@
-BUILT_SOURCES =
EXTRA_DIST =
-CLEANFILES =
-DISTCLEANFILES =
libexec_PROGRAMS = indicator-messages-service
@@ -23,19 +20,20 @@ libmessaging_la_SOURCES = \
im-source-menu-item.h \
ido-detail-label.c \
ido-detail-label.h \
- indicator-messages-service.c \
- indicator-messages-service.h
dbus-data.h
libmessaging_la_CFLAGS = \
$(APPLET_CFLAGS) \
$(COVERAGE_CFLAGS) \
+ -I$(top_builddir)/common \
-Wall \
-Wl,-Bsymbolic-functions \
-Wl,-z,defs \
-Wl,--as-needed \
-Werror \
-DG_LOG_DOMAIN=\"Indicator-Messages\"
-libmessaging_la_LIBADD = $(APPLET_LIBS) -lm
+libmessaging_la_LIBADD = \
+ $(top_builddir)/common/libmessaging-common.la \
+ $(APPLET_LIBS) -lm
libmessaging_la_LDFLAGS = \
$(COVERAGE_LDFLAGS) \
-module -avoid-version
@@ -46,8 +44,6 @@ libmessaging_la_LDFLAGS = \
indicator_messages_service_SOURCES = \
messages-service.c \
- indicator-messages-service.c \
- indicator-messages-service.h \
app-section.c \
app-section.h \
dbus-data.h \
@@ -56,11 +52,16 @@ 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) \
$(COVERAGE_CFLAGS) \
+ -I$(top_builddir)/common \
-Wall \
-Wl,-Bsymbolic-functions \
-Wl,-z,defs \
@@ -69,26 +70,11 @@ indicator_messages_service_CFLAGS = \
-DG_LOG_DOMAIN=\"Indicator-Messages\"
indicator_messages_service_LDADD = \
+ $(top_builddir)/common/libmessaging-common.la \
$(APPLET_LIBS)
indicator_messages_service_LDFLAGS = \
$(COVERAGE_LDFLAGS)
-indicator-messages-service.c: $(top_srcdir)/src/messages-service.xml
- $(AM_V_GEN) gdbus-codegen \
- --interface-prefix com.canonical.indicator.messages. \
- --generate-c-code indicator-messages-service \
- --c-namespace IndicatorMessages \
- $^
-indicator-messages-service.h: indicator-messages-service.c
-
-BUILT_SOURCES += \
- indicator-messages-service.c \
- indicator-messages-service.h
-
EXTRA_DIST += \
messages-service.xml
-
-CLEANFILES += \
- $(BUILT_SOURCES)
-
diff --git a/src/app-section.c b/src/app-section.c
index 823d75a..3a55147 100644
--- a/src/app-section.c
+++ b/src/app-section.c
@@ -33,6 +33,7 @@ with this program. If not, see <http://www.gnu.org/licenses/>.
#include "dbus-data.h"
#include "gmenuutils.h"
#include "gactionmuxer.h"
+#include "indicator-messages-application.h"
struct _AppSectionPrivate
{
@@ -41,11 +42,14 @@ struct _AppSectionPrivate
IndicatorDesktopShortcuts * ids;
+ GCancellable *app_proxy_cancellable;
+ IndicatorMessagesApplication *app_proxy;
+
GMenu *menu;
- GMenuModel *source_menu;
+ GMenu *source_menu;
GSimpleActionGroup *static_shortcuts;
- GActionGroup *source_actions;
+ GSimpleActionGroup *source_actions;
GActionMuxer *muxer;
gboolean draws_attention;
@@ -89,19 +93,6 @@ static void launch_action_change_state (GSimpleAction *action,
gpointer user_data);
static void app_section_set_app_info (AppSection *self,
GDesktopAppInfo *appinfo);
-static gboolean any_action_draws_attention (GActionGroup *group,
- const gchar *ignored_action);
-static void action_added (GActionGroup *group,
- const gchar *action_name,
- gpointer user_data);
-static void action_state_changed (GActionGroup *group,
- const gchar *action_name,
- GVariant *value,
- gpointer user_data);
-static void action_removed (GActionGroup *group,
- const gchar *action_name,
- gpointer user_data);
-static gboolean action_draws_attention (GVariant *state);
static void desktop_file_changed_cb (GFileMonitor *monitor,
GFile *file,
GFile *other_file,
@@ -169,6 +160,7 @@ static void
app_section_init (AppSection *self)
{
AppSectionPrivate *priv;
+ GMenuItem *item;
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
APP_SECTION_TYPE,
@@ -178,10 +170,19 @@ app_section_init (AppSection *self)
priv->appinfo = NULL;
priv->menu = g_menu_new ();
+
+ priv->source_menu = g_menu_new ();
+ item = g_menu_item_new_section (NULL, G_MENU_MODEL (priv->source_menu));
+ g_menu_item_set_attribute (item, "action-namespace", "s", "source");
+ g_menu_append_item (priv->menu, item);
+ g_object_unref (item);
+
priv->static_shortcuts = g_simple_action_group_new ();
+ priv->source_actions = g_simple_action_group_new ();
priv->muxer = g_action_muxer_new ();
g_action_muxer_insert (priv->muxer, NULL, G_ACTION_GROUP (priv->static_shortcuts));
+ g_action_muxer_insert (priv->muxer, "source", G_ACTION_GROUP (priv->source_actions));
priv->draws_attention = FALSE;
@@ -248,32 +249,30 @@ app_section_dispose (GObject *object)
AppSection * self = APP_SECTION(object);
AppSectionPrivate * priv = self->priv;
+ if (priv->app_proxy_cancellable) {
+ g_cancellable_cancel (priv->app_proxy_cancellable);
+ g_clear_object (&priv->app_proxy_cancellable);
+ }
+
if (priv->desktop_file_monitor) {
g_signal_handlers_disconnect_by_func (priv->desktop_file_monitor, desktop_file_changed_cb, self);
g_clear_object (&priv->desktop_file_monitor);
}
+ g_clear_object (&priv->app_proxy);
+
g_clear_object (&priv->menu);
+ g_clear_object (&priv->source_menu);
g_clear_object (&priv->static_shortcuts);
+ g_clear_object (&priv->source_actions);
if (priv->name_watch_id) {
g_bus_unwatch_name (priv->name_watch_id);
priv->name_watch_id = 0;
}
- if (priv->source_actions) {
- g_action_muxer_remove (priv->muxer, "source");
- g_object_disconnect (priv->source_actions,
- "any_signal::action-added", action_added, self,
- "any_signal::action-state-changed", action_state_changed, self,
- "any_signal::action-removed", action_removed, self,
- NULL);
- g_clear_object (&priv->source_actions);
- }
-
g_clear_object (&priv->muxer);
- g_clear_object (&priv->source_menu);
g_clear_object (&priv->ids);
g_clear_object (&priv->appinfo);
@@ -429,6 +428,11 @@ app_section_update_menu (AppSection *self)
g_free(name);
}
+ item = g_menu_item_new_section (NULL, G_MENU_MODEL (priv->source_menu));
+ g_menu_item_set_attribute (item, "action-namespace", "s", "source");
+ g_menu_append_item (priv->menu, item);
+ g_object_unref (item);
+
keyfile = g_file_new_for_path (g_desktop_app_info_get_filename (priv->appinfo));
g_file_load_contents_async (keyfile, NULL, keyfile_loaded, self);
@@ -564,49 +568,242 @@ app_section_get_draws_attention (AppSection *self)
void
app_section_clear_draws_attention (AppSection *self)
{
- AppSectionPrivate * priv = self->priv;
- gchar **action_names;
+ self->priv->draws_attention = FALSE;
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DRAWS_ATTENTION]);
+}
+
+static void
+application_vanished (GDBusConnection *bus,
+ const gchar *name,
+ gpointer user_data)
+{
+ AppSection *self = user_data;
+
+ app_section_unset_object_path (self);
+}
+
+static void
+update_draws_attention (AppSection *self)
+{
+ AppSectionPrivate *priv = self->priv;
+ gchar **actions;
gchar **it;
+ gboolean draws_attention = FALSE;
+
+ actions = g_action_group_list_actions (G_ACTION_GROUP (priv->source_actions));
+
+ for (it = actions; *it; it++) {
+ GVariant *state;
+
+ state = g_action_group_get_action_state (G_ACTION_GROUP (priv->source_actions), *it);
+ if (state) {
+ gboolean b;
+ g_variant_get (state, "(uxsb)", NULL, NULL, NULL, &b);
+ draws_attention = b || draws_attention;
+ g_variant_unref (state);
+ }
+
+ if (draws_attention)
+ break;
+ }
+
+ if (draws_attention != priv->draws_attention) {
+ priv->draws_attention = draws_attention;
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DRAWS_ATTENTION]);
+ }
+
+ g_strfreev (actions);
+}
+
+static void
+remove_source (AppSection *self,
+ const gchar *id)
+{
+ AppSectionPrivate *priv = self->priv;
+ guint n_items;
+ guint i;
+
+ n_items = g_menu_model_get_n_items (G_MENU_MODEL (priv->source_menu));
+ for (i = 0; i < n_items; i++) {
+ gchar *action;
+ gboolean found = FALSE;
+
+ if (g_menu_model_get_item_attribute (G_MENU_MODEL (priv->source_menu), i,
+ G_MENU_ATTRIBUTE_ACTION, "s", &action)) {
+ found = g_str_equal (action, id);
+ g_free (action);
+ }
+
+ if (found) {
+ g_menu_remove (priv->source_menu, i);
+ break;
+ }
+ }
+
+ g_simple_action_group_remove (priv->source_actions, id);
+ update_draws_attention (self);
+}
+
+static void
+source_action_activated (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ AppSection *self = APP_SECTION (user_data);
+ AppSectionPrivate *priv = APP_SECTION (user_data)->priv;
+
+ g_return_if_fail (priv->app_proxy != NULL);
+
+ indicator_messages_application_call_activate_source (priv->app_proxy,
+ g_action_get_name (G_ACTION (action)),
+ priv->app_proxy_cancellable,
+ NULL, NULL);
+
+ remove_source (self, g_action_get_name (G_ACTION (action)));
+}
+
+static void
+sources_listed (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ AppSection *self = user_data;
+ AppSectionPrivate *priv = self->priv;
+ GVariant *sources = NULL;
+ GError *error = NULL;
+ GVariantIter iter;
+ const gchar *id;
+ const gchar *label;
+ const gchar *iconstr;
+ guint32 count;
+ gint64 time;
+ const gchar *string;
+ gboolean draws_attention;
- if (priv->source_actions == NULL)
+ if (!indicator_messages_application_call_list_sources_finish (INDICATOR_MESSAGES_APPLICATION (source_object),
+ &sources, result, &error))
+ {
+ g_warning ("could not fetch the list of sources: %s", error->message);
+ g_error_free (error);
return;
+ }
- action_names = g_action_group_list_actions (priv->source_actions);
+ g_menu_clear (priv->source_menu);
+ g_simple_action_group_clear (priv->source_actions);
+ priv->draws_attention = FALSE;
- for (it = action_names; *it; it++) {
+ g_variant_iter_init (&iter, sources);
+ while (g_variant_iter_next (&iter, "(&s&s&sux&sb)", &id, &label, &iconstr,
+ &count, &time, &string, &draws_attention))
+ {
GVariant *state;
+ GSimpleAction *action;
+ GMenuItem *item;
- state = g_action_group_get_action_state (priv->source_actions, *it);
- if (!state)
- continue;
+ state = g_variant_new ("(uxsb)", count, time, string, draws_attention);
+ action = g_simple_action_new_stateful (id, NULL, state);
+ g_signal_connect (action, "activate", G_CALLBACK (source_action_activated), self);
+ g_simple_action_group_insert (priv->source_actions, G_ACTION (action));
- /* clear draws-attention while preserving other state */
- if (action_draws_attention (state)) {
- guint32 count;
- gint64 time;
- const gchar *str;
- GVariant *new_state;
+ item = g_menu_item_new (label, id);
+ g_menu_item_set_attribute (item, "x-canonical-type", "s", "ImSourceMenuItem");
+ g_menu_append_item (priv->source_menu, item);
- g_variant_get (state, "(ux&sb)", &count, &time, &str, NULL);
+ priv->draws_attention = priv->draws_attention || draws_attention;
- new_state = g_variant_new ("(uxsb)", count, time, str, FALSE);
- g_action_group_change_action_state (priv->source_actions, *it, new_state);
- }
+ g_object_unref (item);
+ g_object_unref (action);
+ }
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DRAWS_ATTENTION]);
+
+ g_variant_unref (sources);
+}
+
+static void
+source_added (IndicatorMessagesApplication *app,
+ const gchar *id,
+ const gchar *label,
+ const gchar *iconstr,
+ guint count,
+ gint64 time,
+ const gchar *string,
+ gboolean draws_attention,
+ gpointer user_data)
+{
+ AppSection *self = user_data;
+ AppSectionPrivate *priv = self->priv;
+ GVariant *state;
+ GSimpleAction *action;
+
+ /* TODO put label and icon into the action as well */
- g_variant_unref (state);
+ state = g_variant_new ("(uxsb)", count, time, string, draws_attention);
+ action = g_simple_action_new_stateful (id, NULL, state);
+
+ g_simple_action_group_insert (priv->source_actions, G_ACTION (action));
+
+ if (draws_attention && !priv->draws_attention) {
+ priv->draws_attention = TRUE;
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DRAWS_ATTENTION]);
}
- g_strfreev (action_names);
+ g_object_unref (action);
+}
+static void
+source_changed (IndicatorMessagesApplication *app,
+ const gchar *id,
+ const gchar *label,
+ const gchar *iconstr,
+ guint count,
+ gint64 time,
+ const gchar *string,
+ gboolean draws_attention,
+ gpointer user_data)
+{
+ AppSection *self = user_data;
+ AppSectionPrivate *priv = self->priv;
+ GVariant *state;
+
+ /* TODO put label and icon into the action as well */
+
+ state = g_variant_new ("(uxsb)", count, time, string, draws_attention);
+ g_action_group_change_action_state (G_ACTION_GROUP (priv->source_actions), id, state);
+
+ update_draws_attention (self);
}
static void
-application_vanished (GDBusConnection *bus,
- const gchar *name,
- gpointer user_data)
+source_removed (IndicatorMessagesApplication *app,
+ const gchar *id,
+ gpointer user_data)
{
AppSection *self = user_data;
- app_section_unset_object_path (self);
+ remove_source (self, id);
+}
+
+static void
+app_proxy_created (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ AppSectionPrivate *priv = APP_SECTION (user_data)->priv;
+ GError *error = NULL;
+
+ priv->app_proxy = indicator_messages_application_proxy_new_finish (result, &error);
+ if (!priv->app_proxy) {
+ g_warning ("could not create application proxy: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ indicator_messages_application_call_list_sources (priv->app_proxy, priv->app_proxy_cancellable,
+ sources_listed, user_data);
+
+ g_signal_connect (priv->app_proxy, "source-added", G_CALLBACK (source_added), user_data);
+ g_signal_connect (priv->app_proxy, "source-changed", G_CALLBACK (source_changed), user_data);
+ g_signal_connect (priv->app_proxy, "source-removed", G_CALLBACK (source_removed), user_data);
}
/*
@@ -627,27 +824,20 @@ app_section_set_object_path (AppSection *self,
const gchar *object_path)
{
AppSectionPrivate *priv = self->priv;
- GMenuItem *item;
g_object_freeze_notify (G_OBJECT (self));
app_section_unset_object_path (self);
- priv->source_actions = G_ACTION_GROUP (g_dbus_action_group_get (bus, bus_name, object_path));
- g_action_muxer_insert (priv->muxer, "source", priv->source_actions);
-
- priv->draws_attention = any_action_draws_attention (priv->source_actions, NULL);
- g_object_connect (priv->source_actions,
- "signal::action-added", action_added, self,
- "signal::action-state-changed", action_state_changed, self,
- "signal::action-removed", action_removed, self,
- NULL);
+ priv->app_proxy_cancellable = g_cancellable_new ();
+ indicator_messages_application_proxy_new (bus,
+ G_DBUS_PROXY_FLAGS_NONE,
+ bus_name,
+ object_path,
+ priv->app_proxy_cancellable,
+ app_proxy_created,
+ self);
- priv->source_menu = G_MENU_MODEL (g_dbus_menu_model_get (bus, bus_name, object_path));
-
- item = g_menu_item_new_section (NULL, priv->source_menu);
- g_menu_item_set_attribute (item, "action-namespace", "s", "source");
- g_menu_append_item (priv->menu, item);
- g_object_unref (item);
+ priv->draws_attention = FALSE;
priv->name_watch_id = g_bus_watch_name_on_connection (bus, bus_name, 0,
NULL, application_vanished,
@@ -675,26 +865,19 @@ app_section_unset_object_path (AppSection *self)
{
AppSectionPrivate *priv = self->priv;
+ if (priv->app_proxy_cancellable) {
+ g_cancellable_cancel (priv->app_proxy_cancellable);
+ g_clear_object (&priv->app_proxy_cancellable);
+ }
+ g_clear_object (&priv->app_proxy);
+
if (priv->name_watch_id) {
g_bus_unwatch_name (priv->name_watch_id);
priv->name_watch_id = 0;
}
- if (priv->source_actions) {
- g_object_disconnect (priv->source_actions,
- "any_signal::action-added", action_added, self,
- "any_signal::action-state-changed", action_state_changed, self,
- "any_signal::action-removed", action_removed, self,
- NULL);
- g_clear_object (&priv->source_actions);
- }
-
- if (priv->source_menu) {
- /* the last menu item points is linked to the app's menumodel */
- gint n_items = g_menu_model_get_n_items (G_MENU_MODEL (priv->menu));
- g_menu_remove (priv->menu, n_items -1);
- g_clear_object (&priv->source_menu);
- }
+ g_simple_action_group_clear (priv->source_actions);
+ g_menu_clear (priv->source_menu);
priv->draws_attention = FALSE;
g_clear_pointer (&priv->chat_status, g_free);
@@ -708,85 +891,6 @@ app_section_unset_object_path (AppSection *self)
"launch", g_variant_new_boolean (FALSE));
}
-static gboolean
-action_draws_attention (GVariant *state)
-{
- gboolean attention;
-
- if (state && g_variant_is_of_type (state, G_VARIANT_TYPE ("(uxsb)")))
- g_variant_get_child (state, 3, "b", &attention);
- else
- attention = FALSE;
-
- return attention;
-}
-
-static gboolean
-any_action_draws_attention (GActionGroup *group,
- const gchar *ignored_action)
-{
- gchar **actions;
- gchar **it;
- gboolean attention = FALSE;
-
- actions = g_action_group_list_actions (group);
-
- for (it = actions; *it && !attention; it++) {
- GVariant *state;
-
- if (ignored_action && g_str_equal (ignored_action, *it))
- continue;
-
- state = g_action_group_get_action_state (group, *it);
- if (state) {
- attention = action_draws_attention (state);
- g_variant_unref (state);
- }
- }
-
- g_strfreev (actions);
- return attention;
-}
-
-static void
-action_added (GActionGroup *group,
- const gchar *action_name,
- gpointer user_data)
-{
- AppSection *self = user_data;
- GVariant *state;
-
- state = g_action_group_get_action_state (group, action_name);
- if (state) {
- self->priv->draws_attention |= action_draws_attention (state);
- g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DRAWS_ATTENTION]);
- g_variant_unref (state);
- }
-}
-
-static void
-action_state_changed (GActionGroup *group,
- const gchar *action_name,
- GVariant *value,
- gpointer user_data)
-{
- AppSection *self = user_data;
-
- self->priv->draws_attention = any_action_draws_attention (group, NULL);
- g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DRAWS_ATTENTION]);
-}
-
-static void
-action_removed (GActionGroup *group,
- const gchar *action_name,
- gpointer user_data)
-{
- AppSection *self = user_data;
-
- self->priv->draws_attention = any_action_draws_attention (group, action_name);
- g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DRAWS_ATTENTION]);
-}
-
gboolean
app_section_get_uses_chat_status (AppSection *self)
{
diff --git a/src/dbus-data.h b/src/dbus-data.h
index 64747a9..59bd305 100644
--- a/src/dbus-data.h
+++ b/src/dbus-data.h
@@ -1,9 +1,24 @@
+/*
+ * Copyright 2012-2013 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/>.
+ */
#ifndef __DBUS_DATA_H__
#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/im-application-list.c b/src/im-application-list.c
new file mode 100644
index 0000000..9a673c9
--- /dev/null
+++ b/src/im-application-list.c
@@ -0,0 +1,880 @@
+/*
+ * 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>
+#include <string.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,
+ REMOVE_ALL,
+ N_SIGNALS
+};
+
+static guint signals[N_SIGNALS];
+
+typedef struct
+{
+ ImApplicationList *list;
+ GDesktopAppInfo *info;
+ gchar *id;
+ IndicatorMessagesApplication *proxy;
+ GActionMuxer *actions;
+ GSimpleActionGroup *source_actions;
+ GSimpleActionGroup *message_actions;
+ GActionMuxer *message_sub_actions;
+ GCancellable *cancellable;
+} Application;
+
+static void
+application_free (gpointer data)
+{
+ Application *app = data;
+
+ if (!app)
+ return;
+
+ g_object_unref (app->info);
+ g_free (app->id);
+
+ 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_object_unref (app->source_actions);
+ g_object_unref (app->message_actions);
+ g_object_unref (app->message_sub_actions);
+ }
+
+ g_slice_free (Application, app);
+}
+
+static void
+im_application_list_source_removed (Application *app,
+ const gchar *id)
+{
+ g_simple_action_group_remove (app->source_actions, id);
+
+ g_signal_emit (app->list, signals[SOURCE_REMOVED], 0, app->id, id);
+}
+
+static void
+im_application_list_source_activated (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ Application *app = user_data;
+ const gchar *source_id;
+
+ source_id = g_action_get_name (G_ACTION (action));
+
+ if (g_variant_get_boolean (parameter))
+ {
+ indicator_messages_application_call_activate_source (app->proxy,
+ source_id,
+ 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);
+}
+
+static guint
+g_action_group_get_n_actions (GActionGroup *group)
+{
+ guint len;
+ gchar **actions;
+
+ actions = g_action_group_list_actions (group);
+ len = g_strv_length (actions);
+
+ g_strfreev (actions);
+ return len;
+}
+
+static gboolean
+application_draws_attention (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ Application *app = value;
+
+ return (g_action_group_get_n_actions (G_ACTION_GROUP (app->source_actions)) +
+ g_action_group_get_n_actions (G_ACTION_GROUP (app->message_actions))) > 0;
+}
+
+static void
+im_application_list_update_draws_attention (ImApplicationList *list)
+{
+ const gchar *icon_name;
+ GVariant *state;
+ GActionGroup *main_actions;
+
+ if (g_hash_table_find (list->applications, application_draws_attention, NULL))
+ icon_name = "indicator-messages-new";
+ else
+ icon_name = "indicator-messages";
+
+ main_actions = g_action_muxer_get_group (list->muxer, NULL);
+ state = g_variant_new ("(sssb)", "", icon_name, "Messages", TRUE);
+ g_action_group_change_action_state (main_actions, "messages", state);
+}
+
+static void
+im_application_list_message_removed (Application *app,
+ const gchar *id)
+{
+ g_simple_action_group_remove (app->message_actions, id);
+ g_action_muxer_remove (app->message_sub_actions, id);
+
+ im_application_list_update_draws_attention (app->list);
+
+ g_signal_emit (app->list, signals[MESSAGE_REMOVED], 0, app->id, id);
+}
+
+static void
+im_application_list_message_activated (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ Application *app = user_data;
+ const gchar *message_id;
+
+ message_id = g_action_get_name (G_ACTION (action));
+
+ if (g_variant_get_boolean (parameter))
+ {
+ indicator_messages_application_call_activate_message (app->proxy,
+ message_id,
+ "",
+ g_variant_new_array (G_VARIANT_TYPE_VARIANT, NULL, 0),
+ 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);
+}
+
+static void
+im_application_list_sub_message_activated (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ Application *app = user_data;
+ const gchar *message_id;
+ const gchar *action_id;
+ GVariantBuilder builder;
+
+ message_id = g_object_get_data (G_OBJECT (action), "message");
+ action_id = g_action_get_name (G_ACTION (action));
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("av"));
+ if (parameter)
+ g_variant_builder_add (&builder, "v", parameter);
+
+ indicator_messages_application_call_activate_message (app->proxy,
+ message_id,
+ action_id,
+ g_variant_builder_end (&builder),
+ app->cancellable,
+ NULL, NULL);
+
+ im_application_list_message_removed (app, message_id);
+}
+
+
+static void
+im_application_list_remove_all (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ ImApplicationList *list = user_data;
+ GHashTableIter iter;
+ Application *app;
+
+ g_signal_emit (list, signals[REMOVE_ALL], 0);
+
+ g_hash_table_iter_init (&iter, list->applications);
+ while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &app))
+ {
+ gchar **source_actions;
+ gchar **message_actions;
+ gchar **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);
+
+ 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);
+
+ 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);
+ }
+}
+
+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_STRING,
+ 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_STRING,
+ 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_STRING,
+ 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,
+ 10,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ G_TYPE_VARIANT,
+ 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_STRING,
+ 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_STRING);
+
+ signals[REMOVE_ALL] = g_signal_new ("remove-all",
+ IM_TYPE_APPLICATION_LIST,
+ G_SIGNAL_RUN_FIRST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+}
+
+static void
+im_application_list_init (ImApplicationList *list)
+{
+ const GActionEntry action_entries[] = {
+ { "messages", NULL, NULL, "('', 'indicator-messages', 'Messages', true)", NULL },
+ { "remove-all", im_application_list_remove_all }
+ };
+
+ GSimpleActionGroup *actions;
+
+ list->applications = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, application_free);
+
+ actions = g_simple_action_group_new ();
+ g_simple_action_group_add_entries (actions, action_entries, G_N_ELEMENTS (action_entries), list);
+
+ list->muxer = g_action_muxer_new ();
+ g_action_muxer_insert (list->muxer, NULL, G_ACTION_GROUP (actions));
+
+ g_object_unref (actions);
+}
+
+ImApplicationList *
+im_application_list_new (void)
+{
+ return g_object_new (IM_TYPE_APPLICATION_LIST, NULL);
+}
+
+static gchar *
+im_application_list_canonical_id (const gchar *id)
+{
+ gchar *str;
+ gchar *p;
+ int len;
+
+ len = strlen (id);
+ if (g_str_has_suffix (id, ".desktop"))
+ len -= 8;
+
+ str = g_strndup (id, len);
+
+ for (p = str; *p; p++)
+ {
+ if (*p == '.')
+ *p = '_';
+ }
+
+ return str;
+}
+
+static Application *
+im_application_list_lookup (ImApplicationList *list,
+ const gchar *desktop_id)
+{
+ gchar *id;
+ Application *app;
+
+ id = im_application_list_canonical_id (desktop_id);
+ app = g_hash_table_lookup (list->applications, id);
+
+ g_free (id);
+ return app;
+}
+
+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);
+
+ if (im_application_list_lookup (list, desktop_id))
+ 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));
+ g_return_if_fail (id != NULL);
+
+ app = g_slice_new0 (Application);
+ app->info = info;
+ app->id = im_application_list_canonical_id (id);
+ app->list = list;
+ app->actions = g_action_muxer_new ();
+ app->source_actions = g_simple_action_group_new ();
+ app->message_actions = g_simple_action_group_new ();
+ app->message_sub_actions = g_action_muxer_new ();
+
+ g_action_muxer_insert (app->actions, "src", G_ACTION_GROUP (app->source_actions));
+ g_action_muxer_insert (app->actions, "msg", G_ACTION_GROUP (app->message_actions));
+ g_action_muxer_insert (app->actions, "msg-actions", G_ACTION_GROUP (app->message_sub_actions));
+
+ g_hash_table_insert (list->applications, (gpointer) app->id, app);
+ g_action_muxer_insert (list->muxer, app->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 = im_application_list_lookup (list, id);
+ if (app)
+ {
+ if (app->proxy || app->cancellable)
+ g_signal_emit (app->list, signals[APP_STOPPED], 0, app->id);
+
+ 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;
+ GVariant *state;
+ GSimpleAction *action;
+
+ g_variant_get (source, "(&s&s&sux&sb)",
+ &id, &label, &iconstr, &count, &time, &string, &draws_attention);
+
+ state = g_variant_new ("(uxsb)", count, time, string, draws_attention);
+ action = g_simple_action_new_stateful (id, G_VARIANT_TYPE_BOOLEAN, state);
+ g_signal_connect (action, "activate", G_CALLBACK (im_application_list_source_activated), app);
+
+ g_simple_action_group_insert (app->source_actions, G_ACTION (action));
+
+ g_signal_emit (app->list, signals[SOURCE_ADDED], 0, app->id, 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->source_actions), id,
+ g_variant_new ("(uxsb)", count, time, string, draws_attention));
+
+ g_signal_emit (app->list, signals[SOURCE_CHANGED], 0, app->id, id, label, iconstr);
+}
+
+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 gchar *
+get_symbolic_app_icon_string (GIcon *icon)
+{
+ const gchar * const *names;
+ gchar *symbolic_name;
+ GIcon *symbolic_icon;
+ gchar *str;
+
+ if (!G_IS_THEMED_ICON (icon))
+ return NULL;
+
+ names = g_themed_icon_get_names (G_THEMED_ICON (icon));
+ if (!names || !names[0])
+ return NULL;
+
+ symbolic_icon = g_themed_icon_new_from_names ((gchar **) names, -1);
+
+ symbolic_name = g_strconcat (names[0], "-symbolic", NULL);
+ g_themed_icon_prepend_name (G_THEMED_ICON (symbolic_icon), symbolic_name);
+
+ str = g_icon_to_string (symbolic_icon);
+
+ g_free (symbolic_name);
+ g_object_unref (symbolic_icon);
+ return str;
+}
+
+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;
+ GVariantIter *action_iter;
+ gboolean draws_attention;
+ GSimpleAction *action;
+ GIcon *app_icon;
+ gchar *app_iconstr = NULL;
+ GVariant *actions = NULL;
+
+ g_variant_get (message, "(&s&s&s&s&sxaa{sv}b)",
+ &id, &iconstr, &title, &subtitle, &body, &time, &action_iter, &draws_attention);
+
+ app_icon = g_app_info_get_icon (G_APP_INFO (app->info));
+ if (app_icon)
+ app_iconstr = get_symbolic_app_icon_string (app_icon);
+
+ action = g_simple_action_new (id, G_VARIANT_TYPE_BOOLEAN);
+ g_signal_connect (action, "activate", G_CALLBACK (im_application_list_message_activated), app);
+ g_simple_action_group_insert (app->message_actions, G_ACTION (action));
+
+ {
+ GVariant *entry;
+ GSimpleActionGroup *action_group;
+ GVariantBuilder actions_builder;
+
+ g_variant_builder_init (&actions_builder, G_VARIANT_TYPE ("aa{sv}"));
+ action_group = g_simple_action_group_new ();
+
+ while ((entry = g_variant_iter_next_value (action_iter)))
+ {
+ const gchar *name;
+ GSimpleAction *action;
+ GVariant *label;
+ const gchar *type = NULL;
+ GVariant *hint;
+ GVariantBuilder dict_builder;
+ gchar *prefixed_name;
+
+ if (!g_variant_lookup (entry, "name", "&s", &name))
+ {
+ g_warning ("action dictionary for message '%s' is missing 'name' key", id);
+ continue;
+ }
+
+ label = g_variant_lookup_value (entry, "label", G_VARIANT_TYPE_STRING);
+ g_variant_lookup (entry, "parameter-type", "&g", &type);
+ hint = g_variant_lookup_value (entry, "parameter-hint", NULL);
+
+ action = g_simple_action_new (name, type ? G_VARIANT_TYPE (type) : NULL);
+ g_object_set_data_full (G_OBJECT (action), "message", g_strdup (id), g_free);
+ g_signal_connect (action, "activate", G_CALLBACK (im_application_list_sub_message_activated), app);
+ g_simple_action_group_insert (action_group, G_ACTION (action));
+
+ g_variant_builder_init (&dict_builder, G_VARIANT_TYPE ("a{sv}"));
+
+ prefixed_name = g_strjoin (".", app->id, "msg-actions", id, name, NULL);
+ g_variant_builder_add (&dict_builder, "{sv}", "name", g_variant_new_string (prefixed_name));
+
+ if (label)
+ {
+ g_variant_builder_add (&dict_builder, "{sv}", "label", label);
+ g_variant_unref (label);
+ }
+
+ if (type)
+ g_variant_builder_add (&dict_builder, "{sv}", "parameter-type", g_variant_new_string (type));
+
+ if (hint)
+ {
+ g_variant_builder_add (&dict_builder, "{sv}", "parameter-hint", hint);
+ g_variant_unref (hint);
+ }
+
+ g_variant_builder_add (&actions_builder, "a{sv}", &dict_builder);
+
+ g_object_unref (action);
+ g_variant_unref (entry);
+ g_free (prefixed_name);
+ }
+
+ g_action_muxer_insert (app->message_sub_actions, id, G_ACTION_GROUP (action_group));
+ actions = g_variant_builder_end (&actions_builder);
+
+ g_object_unref (action_group);
+ }
+
+ im_application_list_update_draws_attention (app->list);
+
+ g_signal_emit (app->list, signals[MESSAGE_ADDED], 0,
+ app->id, app_iconstr, id, iconstr, title,
+ subtitle, body, actions, time, draws_attention);
+
+ g_variant_iter_free (action_iter);
+ g_free (app_iconstr);
+ g_object_unref (action);
+}
+
+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->source_actions);
+ g_object_unref (app->message_actions);
+ g_object_unref (app->message_sub_actions);
+ app->source_actions = g_simple_action_group_new ();
+ app->message_actions = g_simple_action_group_new ();
+ app->message_sub_actions = g_action_muxer_new ();
+ g_action_muxer_insert (app->actions, "src", G_ACTION_GROUP (app->source_actions));
+ g_action_muxer_insert (app->actions, "msg", G_ACTION_GROUP (app->message_actions));
+ g_action_muxer_insert (app->actions, "msg-actions", G_ACTION_GROUP (app->message_sub_actions));
+
+ im_application_list_update_draws_attention (app->list);
+
+ if (was_running)
+ g_signal_emit (app->list, signals[APP_STOPPED], 0, app->id);
+}
+
+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->proxy)
+ {
+ if (error->code != G_IO_ERROR_CANCELLED)
+ 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 = im_application_list_lookup (list, id);
+ if (!app)
+ {
+ g_warning ("'%s' is not a registered application", id);
+ return;
+ }
+
+ if (app->cancellable)
+ {
+ gchar *name_owner = NULL;
+
+ if (app->proxy)
+ name_owner = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (app->proxy));
+ g_warning ("replacing '%s' at %s with %s", id, name_owner, unique_bus_name);
+
+ im_application_list_unset_remote (app);
+
+ g_free (name_owner);
+ }
+
+ 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..3d5f94d
--- /dev/null
+++ b/src/im-phone-menu.c
@@ -0,0 +1,331 @@
+/*
+ * 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_update_toplevel (ImPhoneMenu *menu)
+{
+ if (g_menu_model_get_n_items (G_MENU_MODEL (menu->message_section)) ||
+ g_menu_model_get_n_items (G_MENU_MODEL (menu->source_section)))
+ {
+ if (g_menu_model_get_n_items (G_MENU_MODEL (menu->toplevel_menu)) == 0)
+ {
+ GMenuItem *item;
+
+ 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));
+
+ item = g_menu_item_new ("Clear All", "remove-all");
+ g_menu_item_set_attribute (item, "x-canonical-type", "s", "com.canonical.indicator.button");
+ g_menu_append_item (menu->toplevel_menu, item);
+
+ g_object_unref (item);
+ }
+ }
+ else
+ {
+ while (g_menu_model_get_n_items (G_MENU_MODEL (menu->toplevel_menu)))
+ g_menu_remove (menu->toplevel_menu, 0);
+ }
+}
+
+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->toplevel_menu = g_menu_new ();
+ menu->message_section = g_menu_new ();
+ menu->source_section = g_menu_new ();
+
+ g_signal_connect_swapped (menu->message_section, "items-changed",
+ G_CALLBACK (im_phone_menu_update_toplevel), menu);
+ g_signal_connect_swapped (menu->source_section, "items-changed",
+ G_CALLBACK (im_phone_menu_update_toplevel), menu);
+
+ im_phone_menu_update_toplevel (menu);
+}
+
+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);
+}
+
+static gint64
+im_phone_menu_get_message_time (GMenuModel *model,
+ gint i)
+{
+ gint64 time;
+
+ g_menu_model_get_item_attribute (model, i, "x-canonical-time", "x", &time);
+
+ return time;
+}
+
+void
+im_phone_menu_add_message (ImPhoneMenu *menu,
+ const gchar *app_id,
+ const gchar *app_icon,
+ const gchar *id,
+ const gchar *iconstr,
+ const gchar *title,
+ const gchar *subtitle,
+ const gchar *body,
+ GVariant *actions,
+ gint64 time)
+{
+ GMenuItem *item;
+ gchar *action_name;
+ gint n_messages;
+ gint pos;
+
+ g_return_if_fail (IM_IS_PHONE_MENU (menu));
+ g_return_if_fail (app_id);
+
+ action_name = g_strconcat (app_id, ".msg.", 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);
+
+ if (app_icon)
+ g_menu_item_set_attribute (item, "x-canonical-app-icon", "s", app_icon);
+
+ if (actions)
+ g_menu_item_set_attribute (item, "x-canonical-message-actions", "v", actions);
+
+ n_messages = g_menu_model_get_n_items (G_MENU_MODEL (menu->message_section));
+ pos = 0;
+ while (pos < n_messages &&
+ time < im_phone_menu_get_message_time (G_MENU_MODEL (menu->message_section), pos))
+ pos++;
+
+ g_menu_insert_item (menu->message_section, pos, item);
+
+ g_free (action_name);
+ g_object_unref (item);
+}
+
+void
+im_phone_menu_remove_message (ImPhoneMenu *menu,
+ const gchar *app_id,
+ const gchar *id)
+{
+ gchar *action_name;
+
+ g_return_if_fail (IM_IS_PHONE_MENU (menu));
+ g_return_if_fail (app_id != NULL);
+
+ action_name = g_strconcat (app_id, ".msg.", 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,
+ const gchar *app_id,
+ 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 (app_id != NULL);
+
+ action_name = g_strconcat (app_id, ".src.", 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_prepend_item (menu->source_section, item);
+
+ g_free (action_name);
+ g_object_unref (item);
+}
+
+void
+im_phone_menu_remove_source (ImPhoneMenu *menu,
+ const gchar *app_id,
+ const gchar *id)
+{
+ gchar *action_name;
+
+ g_return_if_fail (IM_IS_PHONE_MENU (menu));
+ g_return_if_fail (app_id != NULL);
+
+ action_name = g_strconcat (app_id, ".src.", 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,
+ const gchar *app_id)
+{
+ gchar *prefix;
+ gint n_items;
+ gint i = 0;
+
+ prefix = g_strconcat (app_id, ".", 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,
+ const gchar *app_id)
+{
+ g_return_if_fail (IM_IS_PHONE_MENU (menu));
+ g_return_if_fail (app_id != NULL);
+
+ im_phone_menu_remove_all_for_app (menu->source_section, app_id);
+ im_phone_menu_remove_all_for_app (menu->message_section, app_id);
+}
+
+void
+im_phone_menu_remove_all (ImPhoneMenu *menu)
+{
+ g_return_if_fail (IM_IS_PHONE_MENU (menu));
+
+ while (g_menu_model_get_n_items (G_MENU_MODEL (menu->toplevel_menu)))
+ g_menu_remove (menu->toplevel_menu, 0);
+
+ g_object_unref (menu->message_section);
+ g_object_unref (menu->source_section);
+
+ menu->message_section = g_menu_new ();
+ menu->source_section = g_menu_new ();
+
+ g_signal_connect_swapped (menu->message_section, "items-changed",
+ G_CALLBACK (im_phone_menu_update_toplevel), menu);
+ g_signal_connect_swapped (menu->source_section, "items-changed",
+ G_CALLBACK (im_phone_menu_update_toplevel), menu);
+}
diff --git a/src/im-phone-menu.h b/src/im-phone-menu.h
new file mode 100644
index 0000000..258ce73
--- /dev/null
+++ b/src/im-phone-menu.h
@@ -0,0 +1,70 @@
+/*
+ * 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>
+
+#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,
+ const gchar *app_id,
+ const gchar *app_icon,
+ const gchar *id,
+ const gchar *iconstr,
+ const gchar *title,
+ const gchar *subtitle,
+ const gchar *body,
+ GVariant *actions,
+ gint64 time);
+
+void im_phone_menu_remove_message (ImPhoneMenu *menu,
+ const gchar *app_id,
+ const gchar *id);
+
+void im_phone_menu_add_source (ImPhoneMenu *menu,
+ const gchar *app_id,
+ const gchar *id,
+ const gchar *label,
+ const gchar *iconstr);
+
+void im_phone_menu_remove_source (ImPhoneMenu *menu,
+ const gchar *app_id,
+ const gchar *id);
+
+void im_phone_menu_remove_application (ImPhoneMenu *menu,
+ const gchar *app_id);
+
+void im_phone_menu_remove_all (ImPhoneMenu *menu);
+
+#endif
diff --git a/src/messages-service.c b/src/messages-service.c
index 8f64478..5f320fc 100644
--- a/src/messages-service.c
+++ b/src/messages-service.c
@@ -23,418 +23,25 @@ with this program. If not, see <http://www.gnu.org/licenses/>.
#include <config.h>
#include <locale.h>
-#include <libindicator/indicator-service.h>
#include <gio/gio.h>
-#include <gio/gdesktopappinfo.h>
#include <glib/gi18n.h>
-#include "app-section.h"
#include "dbus-data.h"
-#include "gactionmuxer.h"
#include "gsettingsstrv.h"
#include "gmenuutils.h"
#include "indicator-messages-service.h"
+#include "indicator-messages-application.h"
+#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 GMenuModel *chat_section;
+static ImPhoneMenu *menu;
static GSettings *settings;
-static gboolean draws_attention;
-static const gchar *global_status[6]; /* max 5: available, away, busy, invisible, offline */
-
-static gchar *
-indicator_messages_get_icon_name ()
-{
- GString *name;
- GIcon *icon;
- gchar *iconstr;
-
- name = g_string_new ("indicator-messages");
-
- if (global_status[0] != NULL)
- {
- if (global_status[1] != NULL)
- g_string_append (name, "-mixed");
- else
- g_string_append_printf (name, "-%s", global_status[0]);
- }
-
- if (draws_attention)
- g_string_append (name, "-new");
-
- icon = g_themed_icon_new (name->str);
- g_themed_icon_append_name (G_THEMED_ICON (icon),
- draws_attention ? "indicator-messages-new"
- : "indicator-messages");
-
- iconstr = g_icon_to_string (icon);
-
- g_object_unref (icon);
- g_string_free (name, TRUE);
-
- 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)
-{
- 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);
-}
-
-
-static gboolean
-app_section_draws_attention (gpointer key,
- gpointer value,
- gpointer user_data)
-{
- AppSection *section = value;
- return app_section_get_draws_attention (section);
-}
-
-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,
- gpointer user_data)
-{
- AppSection *section = value;
- return app_section_get_uses_chat_status (section);
-}
-
-static void
-update_chat_section ()
-{
- 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);
- }
-
- if (first_section != NULL)
- g_object_unref (first_section);
-
- indicator_messages_update_icon ();
-}
-
-static void
-uses_chat_status_changed (GObject *object,
- GParamSpec *pspec,
- 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;
-}
-
-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) &section) &&
- pos < G_N_ELEMENTS (global_status))
- {
- 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);
- }
-
- 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_free (id);
- g_object_unref (appinfo);
- return section;
-}
-
-static void
-remove_application (const char *desktop_id)
-{
- 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);
- 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_idle_add(build_launcher, g_strdup (*app));
- }
-
- 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);
-}
-
-static void
-app_section_remove_attention (gpointer key,
- gpointer value,
- 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);
-}
-
-static void
-status_action_activate (GSimpleAction *action,
- GVariant *parameter,
- gpointer user_data)
-{
- const gchar *status;
-
- status = g_variant_get_string (parameter, NULL);
-
- indicator_messages_service_emit_status_changed (messages_service, status);
-}
static void
register_application (IndicatorMessagesService *service,
@@ -443,18 +50,15 @@ register_application (IndicatorMessagesService *service,
const gchar *menu_path,
gpointer user_data)
{
- AppSection *section;
GDBusConnection *bus;
const gchar *sender;
- section = add_application (desktop_id);
- if (!section)
- 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);
- app_section_set_object_path (section, bus, sender, menu_path);
+ 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);
@@ -466,137 +70,29 @@ unregister_application (IndicatorMessagesService *service,
const gchar *desktop_id,
gpointer user_data)
{
- remove_application (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);
}
static void
-set_status (IndicatorMessagesService *service,
- GDBusMethodInvocation *invocation,
- const gchar *desktop_id,
- const gchar *status_str,
- gpointer user_data)
+on_bus_acquired (GDBusConnection *bus,
+ const gchar *name,
+ 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;
- }
-
- 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);
-}
-
-static GSimpleActionGroup *
-create_action_group (void)
-{
- GSimpleActionGroup *actions;
- GSimpleAction *messages;
- GSimpleAction *clear;
- GSimpleAction *status;
- const gchar *default_status[] = { "offline", NULL };
- gchar *icon;
-
- actions = g_simple_action_group_new ();
-
- /* state of the messages action is its icon name */
- icon = indicator_messages_get_icon_name ();
- messages = g_simple_action_new_stateful ("messages", G_VARIANT_TYPE ("s"),
- g_variant_new_string (icon));
-
- status = g_simple_action_new_stateful ("status", G_VARIANT_TYPE ("s"),
- g_variant_new_strv (default_status, -1));
- g_signal_connect (status, "activate", G_CALLBACK (status_action_activate), NULL);
-
- clear = g_simple_action_new ("clear", NULL);
- g_simple_action_set_enabled (clear, FALSE);
- g_signal_connect (clear, "activate", G_CALLBACK (clear_action_activate), NULL);
-
- g_simple_action_group_insert (actions, G_ACTION (messages));
- g_simple_action_group_insert (actions, G_ACTION (status));
- g_simple_action_group_insert (actions, G_ACTION (clear));
-
- g_free (icon);
- 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,
- gpointer user_data)
-{
- GDBusConnection *bus;
GError *error = NULL;
- bus = g_bus_get_finish (res, &error);
- if (!bus) {
- g_warning ("unable to connect to the session bus: %s", error->message);
- g_error_free (error);
- return;
- }
-
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);
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);
@@ -616,17 +112,29 @@ got_bus (GObject *object,
g_object_unref (bus);
}
+static void
+on_name_lost (GDBusConnection *bus,
+ const gchar *name,
+ gpointer user_data)
+{
+ GMainLoop *mainloop = user_data;
+
+ g_main_loop_quit (mainloop);
+}
+
int
main (int argc, char ** argv)
{
GMainLoop * mainloop = NULL;
- IndicatorService * service = NULL;
+ GMenuItem *root;
+ GBusNameOwnerFlags flags;
- mainloop = g_main_loop_new (NULL, FALSE);
+ /* Glib init */
+#if G_ENCODE_VERSION(GLIB_MAJOR_VERSION, GLIB_MINOR_VERSION) <= GLIB_VERSION_2_34
+ g_type_init();
+#endif
- /* Create the Indicator Service interface */
- service = indicator_service_new_version(INDICATOR_MESSAGES_DBUS_NAME, 1);
- g_signal_connect(service, INDICATOR_SERVICE_SIGNAL_SHUTDOWN, G_CALLBACK(service_shutdown), mainloop);
+ mainloop = g_main_loop_new (NULL, FALSE);
/* Setting up i18n and gettext. Apparently, we need
all of these. */
@@ -637,38 +145,44 @@ main (int argc, char ** argv)
/* Bring up the service DBus interface */
messages_service = indicator_messages_service_skeleton_new ();
- g_bus_get (G_BUS_TYPE_SESSION, NULL, got_bus, NULL);
+ flags = G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT;
+ if (argc >= 2 && g_str_equal (argv[1], "--replace"))
+ flags |= G_BUS_NAME_OWNER_FLAGS_REPLACE;
- actions = create_action_group ();
-
- action_muxer = g_action_muxer_new ();
- g_action_muxer_insert (action_muxer, NULL, G_ACTION_GROUP (actions));
+ g_bus_own_name (G_BUS_TYPE_SESSION, "com.canonical.indicator.messages", flags,
+ on_bus_acquired, NULL, on_name_lost, mainloop, NULL);
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);
- 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");
+ menu = im_phone_menu_new ();
toplevel_menu = g_menu_new ();
+ root = g_menu_item_new (NULL, "messages");
+ g_menu_item_set_attribute (root, "x-canonical-type", "s", "com.canonical.indicator.root");
+ g_menu_item_set_submenu (root, im_phone_menu_get_model (menu));
+ g_menu_append_item (toplevel_menu, root);
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);
+ applications = im_application_list_new ();
+ 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_signal_connect_swapped (applications, "remove-all",
+ G_CALLBACK (im_phone_menu_remove_all), menu);
g_main_loop_run(mainloop);
/* Clean up */
+ g_object_unref (root);
g_object_unref (messages_service);
- g_object_unref (chat_section);
g_object_unref (settings);
- g_hash_table_unref (applications);
+ g_object_unref (applications);
return 0;
}
diff --git a/src/messages-service.xml b/src/messages-service.xml
deleted file mode 100644
index 00ae154..0000000
--- a/src/messages-service.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<node name="/">
- <interface name="com.canonical.indicator.messages.service">
-
- <method name="RegisterApplication">
- <arg type="s" name="desktop_id" direction="in" />
- <arg type="o" name="menu_path" direction="in" />
- </method>
-
- <method name="UnregisterApplication">
- <arg type="s" name="desktop_id" direction="in" />
- </method>
-
- <method name="SetStatus">
- <arg type="s" name="desktop_id" direction="in" />
- <arg type="s" name="status" direction="in" />
- </method>
-
- <signal name="StatusChanged">
- <arg type="s" name="status" direction="in" />
- </signal>
-
- </interface>
-</node>