aboutsummaryrefslogtreecommitdiff
path: root/libmessaging-menu/messaging-menu.c
diff options
context:
space:
mode:
Diffstat (limited to 'libmessaging-menu/messaging-menu.c')
-rw-r--r--libmessaging-menu/messaging-menu.c356
1 files changed, 295 insertions, 61 deletions
diff --git a/libmessaging-menu/messaging-menu.c b/libmessaging-menu/messaging-menu.c
index b090352..93727fc 100644
--- a/libmessaging-menu/messaging-menu.c
+++ b/libmessaging-menu/messaging-menu.c
@@ -24,9 +24,74 @@
#include <gio/gdesktopappinfo.h>
/**
- * SECTION:messagingmenuapp
+ * SECTION:messaging-menu
* @title: MessagingMenuApp
* @short_description: An application section in the messaging menu
+ *
+ * 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 appliction 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
+ * shortcuts actions found in the desktop file which are marked as
+ * appearing in the Messaging Menu (the TargetEnvironment or OnlyShowIn
+ * keywords contains "Messaging Menu"). The <ulink
+ * url="http://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-1.1.html#extra-actions">
+ * desktop file specification</ulink> contains a detailed explanation of
+ * shortcut actions [1]. 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 <ulink
+ * url="https://wiki.ubuntu.com/MessagingMenu">https://wiki.ubuntu.com/MessagingMenu</ulink>.
+ */
+
+/**
+ * MessagingMenuApp:
+ *
+ * #MessagingMenuApp is an opaque structure.
*/
struct _MessagingMenuApp
{
@@ -75,6 +140,67 @@ 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;
+
+ g_return_val_if_fail (app->appinfo != NULL, 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;
+ GDBusConnection *bus;
+ GError *error = NULL;
+ guint id;
+ gchar *object_path;
+
+ bus = g_bus_get_finish (res, &error);
+ if (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);
+
+ id = g_dbus_connection_export_action_group (bus,
+ object_path,
+ G_ACTION_GROUP (app->source_actions),
+ &error);
+ if (!id)
+ {
+ g_warning ("unable to export action group: %s", error->message);
+ g_error_free (error);
+ }
+
+ id = g_dbus_connection_export_menu_model (bus,
+ object_path,
+ G_MENU_MODEL (app->menu),
+ &error);
+ if (!id)
+ {
+ g_warning ("unable to export menu: %s", error->message);
+ g_error_free (error);
+ }
+
+ g_object_unref (bus);
+ g_free (object_path);
+}
+
static void
messaging_menu_app_set_desktop_id (MessagingMenuApp *app,
const gchar *desktop_id)
@@ -88,6 +214,11 @@ messaging_menu_app_set_desktop_id (MessagingMenuApp *app,
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
@@ -157,6 +288,12 @@ messaging_menu_app_class_init (MessagingMenuAppClass *class)
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",
@@ -167,6 +304,16 @@ messaging_menu_app_class_init (MessagingMenuAppClass *class)
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 |
@@ -176,6 +323,18 @@ messaging_menu_app_class_init (MessagingMenuAppClass *class)
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,
@@ -213,47 +372,6 @@ created_messages_service (GObject *source_object,
}
static void
-got_session_bus (GObject *source,
- GAsyncResult *res,
- gpointer user_data)
-{
- MessagingMenuApp *app = user_data;
- GDBusConnection *bus;
- GError *error = NULL;
- guint id;
-
- bus = g_bus_get_finish (res, &error);
- if (bus == NULL)
- {
- g_warning ("unable to connect to session bus: %s", error->message);
- g_error_free (error);
- return;
- }
-
- id = g_dbus_connection_export_action_group (bus,
- "/com/canonical/indicator/messages",
- G_ACTION_GROUP (app->source_actions),
- &error);
- if (!id)
- {
- g_warning ("unable to export action group: %s", error->message);
- g_error_free (error);
- }
-
- id = g_dbus_connection_export_menu_model (bus,
- "/com/canonical/indicator/messages",
- G_MENU_MODEL (app->menu),
- &error);
- if (!id)
- {
- g_warning ("unable to export menu: %s", error->message);
- g_error_free (error);
- }
-
- g_object_unref (bus);
-}
-
-static void
indicator_messages_appeared (GDBusConnection *bus,
const gchar *name,
const gchar *name_owner,
@@ -299,12 +417,6 @@ messaging_menu_app_init (MessagingMenuApp *app)
app->cancellable = g_cancellable_new ();
-
- g_bus_get (G_BUS_TYPE_SESSION,
- app->cancellable,
- got_session_bus,
- app);
-
app->watch_id = g_bus_watch_name (G_BUS_TYPE_SESSION,
"com.canonical.indicator.messages",
G_BUS_NAME_WATCHER_FLAGS_NONE,
@@ -321,11 +433,8 @@ messaging_menu_app_init (MessagingMenuApp *app)
* Creates a new #MessagingMenuApp for the application associated with
* @desktop_id.
*
- * If the application is already registered with the messaging menu, it will be
- * marked as "running". Otherwise, call messaging_menu_app_register().
- *
- * The messaging menu will return to marking the application as not running as
- * soon as the returned #MessagingMenuApp is destroyed.
+ * 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
*/
@@ -341,17 +450,22 @@ messaging_menu_app_new (const gchar *desktop_id)
* messaging_menu_app_register:
* @app: a #MessagingMenuApp
*
- * Registers @app with the messaging menu.
+ * Registers @app with the Messaging Menu.
*
- * The messaging menu will add a section with an app launcher and the shortcuts
- * defined in its desktop file.
+ * 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 "running" as long as @app is alive or
- * messaging_menu_app_unregister() is called.
+ * 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;
@@ -360,19 +474,24 @@ messaging_menu_app_register (MessagingMenuApp *app)
if (!app->messages_service)
return;
+ object_path = messaging_menu_app_get_dbus_object_path (app);
+
indicator_messages_service_call_register_application (app->messages_service,
g_app_info_get_id (G_APP_INFO (app->appinfo)),
- "/com/canonical/indicator/messages",
+ object_path,
app->cancellable,
NULL, NULL);
+
+ g_free (object_path);
}
/**
* messaging_menu_app_unregister:
* @app: a #MessagingMenuApp
*
- * Completely removes the application associated with @desktop_id from the
- * messaging menu.
+ * 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.
*/
@@ -397,6 +516,16 @@ messaging_menu_app_unregister (MessagingMenuApp *app)
* 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,
@@ -794,6 +923,107 @@ messaging_menu_app_has_source (MessagingMenuApp *app,
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: 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;
+ gchar *iconstr;
+
+ 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;
+
+ iconstr = icon ? g_icon_to_string (icon) : NULL;
+ g_menu_item_set_attribute (item, "x-canonical-icon", "s", iconstr);
+ g_menu_replace_item (app->menu, pos, item);
+
+ g_free (iconstr);
+ g_object_unref (item);
+}
+
/**
* messaging_menu_app_set_source_count:
* @app: a #MessagingMenuApp
@@ -876,6 +1106,10 @@ messaging_menu_app_draw_attention (MessagingMenuApp *app,
*
* 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.
*/