From 24ee0b6602960f35712adbf668099e06b8ebfb25 Mon Sep 17 00:00:00 2001 From: Lars Uebernickel Date: Tue, 18 Sep 2012 21:54:37 +0200 Subject: Set the global chat status more intelligently Up until now, the global chat status was set every time an application called _set_status. Thus, global status really meant "status of the app that last changed the status". Now, the service remembers the chat status for each application and sets the global status as a combination of all of application statuses. If applications have different statuses, the menu items are shown in an inconsistent state. This is implemented in IdoMenuItem by making it accept state as an array of strings in addition to a single string. It is drawn inconsistent if the state contains the menu item's target value in addition to other values. When the global status is changed through the messaging menu, the service doesn't update the action immediately anymore. Instead, it notifies all applications about the change via the "status-changed" signal. Applications must call _set_state to acknowledge that they have indeed changed their state. This is consistent with libmessaging-menu's documentation and design. Also, the SetStatus D-Bus call was missing a "desktop-id" parameter to tell the menu which application changed status. Changing this doesn't break existing apps, as the D-Bus interface is considered private to indicator-messages. --- src/app-section.c | 51 +++++++++++++++++++ src/app-section.h | 3 ++ src/ido-menu-item.c | 34 ++++++++++++- src/messages-service.c | 125 ++++++++++++++++++++++++++++++----------------- src/messages-service.xml | 1 + 5 files changed, 167 insertions(+), 47 deletions(-) (limited to 'src') diff --git a/src/app-section.c b/src/app-section.c index 523e249..6aac52a 100644 --- a/src/app-section.c +++ b/src/app-section.c @@ -50,6 +50,7 @@ struct _AppSectionPrivate gboolean draws_attention; gboolean uses_chat_status; + gchar *chat_status; guint name_watch_id; }; @@ -60,6 +61,7 @@ enum { PROP_ACTIONS, PROP_DRAWS_ATTENTION, PROP_USES_CHAT_STATUS, + PROP_CHAT_STATUS, NUM_PROPERTIES }; @@ -78,6 +80,7 @@ static void app_section_set_property (GObject *object, const GValue *value, GParamSpec *pspec); static void app_section_dispose (GObject *object); +static void app_section_finalize (GObject *object); static void activate_cb (GSimpleAction *action, GVariant *param, gpointer userdata); @@ -118,6 +121,7 @@ app_section_class_init (AppSectionClass *klass) object_class->get_property = app_section_get_property; object_class->set_property = app_section_set_property; object_class->dispose = app_section_dispose; + object_class->finalize = app_section_finalize; properties[PROP_APPINFO] = g_param_spec_object ("app-info", "AppInfo", @@ -143,6 +147,13 @@ app_section_class_init (AppSectionClass *klass) FALSE, G_PARAM_READABLE); + properties[PROP_CHAT_STATUS] = g_param_spec_string ("chat-status", + "Chat status", + "Current chat status of the application", + NULL, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + g_object_class_install_properties (object_class, NUM_PROPERTIES, properties); destroy_signal = g_signal_new ("destroy", @@ -199,6 +210,10 @@ app_section_get_property (GObject *object, g_value_set_boolean (value, app_section_get_uses_chat_status (self)); break; + case PROP_CHAT_STATUS: + g_value_set_string (value, app_section_get_status (self)); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } @@ -218,6 +233,10 @@ app_section_set_property (GObject *object, app_section_set_app_info (self, g_value_get_object (value)); break; + case PROP_CHAT_STATUS: + app_section_set_status (self, g_value_get_string (value)); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } @@ -261,6 +280,16 @@ app_section_dispose (GObject *object) G_OBJECT_CLASS (app_section_parent_class)->dispose (object); } +static void +app_section_finalize (GObject *object) +{ + AppSection * self = APP_SECTION(object); + + g_free (self->priv->chat_status); + + G_OBJECT_CLASS (app_section_parent_class)->dispose (object); +} + /* Respond to one of the shortcuts getting clicked on. */ static void nick_activate_cb (GSimpleAction *action, @@ -668,10 +697,12 @@ app_section_unset_object_path (AppSection *self) } priv->draws_attention = FALSE; + g_clear_pointer (&priv->chat_status, g_free); g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ACTIONS]); g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DRAWS_ATTENTION]); g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_USES_CHAT_STATUS]); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CHAT_STATUS]); g_action_group_change_action_state (G_ACTION_GROUP (priv->static_shortcuts), "launch", g_variant_new_boolean (FALSE)); @@ -763,3 +794,23 @@ app_section_get_uses_chat_status (AppSection *self) return priv->uses_chat_status; } + +const gchar * +app_section_get_status (AppSection *self) +{ + AppSectionPrivate * priv = self->priv; + + return priv->chat_status; +} + +void +app_section_set_status (AppSection *self, + const gchar *status) +{ + AppSectionPrivate * priv = self->priv; + + g_free (priv->chat_status); + priv->chat_status = g_strdup (status); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CHAT_STATUS]); +} diff --git a/src/app-section.h b/src/app-section.h index 09697c5..7c39e8e 100644 --- a/src/app-section.h +++ b/src/app-section.h @@ -63,6 +63,9 @@ void app_section_set_object_path (AppSection *self, const gchar *object_path); void app_section_unset_object_path (AppSection *self); gboolean app_section_get_uses_chat_status (AppSection *self); +const gchar * app_section_get_status (AppSection *self); +void app_section_set_status (AppSection *self, + const gchar *status); G_END_DECLS diff --git a/src/ido-menu-item.c b/src/ido-menu-item.c index 6b19d2a..32044ff 100644 --- a/src/ido-menu-item.c +++ b/src/ido-menu-item.c @@ -99,8 +99,37 @@ ido_menu_item_set_state (IdoMenuItem *self, if (priv->target) { ido_menu_item_set_has_indicator (self, TRUE); + ido_menu_item_set_active (self, FALSE); gtk_check_menu_item_set_draw_as_radio (GTK_CHECK_MENU_ITEM (self), TRUE); - ido_menu_item_set_active (self, g_variant_equal (priv->target, state)); + gtk_check_menu_item_set_inconsistent (GTK_CHECK_MENU_ITEM (self), FALSE); + + if (g_variant_is_of_type (state, G_VARIANT_TYPE_STRING)) + { + ido_menu_item_set_active (self, g_variant_equal (priv->target, state)); + } + else if (g_variant_is_of_type (state, G_VARIANT_TYPE ("as")) && + g_variant_is_of_type (priv->target, G_VARIANT_TYPE_STRING)) + { + const gchar *target_str; + const gchar **state_strs; + const gchar **it; + + target_str = g_variant_get_string (priv->target, NULL); + state_strs = g_variant_get_strv (state, NULL); + + it = state_strs; + while (*it != NULL && !g_str_equal (*it, target_str)) + it++; + + if (*it != NULL) + { + ido_menu_item_set_active (self, TRUE); + gtk_check_menu_item_set_inconsistent (GTK_CHECK_MENU_ITEM (self), + g_strv_length ((gchar **)state_strs) > 1); + } + + g_free (state_strs); + } } else if (g_variant_is_of_type (state, G_VARIANT_TYPE_BOOLEAN)) { @@ -258,7 +287,8 @@ ido_menu_item_activate (GtkMenuItem *item) if (!priv->in_set_active && priv->action && priv->action_group) g_action_group_activate_action (priv->action_group, priv->action, priv->target); - GTK_MENU_ITEM_CLASS (ido_menu_item_parent_class)->activate (item); + if (priv->in_set_active) + GTK_MENU_ITEM_CLASS (ido_menu_item_parent_class)->activate (item); } static void diff --git a/src/messages-service.c b/src/messages-service.c index fd0bdcb..821598f 100644 --- a/src/messages-service.c +++ b/src/messages-service.c @@ -144,6 +144,52 @@ uses_chat_status_changed (GObject *object, 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; + const gchar *statuses[] = { NULL, NULL, NULL, NULL, NULL, NULL }; + int pos = 0; + GAction *status; + + g_hash_table_iter_init (&iter, applications); + while (g_hash_table_iter_next (&iter, NULL, (gpointer) §ion)) { + const gchar *status_str = NULL; + + status_str = app_section_get_status (section); + if (status_str != NULL && !strv_contains (statuses, status_str)) + statuses[pos++] = status_str; + } + + + 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 (statuses, -1)); +} + +static void +chat_status_changed (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + update_chat_status (); +} + static void remove_section (AppSection *section, const gchar *id) @@ -156,6 +202,7 @@ remove_section (AppSection *section, 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); @@ -165,6 +212,7 @@ remove_section (AppSection *section, g_menu_remove (toplevel_menu, 0); } + update_chat_status (); update_chat_section (); } @@ -197,6 +245,8 @@ add_application (const gchar *desktop_id) 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), @@ -322,48 +372,15 @@ clear_action_activate (GSimpleAction *simple, } static void -radio_item_activate (GSimpleAction *action, - GVariant *parameter, - gpointer user_data) -{ - g_action_change_state (G_ACTION (action), parameter); -} - -static gboolean -g_action_state_equal (GAction *action, - GVariant *value) -{ - GVariant *state; - gboolean eq; - - state = g_action_get_state (action); - g_return_val_if_fail (state != NULL, FALSE); - - eq = g_variant_equal (state, value); - - g_variant_unref (state); - return eq; -} - -static void -change_status_action (GSimpleAction *action, - GVariant *value, - gpointer user_data) +status_action_activate (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) { const gchar *status; - g_variant_get (value, "&s", &status); + status = g_variant_get_string (parameter, NULL); - g_return_if_fail (g_str_equal (status, "available") || - g_str_equal (status, "away")|| - g_str_equal (status, "busy") || - g_str_equal (status, "invisible") || - g_str_equal (status, "offline")); - - if (!g_action_state_equal (G_ACTION (action), value)) { - g_simple_action_set_state (action, value); - indicator_messages_service_emit_status_changed (messages_service, status); - } + indicator_messages_service_emit_status_changed (messages_service, status); } static void @@ -405,17 +422,35 @@ unregister_application (IndicatorMessagesService *service, static void set_status (IndicatorMessagesService *service, GDBusMethodInvocation *invocation, + const gchar *desktop_id, const gchar *status_str, gpointer user_data) { - GAction *status; + GDesktopAppInfo *appinfo; + gchar *id; + AppSection *section; - status = g_simple_action_group_lookup (actions, "status"); - g_return_if_fail (status != NULL); + 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")); - g_action_change_state (status, g_variant_new_string (status_str)); + 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 * @@ -425,6 +460,7 @@ create_action_group (void) GSimpleAction *messages; GSimpleAction *clear; GSimpleAction *status; + const gchar *default_status[] = { "offline", NULL }; actions = g_simple_action_group_new (); @@ -433,9 +469,8 @@ create_action_group (void) g_variant_new_boolean (FALSE)); status = g_simple_action_new_stateful ("status", G_VARIANT_TYPE ("s"), - g_variant_new ("s", "offline")); - g_signal_connect (status, "activate", G_CALLBACK (radio_item_activate), NULL); - g_signal_connect (status, "change-state", G_CALLBACK (change_status_action), NULL); + 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); diff --git a/src/messages-service.xml b/src/messages-service.xml index edd47c7..00ae154 100644 --- a/src/messages-service.xml +++ b/src/messages-service.xml @@ -12,6 +12,7 @@ + -- cgit v1.2.3 From 4d8755ace7d159d09346828336e2ac7698c16f96 Mon Sep 17 00:00:00 2001 From: Lars Uebernickel Date: Tue, 18 Sep 2012 23:13:46 +0200 Subject: Use indicator-message icons with chat status Changes the state of the "messages" action from a boolean (draws-attention) to a string (icon-name). This has the added benefit that more logic is moved from the plugin into the service. It also fixes an edge case: the messaging menu didn't have the blue icon after the service restarted (if anything was drawing attention). --- src/indicator-messages.c | 39 +++++++++++++++--- src/messages-service.c | 100 +++++++++++++++++++++++++++++++++++------------ 2 files changed, 109 insertions(+), 30 deletions(-) (limited to 'src') diff --git a/src/indicator-messages.c b/src/indicator-messages.c index 2647a76..db4f634 100644 --- a/src/indicator-messages.c +++ b/src/indicator-messages.c @@ -87,6 +87,9 @@ static void menu_items_changed (GMenuModel *menu, gint removed, gint added, gpointer user_data); +static void messages_action_added (GActionGroup *action_group, + gchar *action_name, + gpointer user_data); static void messages_state_changed (GActionGroup *action_group, gchar *action_name, GVariant *value, @@ -174,6 +177,7 @@ static void service_connection_changed (IndicatorServiceManager *sm, GError *error = NULL; if (self->actions != NULL) { + g_signal_handlers_disconnect_by_func (self->actions, messages_action_added, self); g_signal_handlers_disconnect_by_func (self->actions, messages_state_changed, self); g_clear_object (&self->actions); } @@ -199,6 +203,8 @@ static void service_connection_changed (IndicatorServiceManager *sm, gtk_widget_insert_action_group (self->gtkmenu, get_name_hint (INDICATOR_OBJECT (self)), self->actions); + g_signal_connect (self->actions, "action-added::messages", + G_CALLBACK (messages_action_added), self); g_signal_connect (self->actions, "action-state-changed::messages", G_CALLBACK (messages_state_changed), self); @@ -330,6 +336,32 @@ menu_items_changed (GMenuModel *menu, indicator_object_set_visible (INDICATOR_OBJECT (self), FALSE); } +static void +indicator_messages_update_icon (IndicatorMessages *self, + GVariant *state) +{ + g_return_if_fail (g_variant_is_of_type (state, G_VARIANT_TYPE_STRING)); + + gtk_image_set_from_icon_name (GTK_IMAGE (self->image), + g_variant_get_string (state, NULL), + GTK_ICON_SIZE_MENU); + +} + +static void +messages_action_added (GActionGroup *action_group, + gchar *action_name, + gpointer user_data) +{ + IndicatorMessages *self = user_data; + GVariant *state; + + state = g_action_group_get_action_state (action_group, "messages"); + indicator_messages_update_icon (self, state); + + g_variant_unref (state); +} + static void messages_state_changed (GActionGroup *action_group, gchar *action_name, @@ -338,10 +370,5 @@ messages_state_changed (GActionGroup *action_group, { IndicatorMessages *self = user_data; - g_return_if_fail (g_variant_is_of_type (value, G_VARIANT_TYPE_BOOLEAN)); - - if (g_variant_get_boolean (value)) - gtk_image_set_from_icon_name (GTK_IMAGE (self->image), "indicator-messages-new", GTK_ICON_SIZE_MENU); - else - gtk_image_set_from_icon_name (GTK_IMAGE (self->image), "indicator-messages", GTK_ICON_SIZE_MENU); + indicator_messages_update_icon (self, value); } diff --git a/src/messages-service.c b/src/messages-service.c index 821598f..f71614c 100644 --- a/src/messages-service.c +++ b/src/messages-service.c @@ -35,6 +35,8 @@ with this program. If not, see . #include "gmenuutils.h" #include "indicator-messages-service.h" +#define NUM_STATUSES 5 + static GHashTable *applications; IndicatorMessagesService *messages_service; @@ -44,6 +46,52 @@ static GMenu *toplevel_menu; static GMenu *menu; static GMenuModel *chat_section; static GSettings *settings; +gboolean draws_attention; +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); + 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) @@ -91,18 +139,16 @@ draws_attention_changed (GObject *object, GParamSpec *pspec, gpointer user_data) { - GSimpleAction *messages; GSimpleAction *clear; - gboolean attention; - messages = G_SIMPLE_ACTION (g_simple_action_group_lookup (actions, "messages")); clear = G_SIMPLE_ACTION (g_simple_action_group_lookup (actions, "clear")); - g_return_if_fail (messages != NULL && clear != NULL); + g_return_if_fail (clear != NULL); + + draws_attention = g_hash_table_find (applications, app_section_draws_attention, NULL) != NULL; - attention = g_hash_table_find (applications, app_section_draws_attention, NULL) != NULL; + g_simple_action_set_enabled (clear, draws_attention); - g_simple_action_set_state (messages, g_variant_new_boolean (attention)); - g_simple_action_set_enabled (clear, attention); + indicator_messages_update_icon (); } static gboolean @@ -134,6 +180,8 @@ update_chat_section () if (first_section != NULL) g_object_unref (first_section); + + indicator_messages_update_icon (); } static void @@ -162,24 +210,33 @@ update_chat_status () { GHashTableIter iter; AppSection *section; - const gchar *statuses[] = { NULL, NULL, NULL, NULL, NULL, NULL }; - int pos = 0; + int pos; GAction *status; + for (pos = 0; pos < G_N_ELEMENTS (global_status); pos++) + global_status[pos] = NULL; + + pos = 0; g_hash_table_iter_init (&iter, applications); - while (g_hash_table_iter_next (&iter, NULL, (gpointer) §ion)) { + while (g_hash_table_iter_next (&iter, NULL, (gpointer) §ion) && + pos < G_N_ELEMENTS (global_status)) + { const gchar *status_str = NULL; status_str = app_section_get_status (section); - if (status_str != NULL && !strv_contains (statuses, status_str)) - statuses[pos++] = status_str; + 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 (statuses, -1)); + g_simple_action_set_state (G_SIMPLE_ACTION (status), g_variant_new_strv (global_status, -1)); + + indicator_messages_update_icon (); } static void @@ -262,21 +319,13 @@ add_application (const gchar *desktop_id) if (g_menu_model_get_n_items (G_MENU_MODEL (toplevel_menu)) == 0) { GMenuItem *header; - GIcon *icon; - gchar *iconstr; - - icon = g_themed_icon_new ("indicator-messages"); - iconstr = g_icon_to_string (icon); 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-icon", "s", iconstr); 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 (iconstr); - g_object_unref (icon); } g_free (id); @@ -461,12 +510,14 @@ create_action_group (void) GSimpleAction *clear; GSimpleAction *status; const gchar *default_status[] = { "offline", NULL }; + gchar *icon; actions = g_simple_action_group_new (); - /* state of the messages action mirrors "draws-attention" */ - messages = g_simple_action_new_stateful ("messages", G_VARIANT_TYPE ("b"), - g_variant_new_boolean (FALSE)); + /* 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)); @@ -480,6 +531,7 @@ create_action_group (void) g_simple_action_group_insert (actions, G_ACTION (status)); g_simple_action_group_insert (actions, G_ACTION (clear)); + g_free (icon); return actions; } -- cgit v1.2.3 From c03005628b210cc7b775088d4693a87ca13f99e4 Mon Sep 17 00:00:00 2001 From: Lars Uebernickel Date: Wed, 19 Sep 2012 08:28:17 +0200 Subject: messages-service: define all global variables as static --- src/messages-service.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/messages-service.c b/src/messages-service.c index f71614c..73f3d35 100644 --- a/src/messages-service.c +++ b/src/messages-service.c @@ -39,15 +39,15 @@ with this program. If not, see . static GHashTable *applications; -IndicatorMessagesService *messages_service; +static IndicatorMessagesService *messages_service; static GSimpleActionGroup *actions; static GActionMuxer *action_muxer; static GMenu *toplevel_menu; static GMenu *menu; static GMenuModel *chat_section; static GSettings *settings; -gboolean draws_attention; -const gchar *global_status[6]; /* max 5: available, away, busy, invisible, offline */ +static gboolean draws_attention; +static const gchar *global_status[6]; /* max 5: available, away, busy, invisible, offline */ static gchar * indicator_messages_get_icon_name () -- cgit v1.2.3