/* * 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 . * * Authors: * Lars Uebernickel */ #include "messaging-menu.h" #include "indicator-messages-service.h" #include /** * SECTION:messaging-menu * @title: MessagingMenuApp * @short_description: An application section in the messaging menu * @include: messaging-menu.h * * A #MessagingMenuApp represents an application section in the * Messaging Menu. An application section is tied to an installed * application through a desktop file id, which must be passed to * messaging_menu_app_new(). * * To register the application with the Messaging Menu, call * messaging_menu_app_register(). This signifies that the application * should be present in the menu and be marked as "running". * * The first menu item in an application section represents the * application itself, using the name and icon found in the associated * desktop file. Activating this item starts the application. * * Following the application item, the Messaging Menu inserts all * shortcut actions found in the desktop file. Actions whose * NotShowIn keyword contains "Messaging Menu" or whose * OnlyShowIn keyword does not contain "Messaging Menu" * will not appear (the * desktop file specification contains a detailed explanation of * shortcut actions.) An application cannot add, remove, or change * these shortcut items while it is running. * * Next, an application section contains menu items for message sources. * What exactly constitutes a message source depends on the type of * application: an email client's message sources are folders * containing new messages, while those of a chat program are persons * that have contacted the user. * * A message source is represented in the menu by a label and optionally * also an icon. It can be associated with either a count, a time, or * an arbitrary string, which will appear on the right side of the menu * item. * * When the user activates a source, the source is immediately removed * from the menu and the "activate-source" signal is emitted. * * Applications should always expose all the message sources available. * However, the Messaging Menu might limit the amount of sources it * displays to the user. * * The Messaging Menu offers users a way to set their chat status * (available, away, busy, invisible, or offline) for multiple * applications at once. Applications that appear in the Messaging Menu * can integrate with this by setting the * "X-MessagingMenu-UsesChatSection" key in their desktop file to True. * Use messaging_menu_app_set_status() to signify that the application's * chat status has changed. When the user changes status through the * Messaging Menu, the ::status-changed signal will be emitted. * * If the application stops running without calling * messaging_menu_app_unregister(), it will be marked as "not running". * Its application and shortcut items stay in the menu, but all message * sources are removed. If messaging_menu_app_unregister() is called, * the application section is removed completely. * * More information about the design and recommended usage of the * Messaging Menu is available at https://wiki.ubuntu.com/MessagingMenu. */ /** * MessagingMenuApp: * * #MessagingMenuApp is an opaque structure. */ struct _MessagingMenuApp { GObject parent_instance; GDesktopAppInfo *appinfo; int registered; /* -1 for unknown */ MessagingMenuStatus status; gboolean status_set; GSimpleActionGroup *source_actions; GMenu *menu; GDBusConnection *bus; IndicatorMessagesService *messages_service; guint watch_id; guint action_export_id; guint menu_export_id; GCancellable *cancellable; }; G_DEFINE_TYPE (MessagingMenuApp, messaging_menu_app, G_TYPE_OBJECT); enum { PROP_0, PROP_DESKTOP_ID, N_PROPERTIES }; enum { ACTIVATE_SOURCE, STATUS_CHANGED, N_SIGNALS }; static GParamSpec *properties[N_PROPERTIES]; static guint signals[N_SIGNALS]; static const gchar *status_ids[] = { "available", "away", "busy", "invisible", "offline" }; static void global_status_changed (IndicatorMessagesService *service, const gchar *status_str, gpointer user_data); static gchar * messaging_menu_app_get_dbus_object_path (MessagingMenuApp *app) { gchar *path; if (!app->appinfo) return NULL; path = g_strconcat ("/com/canonical/indicator/messages/", g_app_info_get_id (G_APP_INFO (app->appinfo)), NULL); g_strcanon (path, "/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", '_'); return path; } static void export_menus_and_actions (GObject *source, GAsyncResult *res, gpointer user_data) { MessagingMenuApp *app = user_data; GError *error = NULL; gchar *object_path; object_path = messaging_menu_app_get_dbus_object_path (app); if (!object_path) return; app->bus = g_bus_get_finish (res, &error); if (app->bus == NULL) { g_warning ("unable to connect to session bus: %s", error->message); g_error_free (error); return; } app->action_export_id = g_dbus_connection_export_action_group (app->bus, object_path, G_ACTION_GROUP (app->source_actions), &error); if (!app->action_export_id) { g_warning ("unable to export action group: %s", error->message); g_clear_error (&error); } app->menu_export_id = g_dbus_connection_export_menu_model (app->bus, object_path, G_MENU_MODEL (app->menu), &error); if (!app->menu_export_id) { g_warning ("unable to export menu: %s", error->message); g_clear_error (&error); } g_free (object_path); } static void messaging_menu_app_set_desktop_id (MessagingMenuApp *app, const gchar *desktop_id) { g_return_if_fail (desktop_id != NULL); /* no need to clean up, it's construct only */ app->appinfo = g_desktop_app_info_new (desktop_id); if (app->appinfo == NULL) { g_warning ("could not find the desktop file for '%s'", desktop_id); } g_bus_get (G_BUS_TYPE_SESSION, app->cancellable, export_menus_and_actions, app); } static void messaging_menu_app_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { MessagingMenuApp *app = MESSAGING_MENU_APP (object); switch (prop_id) { case PROP_DESKTOP_ID: messaging_menu_app_set_desktop_id (app, g_value_get_string (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void messaging_menu_app_finalize (GObject *object) { G_OBJECT_CLASS (messaging_menu_app_parent_class)->finalize (object); } static void messaging_menu_app_dispose (GObject *object) { MessagingMenuApp *app = MESSAGING_MENU_APP (object); if (app->bus) { if (app->action_export_id > 0) g_dbus_connection_unexport_action_group (app->bus, app->action_export_id); if (app->menu_export_id > 0) g_dbus_connection_unexport_menu_model (app->bus, app->menu_export_id); app->action_export_id = 0; app->menu_export_id = 0; g_object_unref (app->bus); app->bus = NULL; } if (app->watch_id > 0) { g_bus_unwatch_name (app->watch_id); app->watch_id = 0; } if (app->cancellable) { g_cancellable_cancel (app->cancellable); g_object_unref (app->cancellable); app->cancellable = NULL; } if (app->messages_service) { g_signal_handlers_disconnect_by_func (app->messages_service, global_status_changed, app); g_clear_object (&app->messages_service); } g_clear_object (&app->appinfo); g_clear_object (&app->source_actions); g_clear_object (&app->menu); G_OBJECT_CLASS (messaging_menu_app_parent_class)->dispose (object); } static void messaging_menu_app_class_init (MessagingMenuAppClass *class) { GObjectClass *object_class = G_OBJECT_CLASS (class); object_class->set_property = messaging_menu_app_set_property; object_class->finalize = messaging_menu_app_finalize; object_class->dispose = messaging_menu_app_dispose; /** * MessagingMenuApp:desktop-id: * * The desktop id of the application associated with this application * section. Must be given when the #MessagingMenuApp is created. */ properties[PROP_DESKTOP_ID] = g_param_spec_string ("desktop-id", "Desktop Id", "The desktop id of the associated application", NULL, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (object_class, N_PROPERTIES, properties); /** * MessagingMenuApp::activate-source: * @mmapp: the #MessagingMenuApp * @source_id: the source id that was activated * * Emitted when the user has activated the message source with id * @source_id. The source is immediately removed from the menu, * handlers of this signal do not need to call * messaging_menu_app_remove_source(). */ signals[ACTIVATE_SOURCE] = g_signal_new ("activate-source", MESSAGING_MENU_TYPE_APP, G_SIGNAL_RUN_FIRST | G_SIGNAL_DETAILED, 0, NULL, NULL, g_cclosure_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING); /** * MessagingMenuApp::status-changed: * @mmapp: the #MessagingMenuApp * @status: a #MessagingMenuStatus * * Emitted when the chat status is changed through the messaging menu. * * Applications which are registered to use the chat status should * change their status to @status upon receiving this signal. Call * messaging_menu_app_set_status() to acknowledge that the application * changed its status. */ signals[STATUS_CHANGED] = g_signal_new ("status-changed", MESSAGING_MENU_TYPE_APP, G_SIGNAL_RUN_FIRST, 0, NULL, NULL, g_cclosure_marshal_VOID__INT, G_TYPE_NONE, 1, G_TYPE_INT); } static void created_messages_service (GObject *source_object, GAsyncResult *result, gpointer user_data) { MessagingMenuApp *app = user_data; GError *error = NULL; app->messages_service = indicator_messages_service_proxy_new_finish (result, &error); if (!app->messages_service) { g_warning ("unable to connect to the mesaging menu service: %s", error->message); g_error_free (error); return; } g_signal_connect (app->messages_service, "status-changed", G_CALLBACK (global_status_changed), app); /* sync current status */ if (app->registered == TRUE) messaging_menu_app_register (app); else if (app->registered == FALSE) messaging_menu_app_unregister (app); if (app->status_set) messaging_menu_app_set_status (app, app->status); } static void indicator_messages_appeared (GDBusConnection *bus, const gchar *name, const gchar *name_owner, gpointer user_data) { MessagingMenuApp *app = user_data; indicator_messages_service_proxy_new (bus, G_DBUS_PROXY_FLAGS_NONE, "com.canonical.indicator.messages", "/com/canonical/indicator/messages/service", app->cancellable, created_messages_service, app); } static void indicator_messages_vanished (GDBusConnection *bus, const gchar *name, gpointer user_data) { MessagingMenuApp *app = user_data; if (app->messages_service) { g_signal_handlers_disconnect_by_func (app->messages_service, global_status_changed, app); g_clear_object (&app->messages_service); } } static void messaging_menu_app_init (MessagingMenuApp *app) { app->registered = -1; app->status_set = FALSE; app->bus = NULL; app->action_export_id = 0; app->menu_export_id = 0; app->cancellable = g_cancellable_new (); app->source_actions = g_simple_action_group_new (); app->menu = g_menu_new (); app->cancellable = g_cancellable_new (); app->watch_id = g_bus_watch_name (G_BUS_TYPE_SESSION, "com.canonical.indicator.messages", G_BUS_NAME_WATCHER_FLAGS_NONE, indicator_messages_appeared, indicator_messages_vanished, app, NULL); } /** * messaging_menu_new: * @desktop_id: a desktop file id. See g_desktop_app_info_new() * * Creates a new #MessagingMenuApp for the application associated with * @desktop_id. * * The application will not show up (nor be marked as "running") in the * Messaging Menu before messaging_menu_app_register() has been called. * * Returns: (transfer full): a new #MessagingMenuApp */ MessagingMenuApp * messaging_menu_app_new (const gchar *desktop_id) { return g_object_new (MESSAGING_MENU_TYPE_APP, "desktop-id", desktop_id, NULL); } /** * messaging_menu_app_register: * @app: a #MessagingMenuApp * * Registers @app with the Messaging Menu. * * If the application doesn't already have a section in the Messaging * Menu, one will be created for it. The application will also be * marked as "running". * * The application will be marked as "not running" as soon as @app is * destroyed. The application launcher as well as shortcut actions will * remain in the menu. To completely remove the application section * from the Messaging Menu, call messaging_menu_app_unregister(). */ void messaging_menu_app_register (MessagingMenuApp *app) { gchar *object_path; g_return_if_fail (MESSAGING_MENU_IS_APP (app)); app->registered = TRUE; /* state will be synced right after connecting to the service */ if (!app->messages_service) return; object_path = messaging_menu_app_get_dbus_object_path (app); if (!object_path) return; indicator_messages_service_call_register_application (app->messages_service, g_app_info_get_id (G_APP_INFO (app->appinfo)), object_path, app->cancellable, NULL, NULL); g_free (object_path); } /** * messaging_menu_app_unregister: * @app: a #MessagingMenuApp * * Completely removes the @app from the Messaging Menu. If the * application's launcher and shortcut actions should remain in the * menu, destroying @app with g_object_unref() suffices. * * Note: @app will remain valid and usable after this call. */ void messaging_menu_app_unregister (MessagingMenuApp *app) { g_return_if_fail (MESSAGING_MENU_IS_APP (app)); app->registered = FALSE; /* state will be synced right after connecting to the service */ if (!app->messages_service) return; if (!app->appinfo) return; indicator_messages_service_call_unregister_application (app->messages_service, g_app_info_get_id (G_APP_INFO (app->appinfo)), app->cancellable, NULL, NULL); } /** * messaging_menu_app_set_status: * @app: a #MessagingMenuApp * @status: a #MessagingMenuStatus * * Notify the Messaging Menu that the chat status of @app has changed to * @status. * * Connect to the ::status-changed signal to receive notification about * the user changing their global chat status through the Messaging * Menu. * * This function does nothing for applications whose desktop file does * not include X-MessagingMenu-UsesChatSection. */ void messaging_menu_app_set_status (MessagingMenuApp *app, MessagingMenuStatus status) { g_return_if_fail (MESSAGING_MENU_IS_APP (app)); g_return_if_fail (status >= MESSAGING_MENU_STATUS_AVAILABLE && status <= MESSAGING_MENU_STATUS_OFFLINE); app->status = status; app->status_set = TRUE; /* state will be synced right after connecting to the service */ if (!app->messages_service) return; if (!app->appinfo) return; indicator_messages_service_call_set_status (app->messages_service, g_app_info_get_id (G_APP_INFO (app->appinfo)), status_ids [status], app->cancellable, NULL, NULL); } static int status_from_string (const gchar *s) { int i; if (!s) return -1; for (i = 0; i <= MESSAGING_MENU_STATUS_OFFLINE; i++) { if (g_str_equal (s, status_ids[i])) return i; } return -1; } static void global_status_changed (IndicatorMessagesService *service, const gchar *status_str, gpointer user_data) { MessagingMenuApp *app = user_data; int status; status = status_from_string (status_str); g_return_if_fail (status >= 0); g_signal_emit (app, signals[STATUS_CHANGED], 0, status); } static void source_action_activated (GSimpleAction *action, GVariant *parameter, gpointer user_data) { MessagingMenuApp *app = user_data; const gchar *name = g_action_get_name (G_ACTION (action)); GQuark q = g_quark_from_string (name); messaging_menu_app_remove_source (app, name); g_signal_emit (app, signals[ACTIVATE_SOURCE], q, name); } static void messaging_menu_app_insert_source_action (MessagingMenuApp *app, gint position, const gchar *id, GIcon *icon, const gchar *label, GVariant *state) { GSimpleAction *action; GMenuItem *menuitem; g_return_if_fail (MESSAGING_MENU_IS_APP (app)); g_return_if_fail (id != NULL); if (g_simple_action_group_lookup (app->source_actions, id)) { g_warning ("a source with id '%s' already exists", id); return; } action = g_simple_action_new_stateful (id, NULL, state); g_signal_connect (action, "activate", G_CALLBACK (source_action_activated), app); g_simple_action_group_insert (app->source_actions, G_ACTION (action)); g_object_unref (action); menuitem = g_menu_item_new (label, id); g_menu_item_set_attribute (menuitem, "x-canonical-type", "s", "ImSourceMenuItem"); if (icon) { gchar *iconstr = g_icon_to_string (icon); g_menu_item_set_attribute (menuitem, "x-canonical-icon", "s", iconstr); g_free (iconstr); } g_menu_insert_item (app->menu, position, menuitem); g_object_unref (menuitem); } static GSimpleAction * messaging_menu_app_get_source_action (MessagingMenuApp *app, const gchar *source_id) { GAction *action; g_return_val_if_fail (MESSAGING_MENU_IS_APP (app), NULL); g_return_val_if_fail (source_id != NULL, NULL); action = g_simple_action_group_lookup (app->source_actions, source_id); if (action == NULL) g_warning ("a source with id '%s' doesn't exist", source_id); return G_SIMPLE_ACTION (action); } static void messaging_menu_app_set_source_action (MessagingMenuApp *app, const gchar *source_id, guint count, gint64 time, const gchar *string) { GSimpleAction *action; GVariant *state; gboolean draws_attention; GVariant *new_state; action = messaging_menu_app_get_source_action (app, source_id); if (!action) return; state = g_action_get_state (G_ACTION (action)); g_variant_get_child (state, 3, "b", &draws_attention); new_state = g_variant_new ("(uxsb)", count, time, string, draws_attention); g_simple_action_set_state (action, new_state); g_variant_unref (state); } static void messaging_menu_app_set_draws_attention (MessagingMenuApp *app, const gchar *source_id, gboolean draws_attention) { GSimpleAction *action; GVariant *state; guint count; gint64 time; const gchar *string; GVariant *new_state; action = messaging_menu_app_get_source_action (app, source_id); if (!action) return; state = g_action_get_state (G_ACTION (action)); g_variant_get (state, "(ux&sb)", &count, &time, &string); new_state = g_variant_new ("(uxsb)", count, time, string, TRUE); g_simple_action_set_state (action, new_state); g_variant_unref (state); } /** * messaging_menu_app_insert_source: * @app: a #MessagingMenuApp * @position: the position at which to insert the source * @id: a unique identifier for the source to be added * @icon: the icon associated with the source * @label: a user-visible string best describing the source * * Inserts a new message source into the section representing @app. Equivalent * to calling messaging_menu_app_insert_source_with_time() with the current * time. * * It is an error to insert a source with an @id which already exists. Use * messaging_menu_app_has_source() to find out whether there is such a source. */ void messaging_menu_app_insert_source (MessagingMenuApp *app, gint position, const gchar *id, GIcon *icon, const gchar *label) { messaging_menu_app_insert_source_with_time (app, position, id, icon, label, g_get_real_time ()); } /** * messaging_menu_app_append_source: * @app: a #MessagingMenuApp * @id: a unique identifier for the source to be added * @icon: (allow-none): the icon associated with the source * @label: a user-visible string best describing the source * * Appends a new message source to the end of the section representing @app. * Equivalent to calling messaging_menu_app_append_source_with_time() with the * current time. * * It is an error to add a source with an @id which already exists. Use * messaging_menu_app_has_source() to find out whether there is such a source. */ void messaging_menu_app_append_source (MessagingMenuApp *app, const gchar *id, GIcon *icon, const gchar *label) { messaging_menu_app_insert_source (app, -1, id, icon, label); } /** * messaging_menu_app_insert_source_with_count: * @app: a #MessagingMenuApp * @position: the position at which to insert the source * @id: a unique identifier for the source to be added * @icon: (allow-none): the icon associated with the source * @label: a user-visible string best describing the source * @count: the count for the source * * Inserts a new message source into the section representing @app and * initializes it with @count. * * To update the count, use messaging_menu_app_set_source_count(). * * It is an error to insert a source with an @id which already exists. Use * messaging_menu_app_has_source() to find out whether there is such a source. */ void messaging_menu_app_insert_source_with_count (MessagingMenuApp *app, gint position, const gchar *id, GIcon *icon, const gchar *label, guint count) { messaging_menu_app_insert_source_action (app, position, id, icon, label, g_variant_new ("(uxsb)", count, 0, "", FALSE)); } /** * messaging_menu_app_append_source_with_count: * @app: a #MessagingMenuApp * @id: a unique identifier for the source to be added * @icon: (allow-none): the icon associated with the source * @label: a user-visible string best describing the source * @count: the count for the source * * Appends a new message source to the end of the section representing @app and * initializes it with @count. * * To update the count, use messaging_menu_app_set_source_count(). * * It is an error to add a source with an @id which already exists. Use * messaging_menu_app_has_source() to find out whether there is such a source. */ void messaging_menu_app_append_source_with_count (MessagingMenuApp *app, const gchar *id, GIcon *icon, const gchar *label, guint count) { messaging_menu_app_insert_source_with_count (app, -1, id, icon, label, count); } /** * messaging_menu_app_insert_source_with_time: * @app: a #MessagingMenuApp * @position: the position at which to insert the source * @id: a unique identifier for the source to be added * @icon: (allow-none): the icon associated with the source * @label: a user-visible string best describing the source * @time: the time when the source was created, in microseconds * * Inserts a new message source into the section representing @app and * initializes it with @time. Use messaging_menu_app_insert_source() to * insert a source with the current time. * * To change the time, use messaging_menu_app_set_source_time(). * * It is an error to insert a source with an @id which already exists. Use * messaging_menu_app_has_source() to find out whether there is such a source. */ void messaging_menu_app_insert_source_with_time (MessagingMenuApp *app, gint position, const gchar *id, GIcon *icon, const gchar *label, gint64 time) { messaging_menu_app_insert_source_action (app, position, id, icon, label, g_variant_new ("(uxsb)", 0, time, "", FALSE)); } /** * messaging_menu_app_append_source_with_time: * @app: a #MessagingMenuApp * @id: a unique identifier for the source to be added * @icon: (allow-none): the icon associated with the source * @label: a user-visible string best describing the source * @time: the time when the source was created, in microseconds * * Appends a new message source to the end of the section representing * @app and initializes it with @time. Use * messaging_menu_app_append_source() to append a source with the * current time. * * To change the time, use messaging_menu_app_set_source_time(). * * It is an error to insert a source with an @id which already exists. Use * messaging_menu_app_has_source() to find out whether there is such a source. */ void messaging_menu_app_append_source_with_time (MessagingMenuApp *app, const gchar *id, GIcon *icon, const gchar *label, gint64 time) { messaging_menu_app_insert_source_with_time (app, -1, id, icon, label, time); } /** * messaging_menu_app_insert_source_with_string: * @app: a #MessagingMenuApp * @position: the position at which to insert the source * @id: a unique identifier for the source to be added * @icon: (allow-none): the icon associated with the source * @label: a user-visible string best describing the source * @str: a string associated with the source * * Inserts a new message source into the section representing @app and * initializes it with @str. * * To update the string, use messaging_menu_app_set_source_string(). * * It is an error to insert a source with an @id which already exists. Use * messaging_menu_app_has_source() to find out whether there is such a source. */ void messaging_menu_app_insert_source_with_string (MessagingMenuApp *app, gint position, const gchar *id, GIcon *icon, const gchar *label, const gchar *str) { messaging_menu_app_insert_source_action (app, position, id, icon, label, g_variant_new ("(uxsb)", 0, 0, str, FALSE)); } /** * messaging_menu_app_append_source_with_string: * @app: a #MessagingMenuApp * @id: a unique identifier for the source to be added * @icon: (allow-none): the icon associated with the source * @label: a user-visible string best describing the source * @str: a string associated with the source * * Appends a new message source to the end of the section representing @app and * initializes it with @str. * * To update the string, use messaging_menu_app_set_source_string(). * * It is an error to insert a source with an @id which already exists. Use * messaging_menu_app_has_source() to find out whether there is such a source. */ void messaging_menu_app_append_source_with_string (MessagingMenuApp *app, const gchar *id, GIcon *icon, const gchar *label, const gchar *str) { messaging_menu_app_insert_source_with_string (app, -1, id, icon, label, str); } /** * messaging_menu_app_remove_source: * @app: a #MessagingMenuApp * @source_id: the id of the source to remove * * Removes the source corresponding to @source_id from the menu. */ void messaging_menu_app_remove_source (MessagingMenuApp *app, const gchar *source_id) { int n_items; int i; g_return_if_fail (MESSAGING_MENU_IS_APP (app)); g_return_if_fail (source_id != NULL); if (g_simple_action_group_lookup (app->source_actions, source_id) == NULL) return; n_items = g_menu_model_get_n_items (G_MENU_MODEL (app->menu)); for (i = 0; i < n_items; i++) { gchar *action; if (g_menu_model_get_item_attribute (G_MENU_MODEL (app->menu), i, "action", "s", &action)) { if (!g_strcmp0 (action, source_id)) { g_menu_remove (app->menu, i); break; } g_free (action); } } g_simple_action_group_remove (app->source_actions, source_id); } /** * messaging_menu_app_has_source: * @app: a #MessagingMenuApp * @source_id: a source id * * Returns: TRUE if there is a source associated with @source_id */ gboolean messaging_menu_app_has_source (MessagingMenuApp *app, const gchar *source_id) { g_return_val_if_fail (MESSAGING_MENU_IS_APP (app), FALSE); g_return_val_if_fail (source_id != NULL, FALSE); return g_simple_action_group_lookup (app->source_actions, source_id) != NULL; } static GMenuItem * g_menu_find_item_with_action (GMenu *menu, const gchar *action, gint *out_pos) { gint i; gint n_elements; GMenuItem *item = NULL; n_elements = g_menu_model_get_n_items (G_MENU_MODEL (menu)); for (i = 0; i < n_elements && item == NULL; i++) { GVariant *attr; item = g_menu_item_new_from_model (G_MENU_MODEL (menu), i); attr = g_menu_item_get_attribute_value (item, G_MENU_ATTRIBUTE_ACTION, G_VARIANT_TYPE_STRING); if (!g_str_equal (action, g_variant_get_string (attr, NULL))) g_clear_object (&item); g_variant_unref (attr); } if (item && out_pos) *out_pos = i - 1; return item; } static void g_menu_replace_item (GMenu *menu, gint pos, GMenuItem *item) { g_menu_remove (menu, pos); g_menu_insert_item (menu, pos, item); } /** * messaging_menu_app_set_source_label: * @app: a #MessagingMenuApp * @source_id: a source id * @label: the new label for the source * * Changes the label of @source_id to @label. */ void messaging_menu_app_set_source_label (MessagingMenuApp *app, const gchar *source_id, const gchar *label) { gint pos; GMenuItem *item; g_return_if_fail (MESSAGING_MENU_IS_APP (app)); g_return_if_fail (source_id != NULL); g_return_if_fail (label != NULL); item = g_menu_find_item_with_action (app->menu, source_id, &pos); if (item == NULL) return; g_menu_item_set_attribute (item, G_MENU_ATTRIBUTE_LABEL, "s", label); g_menu_replace_item (app->menu, pos, item); g_object_unref (item); } /** * messaging_menu_app_set_source_icon: * @app: a #MessagingMenuApp * @source_id: a source id * @icon: (allow-none): the new icon for the source * * Changes the icon of @source_id to @icon. */ void messaging_menu_app_set_source_icon (MessagingMenuApp *app, const gchar *source_id, GIcon *icon) { gint pos; GMenuItem *item; g_return_if_fail (MESSAGING_MENU_IS_APP (app)); g_return_if_fail (source_id != NULL); item = g_menu_find_item_with_action (app->menu, source_id, &pos); if (item == NULL) return; if (icon) { gchar *iconstr; iconstr = g_icon_to_string (icon); g_menu_item_set_attribute (item, "x-canonical-icon", "s", iconstr); g_free (iconstr); } else { g_menu_item_set_attribute_value (item, "x-canonical-icon", NULL); } g_menu_replace_item (app->menu, pos, item); g_object_unref (item); } /** * messaging_menu_app_set_source_count: * @app: a #MessagingMenuApp * @source_id: a source id * @count: the new count for the source * * Updates the count of @source_id to @count. */ void messaging_menu_app_set_source_count (MessagingMenuApp *app, const gchar *source_id, guint count) { messaging_menu_app_set_source_action (app, source_id, count, 0, ""); } /** * messaging_menu_app_set_source_time: * @app: a #MessagingMenuApp * @source_id: a source id * @time: the new time for the source, in microseconds * * Updates the time of @source_id to @time. */ void messaging_menu_app_set_source_time (MessagingMenuApp *app, const gchar *source_id, gint64 time) { messaging_menu_app_set_source_action (app, source_id, 0, time, ""); } /** * messaging_menu_app_set_source_string: * @app: a #MessagingMenuApp * @source_id: a source id * @str: the new string for the source * * Updates the string displayed next to @source_id to @str. */ void messaging_menu_app_set_source_string (MessagingMenuApp *app, const gchar *source_id, const gchar *str) { messaging_menu_app_set_source_action (app, source_id, 0, 0, str); } /** * messaging_menu_app_draw_attention: * @app: a #MessagingMenuApp * @source_id: a source id * * Indicates that @source_id has important unread messages. Currently, this * means that the messaging menu's envelope icon will turn blue. * * Use messaging_menu_app_remove_attention() to stop indicating that the source * needs attention. */ void messaging_menu_app_draw_attention (MessagingMenuApp *app, const gchar *source_id) { messaging_menu_app_set_draws_attention (app, source_id, TRUE); } /** * messaging_menu_app_remove_attention: * @app: a #MessagingMenuApp * @source_id: a source id * * Stop indicating that @source_id needs attention. * * This function does not need to be called when the source is removed * with messaging_menu_app_remove_source() or the user has activated the * source. * * Use messaging_menu_app_draw_attention() to make @source_id draw attention * again. */ void messaging_menu_app_remove_attention (MessagingMenuApp *app, const gchar *source_id) { messaging_menu_app_set_draws_attention (app, source_id, TRUE); }