ApplicationIndicators

Revision 8 as of 2009-12-10 21:47:15

Clear message

Application Panel Indicators

Summary

For the Lucid cycle, the DX team is focusing on a part of the desktop that has become a problem-area:

disaster.png

The current notification area is more and more difficult for users to interact with. Each application behaves differently, they're not accessible, and they're even sometime styled differently.

We propose to migrate the different applets to using simple menus. This change should provide a more consistent interface and be an important step to improve the user experience with the right hand side of the panel.

To implement that change, we're proposing to use a set of dbus APIs. These protocols are based on current technologies, some already used in Ubuntu, others also discussed by members of the FreeDesktop.org group. This includes an implementation already validated by the KDE project.

We are providing a set of libraries compatible with the 2 major desktop environments, along with tools and documentation to help porting applications to use the new protocols.

This effort is managed as part of the Ayatana project group on Launchpad, joining other cross-desktop projects that were delivered during the last Ubuntu development cycles. See resources below.

Notification area usage analysis

The panel indicator area was introduced in an early version of Gnome, on the model of the Microsoft '95 design, as an area dedicated to notifications. It is often called the systray or the notification area.

Use cases

Generally it allows users to very quickly access important application features. For application developers, there are thus certain occasions were it's interesting to host some of the user interface directly on the panel:

  • Someone using Last.fm wants quick access to play, pause, skip, love, and ban tracks. Some of these actions are specific to Last.fm, and not relevant to any other music player.
  • Someone using Ubuntu One to sync files wants to see whether it is currently syncing, and wants quick access to pause and resume functions. These tasks would not make sense in any other menu.
  • Someone using Tomboy wants a menu to let them quickly create new notes and access recent notes. Eventually these tasks may be integrated into a more generic document-creation interface, but for now, these tasks do not make sense in any other menu.
  • A software developer testing visual bugs in her software wants quick access to start and stop recording of the screen.

Issues with the current situation

Over time the notification area grew organically and somewhat inconsistently, for example, by including various applets which, according to the Guidelines, do not properly belong in the panel indicators area.

panel-suck.png

1. The areas of the panel are not clearly defined or identifiable at a glance because items are no longer organised visually, behaviorally and logically. Each item seems to have an independent purpose and, when clicked, behaves very differently one from the other. We have buttons, menu-ish panel, menu-ish slider and menus. This makes it almost impossible for users to gain a clear overall picture of the function of that area.

2. Users expect items close to each other to behave similarly. They rely on the proximity of applets/applications/menus to predict their behaviours and explain their functions (Usability principles: 1) proximity compatibility (Barnett & Wickens, 1988; Wickens & Carswell, 1995) and 2) consistency (Universal Principle of Design; Nielsen Norman; Bastien & Scapin). The unpredictability of behaviours of items in the panel indicator generates some confusion since users are not always in a position to interpret what the system is doing in response to their actions.

Recommendations: Decisions have to be made about the role of the panel indicators area and items have to be grouped and organized with consistency as to goal, function and behavior in order to offer users a consistent, predictable and therefore intuitive experience.

Proposed changes

We propose to define 2 main areas on the panel:

  • a "system indicator" area at the far right of the panel, gathering system features like sound, power or session management
  • an "application indicator" area, just before it, grouping icons and menus from applications that wish to host a part of their interface on the panel

Additionaly, we propose to turn all of the elements on the right side of the panel into regular menus.

panel-suck-less.png

This way, users will not have to worry about whether a function is accessible with a left or right click, or be suprised when a click on a panel icon suddenly hides or shows a full application window. Additionally, that will help provide:

  • an accessible interface: icons and menus will be introspectable by screen readers
  • style consistency: as the menus will be rendered by the same toolkit as the panel, the context menus of KDE applications will be rendered in the same style as other applications running on a Gnome desktop, and vice versa
  • constant layout: being integrated in a common menubar (as opposed to a collection of applets), icons and menus shouldn't be disturbed by resolution changes, like when doing a presentation and then returning to the desktop to see all applets totally disorganized
  • menu scrubbing: users frequently browse menus in search of a feature; by turning the notification area into a set of menus, the users will be able to search the interface more easily

Note: for compatibility reasons, we'll maintain a notification area zone for a while, to let application developers gradually migrate to the new protocol. Eventually, the old notification area should disappear and applications being able to inject generic/common functions in the system area, while the application-specific features will stay in the "application indicator" zone.

Software Architecture

During the Jaunty and Karmic cycles, we initiated a process to connect the panel and parts of the system using dbus. Traditionally the panel communicates with the rest of the system through a variety of technologies like: Orbit, X properties or Xembed. That flexibility has allowed a lot of applications to use the panel, but the general result is that this starts to show its age.

Using dbus for the panel. We've started with some menus (that we call indicators). We want to continue with all systray/notification-area applications. We call these application-indicators.

