/* * 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-app.h" #include "indicator-messages-service.h" #include "indicator-messages-application.h" #include #include /** * SECTION:messaging-menu-app * @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; GDBusConnection *bus; GHashTable *messages; GList *sources; IndicatorMessagesApplication *app_interface; IndicatorMessagesService *messages_service; guint watch_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" }; typedef struct { gchar *id; GIcon *icon; gchar *label; guint32 count; gint64 time; gchar *string; gboolean draws_attention; } Source; static void global_status_changed (IndicatorMessagesService *service, const gchar *status_str, gpointer user_data); /* in messaging-menu-message.c */ GVariant * _messaging_menu_message_to_variant (MessagingMenuMessage *msg); static void source_free (gpointer data) { Source *source = data; if (source) { g_free (source->id); g_clear_object (&source->icon); g_free (source->label); g_free (source->string); g_slice_free (Source, source); } } static GVariant * source_to_variant (Source *source) { GVariant *v; GVariant *serialized_icon; GVariantBuilder builder; serialized_icon = source->icon ? g_icon_serialize (source->icon) : NULL; g_variant_builder_init (&builder, G_VARIANT_TYPE ("av")); if (serialized_icon) { g_variant_builder_add (&builder, "v", serialized_icon); g_variant_unref (serialized_icon); } v = g_variant_new ("(ssavuxsb)", source->id, source->label, &builder, source->count, source->time, source->string ? source->string : "", source->draws_attention); return v; } static gchar * messaging_menu_app_get_dbus_object_path (MessagingMenuApp *app) { gchar *path; if (!app->appinfo) return NULL; path = g_strconcat ("/org/ayatana/indicator/messages/", g_app_info_get_id (G_APP_INFO (app->appinfo)), NULL); g_strcanon (path, "/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", '_'); return path; } static void messaging_menu_app_got_bus (GObject *source, GAsyncResult *res, gpointer user_data) { MessagingMenuApp *app = user_data; GError *error = NULL; gchar *object_path; 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; } object_path = messaging_menu_app_get_dbus_object_path (app); if (object_path && !g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (app->app_interface), app->bus, object_path, &error)) { g_warning ("unable to export application interface: %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, messaging_menu_app_got_bus, 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->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) { indicator_messages_service_call_application_stopped_running (app->messages_service, g_app_info_get_id (G_APP_INFO (app->appinfo)), NULL, NULL, NULL); g_signal_handlers_disconnect_by_func (app->messages_service, global_status_changed, app); g_clear_object (&app->messages_service); } g_clear_pointer (&app->messages, g_hash_table_unref); g_list_free_full (app->sources, source_free); app->sources = NULL; g_clear_object (&app->app_interface); g_clear_object (&app->appinfo); g_clear_object (&app->bus); 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, "org.ayatana.indicator.messages", "/org/ayatana/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 gboolean messaging_menu_app_list_sources (IndicatorMessagesApplication *app_interface, GDBusMethodInvocation *invocation, gpointer user_data) { MessagingMenuApp *app = user_data; GVariantBuilder builder; GList *it; g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ssavuxsb)")); for (it = app->sources; it; it = it->next) g_variant_builder_add_value (&builder, source_to_variant (it->data)); indicator_messages_application_complete_list_sources (app_interface, invocation, g_variant_builder_end (&builder)); return TRUE; } static gint compare_source_id (gconstpointer a, gconstpointer b) { const Source *source = a; const gchar *id = b; return strcmp (source->id, id); } static gboolean messaging_menu_app_remove_source_internal (MessagingMenuApp *app, const gchar *source_id) { GList *node; node = g_list_find_custom (app->sources, source_id, compare_source_id); if (node) { source_free (node->data); app->sources = g_list_delete_link (app->sources, node); return TRUE; } return FALSE; } static gboolean messaging_menu_app_remove_message_internal (MessagingMenuApp *app, const gchar *message_id) { return g_hash_table_remove (app->messages, message_id); } static gboolean messaging_menu_app_activate_source (IndicatorMessagesApplication *app_interface, GDBusMethodInvocation *invocation, const gchar *source_id, gpointer user_data) { MessagingMenuApp *app = user_data; GQuark q = g_quark_from_string (source_id); /* Activate implies removing the source, no need for SourceRemoved */ if (messaging_menu_app_remove_source_internal (app, source_id)) g_signal_emit (app, signals[ACTIVATE_SOURCE], q, source_id); indicator_messages_application_complete_activate_source (app_interface, invocation); return TRUE; } static gboolean messaging_menu_app_list_messages (IndicatorMessagesApplication *app_interface, GDBusMethodInvocation *invocation, gpointer user_data) { MessagingMenuApp *app = user_data; GVariantBuilder builder; GHashTableIter iter; MessagingMenuMessage *message; g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(savsssxaa{sv}b)")); g_hash_table_iter_init (&iter, app->messages); while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &message)) g_variant_builder_add_value (&builder, _messaging_menu_message_to_variant (message)); indicator_messages_application_complete_list_messages (app_interface, invocation, g_variant_builder_end (&builder)); return TRUE; } static gboolean messaging_menu_app_activate_message (IndicatorMessagesApplication *app_interface, GDBusMethodInvocation *invocation, const gchar *message_id, const gchar *action_id, GVariant *params, gpointer user_data) { MessagingMenuApp *app = user_data; MessagingMenuMessage *msg; msg = g_hash_table_lookup (app->messages, message_id); if (msg) { if (*action_id) { gchar *signal; signal = g_strconcat ("activate::", action_id, NULL); if (g_variant_n_children (params)) { GVariant *param; g_variant_get_child (params, 0, "v", ¶m); g_signal_emit_by_name (msg, signal, action_id, param); g_variant_unref (param); } else g_signal_emit_by_name (msg, signal, action_id, NULL); g_free (signal); } else g_signal_emit_by_name (msg, "activate", NULL, NULL); /* Activate implies removing the message, no need for MessageRemoved */ messaging_menu_app_remove_message_internal (app, message_id); } indicator_messages_application_complete_activate_message (app_interface, invocation); return TRUE; } static gboolean messaging_menu_app_dismiss (IndicatorMessagesApplication *app_interface, GDBusMethodInvocation *invocation, const gchar * const *sources, const gchar * const *messages, gpointer user_data) { MessagingMenuApp *app = user_data; const gchar * const *it; for (it = sources; *it; it++) messaging_menu_app_remove_source_internal (app, *it); for (it = messages; *it; it++) messaging_menu_app_remove_message_internal (app, *it); return TRUE; } static void messaging_menu_app_init (MessagingMenuApp *app) { app->registered = -1; app->status_set = FALSE; app->bus = NULL; app->cancellable = g_cancellable_new (); app->app_interface = indicator_messages_application_skeleton_new (); g_signal_connect (app->app_interface, "handle-list-sources", G_CALLBACK (messaging_menu_app_list_sources), app); g_signal_connect (app->app_interface, "handle-activate-source", G_CALLBACK (messaging_menu_app_activate_source), app); g_signal_connect (app->app_interface, "handle-list-messages", G_CALLBACK (messaging_menu_app_list_messages), app); g_signal_connect (app->app_interface, "handle-activate-message", G_CALLBACK (messaging_menu_app_activate_message), app); g_signal_connect (app->app_interface, "handle-dismiss", G_CALLBACK (messaging_menu_app_dismiss), app); app->messages = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); app->watch_id = g_bus_watch_name (G_BUS_TYPE_SESSION, "org.ayatana.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 Source * messaging_menu_app_lookup_source (MessagingMenuApp *app, const gchar *id) { GList *node; node = g_list_find_custom (app->sources, id, compare_source_id); return node ? node->data : NULL; } static Source * messaging_menu_app_get_source (MessagingMenuApp *app, const gchar *id) { Source *source; source = messaging_menu_app_lookup_source (app, id); if (!source) g_warning ("a source with id '%s' doesn't exist", id); return source; } static void messaging_menu_app_notify_source_changed (MessagingMenuApp *app, Source *source) { indicator_messages_application_emit_source_changed (app->app_interface, source_to_variant (source)); } static void messaging_menu_app_insert_source_internal (MessagingMenuApp *app, gint position, const gchar *id, GIcon *icon, const gchar *label, guint count, gint64 time, const gchar *string) { Source *source; g_return_if_fail (MESSAGING_MENU_IS_APP (app)); g_return_if_fail (id != NULL); g_return_if_fail (label != NULL); if (messaging_menu_app_lookup_source (app, id)) { g_warning ("a source with id '%s' already exists", id); return; } source = g_slice_new0 (Source); source->id = g_strdup (id); source->label = g_strdup (label); if (icon) source->icon = g_object_ref (icon); source->count = count; source->time = time; source->string = g_strdup (string); app->sources = g_list_insert (app->sources, source, position); indicator_messages_application_emit_source_added (app->app_interface, position, source_to_variant (source)); } /** * 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_internal (app, position, id, icon, label, count, 0, ""); } /** * 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_internal (app, position, id, icon, label, 0, time, ""); } /** * 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_internal (app, position, id, icon, label, 0, 0, str); } /** * 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) { g_return_if_fail (MESSAGING_MENU_IS_APP (app)); g_return_if_fail (source_id != NULL); if (messaging_menu_app_remove_source_internal (app, source_id)) indicator_messages_application_emit_source_removed (app->app_interface, 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 messaging_menu_app_lookup_source (app, source_id) != NULL; } /** * 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) { Source *source; g_return_if_fail (MESSAGING_MENU_IS_APP (app)); g_return_if_fail (source_id != NULL); g_return_if_fail (label != NULL); source = messaging_menu_app_get_source (app, source_id); if (source) { g_free (source->label); source->label = g_strdup (label); messaging_menu_app_notify_source_changed (app, source); } } /** * 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) { Source *source; g_return_if_fail (MESSAGING_MENU_IS_APP (app)); g_return_if_fail (source_id != NULL); source = messaging_menu_app_get_source (app, source_id); if (source) { g_clear_object (&source->icon); if (icon) source->icon = g_object_ref (icon); messaging_menu_app_notify_source_changed (app, source); } } /** * 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) { Source *source; g_return_if_fail (MESSAGING_MENU_IS_APP (app)); g_return_if_fail (source_id != NULL); source = messaging_menu_app_get_source (app, source_id); if (source) { source->count = count; messaging_menu_app_notify_source_changed (app, source); } } /** * 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) { Source *source; g_return_if_fail (MESSAGING_MENU_IS_APP (app)); g_return_if_fail (source_id != NULL); source = messaging_menu_app_get_source (app, source_id); if (source) { source->time = time; messaging_menu_app_notify_source_changed (app, source); } } /** * 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) { Source *source; g_return_if_fail (MESSAGING_MENU_IS_APP (app)); g_return_if_fail (source_id != NULL); source = messaging_menu_app_get_source (app, source_id); if (source) { g_free (source->string); source->string = g_strdup (str); messaging_menu_app_notify_source_changed (app, source); } } /** * 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) { Source *source; g_return_if_fail (MESSAGING_MENU_IS_APP (app)); g_return_if_fail (source_id != NULL); source = messaging_menu_app_get_source (app, source_id); if (source) { source->draws_attention = TRUE; messaging_menu_app_notify_source_changed (app, source); } } /** * 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) { Source *source; g_return_if_fail (MESSAGING_MENU_IS_APP (app)); g_return_if_fail (source_id != NULL); source = messaging_menu_app_get_source (app, source_id); if (source) { source->draws_attention = FALSE; messaging_menu_app_notify_source_changed (app, source); } } /** * messaging_menu_app_append_message: * @app: a #MessagingMenuApp * @msg: the #MessagingMenuMessage to append * @source_id: (allow-none): the source id to which @msg is added, or NULL * @notify: whether a notification bubble should be shown for this * message * * Appends @msg to the source with id @source_id of @app. The messaging * menu might not display this message immediately if other messages are * queued before this one. * * If @source_id has a count associated with it, that count will be * increased by one. * * If @source_id is %NULL, @msg won't be associated with a source. */ void messaging_menu_app_append_message (MessagingMenuApp *app, MessagingMenuMessage *msg, const gchar *source_id, gboolean notify) { const gchar *id; g_return_if_fail (MESSAGING_MENU_IS_APP (app)); g_return_if_fail (MESSAGING_MENU_IS_MESSAGE (msg)); id = messaging_menu_message_get_id (msg); if (g_hash_table_lookup (app->messages, id)) { g_warning ("a message with id '%s' already exists", id); return; } g_hash_table_insert (app->messages, g_strdup (id), g_object_ref (msg)); indicator_messages_application_emit_message_added (app->app_interface, _messaging_menu_message_to_variant (msg)); if (source_id) { Source *source; source = messaging_menu_app_get_source (app, source_id); if (source && source->count >= 0) { source->count++; messaging_menu_app_notify_source_changed (app, source); } } } /** * messaging_menu_app_get_message: * @app: a #MessagingMenuApp * @id: id of the message to retrieve * * Retrieves the message with @id, that was added with * messaging_menu_app_append_message(). * * Returns: (transfer none) (allow-none): the #MessagingMenuApp with * @id, or %NULL */ MessagingMenuMessage * messaging_menu_app_get_message (MessagingMenuApp *app, const gchar *id) { g_return_val_if_fail (MESSAGING_MENU_IS_APP (app), NULL); g_return_val_if_fail (id != NULL, NULL); return g_hash_table_lookup (app->messages, id); } /** * messaging_menu_app_remove_message: * @app: a #MessagingMenuApp * @msg: the #MessagingMenuMessage to remove * * Removes @msg from @app. * * If @source_id has a count associated with it, that count will be * decreased by one. */ void messaging_menu_app_remove_message (MessagingMenuApp *app, MessagingMenuMessage *msg) { /* take a ref of @msg here to make sure the pointer returned by * _get_id() is valid for the duration of remove_message_by_id. */ g_object_ref (msg); messaging_menu_app_remove_message_by_id (app, messaging_menu_message_get_id (msg)); g_object_unref (msg); } /** * messaging_menu_app_remove_message_by_id: * @app: a #MessagingMenuApp * @id: the unique id of @msg * * Removes the message with the id @id from @app. * * If @source_id has a count associated with it, that count will be * decreased by one. */ void messaging_menu_app_remove_message_by_id (MessagingMenuApp *app, const gchar *id) { g_return_if_fail (MESSAGING_MENU_IS_APP (app)); g_return_if_fail (id != NULL); if (messaging_menu_app_remove_message_internal (app, id)) indicator_messages_application_emit_message_removed (app->app_interface, id); }