There are different parts composing the architecture required to implement the proposed solution.

  1. A panel applet to host the variety of application icons: the applet should display the icons or menu titles, render the content of menus/sub-menus and dialog with the application once the user activates a menu entry.

  2. A library to let applications register a status icon in the panel area; the library should be similar to the existing API, namely gtk_status_icon_new on a Gnome desktop.

  3. A "signaling protocol" between the panel and applications, to maintain that bi-directional link between applications and their icons and menus.

  4. A library to export the context menus, their content and structure, between an application and the panel applet. Trying hard to make that as transparent as possible.

  5. A corresponding protocol to transport the menus and allow for rapid content-related signals to be propagated between the application and the applet, across a dbus link.

We want to do this in a cross-desktop friendly manner that uses existing standards and is easy to migrate to. Therefore we propose this new method of application panel indicators.

In the implementation we developed for the Gnome desktop, we're reusing some existing technologies and introducing a new library.

  • dbusmenu is the library implementing the transport protocol between the applications and the panel (#5). dbusmenu has already been used for developing the session menu, and is a building block also for implementing some new system menus in Lucid

  • indicator-applet is the panel applet that will host the application indicator area. indicator-applet was developed as part of the messaging menu project and is meant to be a generic container for panel indicators

  • libappindicator is a new library introduced to help with application side changes; it does register icons and menus and internally uses dbusmenu to publish context menus over dbus. While its a new library for the Gnome desktop, it is based on a protocol (the "signaling protocol" part above) that was designed in the KDE project to renovate the systray protocol. libappindicator adopts and extends this protocol, and connects it with dbusmenu to provide the full set of service that an application will require to migrate its code

Note: the extensions to KDE's KStatusNotifier API have been discussed with upstream developers in order to have cross-desktop compatible protocols. This way a KDE application running on a Gnome desktop will have its icon and context menu properly rendered in the Gnome panel. Similarly, a Gnome application running under KDE, should see its icon and menu rendered in the new systray plasmoïd.

Technical Resources

Libraries

Get the code here or (browse):

bzr branch lp:indicator-application

We are providing sample implementations in C, C#, and Python using three applications: Rhythmbox, Tomboy, and system-config-printer.

  • C patch for Rhythmbox: see bug 496225

  • C# patch for Tomboy: (coming soon)
  • Pyhton patch for system-config-printer: (coming soon)

Public Package Archive(s)

We have set up a PPA with the core libraries needed for you to install and test libappindicator for Ubuntu Karmic. For Lucid users the libraries will be included in the distro by default.

If you are interested in porting and getting applications tested, we have set up an upstream applications PPA as a service to upstreams who want to get their applications tested. If you need help with this please contact JorgeCastro.

Bug reports, lists and IRC

Porting Guide for Applications

TODO: Python and C# examples

The application indicators API is similar to the GtkStatusIcon API in some ways, but simpler and more opinionated in its design.

Indicators are grouped together by category, so it's important for the application to accurately specify its category. Possible categories for indicators include:

  • APP_INDICATOR_CATEGORY_APPLICATION_STATUS: used to tell the user the current state of the application and allow users to act on that.
  • APP_INDICATOR_CATEGORY_COMMUNICATIONS: might be used by applications such as IM clients to let the user know they have received new messages.
  • APP_INDICATOR_CATEGORY_SYSTEM_SERVICES: inform the user about system-related events. Maybe there are updates available?
  • APP_INDICATOR_CATEGORY_HARDWARE: used for hardware-related tasks, for example Bluetooth.
  • APP_INDICATOR_CATEGORY_OTHER: if the app doesn't belong in one of the other categories but needs an indicator, it should use other.

The category is set when the indicator is created and isn't changed.

Application indicators fall into one of three states:

  • APP_INDICATOR_STATUS_PASSIVE: the indicator should be hidden from the user
  • APP_INDICATOR_STATUS_ACTIVE: the indicator should be shown normally
  • APP_INDICATOR_STATUS_ATTENTION: the application wants the user's attention

The indicator status can be set using app_indicator_set_status().

Typical usage

Indicators should typically be created using the helper function app_indicator_new(). This returns a pointer to a newly created AppIndicator object, which is may be unref'd normally when it is no longer needed. This function expects three parameters:

  AppIndicator* app_indicator_new (const gchar          *id,
                                   const gchar          *icon_name,
                                   AppIndicatorCategory  category);

The id parameter should be unique to your application. Because app indicators are cross-desktop, the icon_name parameter expects an icon name according to the usual icon naming spec. The category parameter is the indicator category as described above.

Once an indicator object is created, the application may decide to set an attention icon using app_indicator_set_attention_icon().

Lastly, the indicator should be given a GtkMenu object. This can be either created manually or using some other method such as GtkUIManager. Below is an example using GtkUIManager.

#include <gtk/gtk.h>
#include <libappindicator/app-indicator.h>

static void activate_action (GtkAction *action);

static GtkActionEntry entries[] = {
  { "FileMenu", NULL, "_File" },
  { "New",      "document-new", "_New", "<control>N",
    "Create a new file", G_CALLBACK (activate_action) },
  { "Open",     "document-open", "_Open", "<control>O",
    "Open a file", G_CALLBACK (activate_action) },
  { "Save",     "document-save", "_Save", "<control>S",
    "Save file", G_CALLBACK (activate_action) },
  { "Quit",     "application-exit", "_Quit", "<control>Q",
    "Exit the application", G_CALLBACK (gtk_main_quit) },
};
static guint n_entries = G_N_ELEMENTS (entries);

static const gchar *ui_info =
"<ui>"
"  <menubar name='MenuBar'>"
"    <menu action='FileMenu'>"
"      <menuitem action='New'/>"
"      <menuitem action='Open'/>"
"      <menuitem action='Save'/>"
"      <separator/>"
"      <menuitem action='Quit'/>"
"    </menu>"
"  </menubar>"
"  <popup name='IndicatorPopup'>"
"    <menuitem action='New' />"
"    <menuitem action='Open' />"
"    <menuitem action='Save' />"
"    <menuitem action='Quit' />"
"  </popup>"
"</ui>";

static void
activate_action (GtkAction *action)
{
        const gchar *name = gtk_action_get_name (action);
        GtkWidget *dialog;

        dialog = gtk_message_dialog_new (NULL,
                                         GTK_DIALOG_DESTROY_WITH_PARENT,
                                         GTK_MESSAGE_INFO,
                                         GTK_BUTTONS_CLOSE,
                                         "You activated action: \"%s\"",
                                         name);

        g_signal_connect (dialog, "response",
                          G_CALLBACK (gtk_widget_destroy), NULL);

        gtk_widget_show (dialog);
}

int main (int argc, char **argv)
{
  GtkWidget *window;
  GtkWidget *menubar;
  GtkWidget *table;
  GtkWidget *sw;
  GtkWidget *doc;
  GtkWidget *statusbar;
  GtkWidget *indicator_menu;
  GtkActionGroup *action_group;
  GtkUIManager *uim;
  AppIndicator *indicator;
  GError *error = NULL;

  gtk_init (&argc, &argv);

  /* main window */
  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title (GTK_WINDOW (window), "Indicator Demo");
  gtk_window_set_icon_name (GTK_WINDOW (window), "indicator-messages-new");
  g_signal_connect (G_OBJECT (window),
                    "destroy",
                    G_CALLBACK (gtk_main_quit),
                    NULL);

  table = gtk_table_new (1, 5, FALSE);
  gtk_container_add (GTK_CONTAINER (window), table);

  /* Menus */
  action_group = gtk_action_group_new ("AppActions");
  gtk_action_group_add_actions (action_group,
                                entries, n_entries,
                                window);

  uim = gtk_ui_manager_new ();
  g_object_set_data_full (G_OBJECT (window),
                          "ui-manager", uim,
                          g_object_unref);
  gtk_ui_manager_insert_action_group (uim, action_group, 0);
  gtk_window_add_accel_group (GTK_WINDOW (window),
                              gtk_ui_manager_get_accel_group (uim));

  if (!gtk_ui_manager_add_ui_from_string (uim, ui_info, -1, &error))
    {
      g_message ("Failed to build menus: %s\n", error->message);
      g_error_free (error);
      error = NULL;
    }

  menubar = gtk_ui_manager_get_widget (uim, "/ui/MenuBar");
  gtk_widget_show (menubar);
  gtk_table_attach (GTK_TABLE (table),
                    menubar,
                    0, 1,                    0, 1,
                    GTK_EXPAND | GTK_FILL,   0,
                    0,                       0);

  /* Document */
  sw = gtk_scrolled_window_new (NULL, NULL);

  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
                                  GTK_POLICY_AUTOMATIC,
                                  GTK_POLICY_AUTOMATIC);

  gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw),
                                       GTK_SHADOW_IN);

  gtk_table_attach (GTK_TABLE (table),
                    sw,
                    /* X direction */       /* Y direction */
                    0, 1,                   3, 4,
                    GTK_EXPAND | GTK_FILL,  GTK_EXPAND | GTK_FILL,
                    0,                      0);

  gtk_window_set_default_size (GTK_WINDOW (window),
                               200, 200);

  contents = gtk_text_view_new ();
  gtk_widget_grab_focus (contents);

  gtk_container_add (GTK_CONTAINER (sw),
                     contents);


  /* Create statusbar */
  statusbar = gtk_statusbar_new ();
  gtk_table_attach (GTK_TABLE (table),
                    statusbar,
                    /* X direction */       /* Y direction */
                    0, 1,                   4, 5,
                    GTK_EXPAND | GTK_FILL,  0,
                    0,                      0);

  /* Show the window */
  gtk_widget_show_all (window);

  /* Indicator */
  indicator = app_indicator_new ("example-simple-client",
                                 "indicator-messages",
                                 APP_INDICATOR_CATEGORY_APPLICATION_STATUS);

  indicator_menu = gtk_ui_manager_get_widget (uim, "/ui/IndicatorPopup");

  app_indicator_set_status (indicator, APP_INDICATOR_STATUS_ACTIVE);
  app_indicator_set_attention_icon (indicator, "indicator-messages-new");

  app_indicator_set_menu (indicator, GTK_MENU (indicator_menu));

  gtk_main ();

  return 0;
}