# Panel Applet Library Reference Manual

## Panel Applet Library Overview

Applets are small applications that are embedded in the GNOME panel. They can be used to give quick access to some features, or to display the state of something specific.

The Panel Applet library is what should be used to write applets as it handles all the communication with the GNOME panel. It hides all of the embedding process of the applet behind a PanelApplet widget. It also provides PanelApplet API to properly integrate the applet in the panel.

Keep in mind that starting with GNOME 3, the panel and applets are only available in the fallback mode. An applet will therefore not be usable in the default GNOME that users may use.

## Getting Started with the Panel Applet library

### Introduction

While applets are rather simple to write, they are not the most easy form of interaction to users for two reasons: it is not intuitive how to add or remove applets for many users, and the restriction in size of applets can limit their interface. Therefore, before writing an applet, think hard whether this is the form of interaction that is best for the feature you want to provide.

Keep in mind that starting with GNOME 3, the panel and applets are only available in the fallback mode. An applet will therefore not be usable in the default GNOME that users may use.

### Concepts

#### Applet Types

The applet type is the identifier representing a type of applets to the panel. It is a simple string, like `HelloWorldApplet` and is unique per applet factory.

#### Applet Factory

The applet factory is an implementation detail that is mostly hidden by the Panel Applet library, but it still appears in a few places (`PANEL_APPLET_OUT_PROCESS_FACTORY()`, `PANEL_APPLET_IN_PROCESS_FACTORY()` and `.panel-applet` files) so it is important to understand what is an applet factory.

The applet factory is the object that will create a new applet instance when the panel requests a new applet to be created. It is identified with a simple string id, for example `HelloWorldFactory`.

The requests the applet factory will receive from the panel specify which type of applet should be created. This is what makes it possible to have more than one applet types in one applet binary. In most cases, however, the applet factory will be specific to only one applet type. The map between applet types and the applet factory is recorded in `.panel-applet` files.

There is only one applet factory per applet binary, and it is always running before any applet instance is created by the applet binary. The applet factory is created via `PANEL_APPLET_OUT_PROCESS_FACTORY()` or `PANEL_APPLET_IN_PROCESS_FACTORY()`.

#### Applet Instances

There is no restriction as to how many instances of one applet type can be created. The user might choose to add more than one `HelloWorldApplet` applets to his panels. This can have some implications on the design used to write applets. The most important implication is that it is generally wrong to have global variables to keep a state specific to an applet instance.

### Hello World Example

An example is worth a million words, so here is a simple one:

#### Hello World applet

    #include <gtk/gtk.h>
    #include <panel-applet.h>

    static gboolean
    hello_world_applet_start (PanelApplet *applet)
    {
        GtkWidget *label;

        label = gtk_label_new ("Hello World");
        gtk_container_add (GTK_CONTAINER (applet), label);
        gtk_widget_show_all (GTK_WIDGET (applet));

        return TRUE;
    }

    static gboolean
    hello_world_factory_callback (PanelApplet  *applet,
                                  const gchar  *iid,
                                  gpointer      data)
    {
        gboolean retval = FALSE;

        if (g_strcmp0 (iid, "HelloWorldApplet") == 0)
            retval = hello_world_applet_start (applet);

        return retval;
    }

    PANEL_APPLET_OUT_PROCESS_FACTORY ("HelloWorldFactory",
                                      PANEL_TYPE_APPLET,
                                      hello_world_factory_callback,
                                      NULL)

Here are the few things that are important in this example:

- `PANEL_APPLET_OUT_PROCESS_FACTORY()`: this creates an applet factory named `HelloWorldFactory`, and each time this applet factory will create an applet instance, it will call `hello_world_factory_callback()` with a PanelApplet object already created.

- `hello_world_factory_callback()`: this checks if the request to create an applet instance is for an applet type supported by the applet factory. Here we can see that we only support the `HelloWorldApplet` applet type. This function returns `TRUE` on success and `FALSE` on failures.

- `hello_world_applet_start()`: this is where we actually setup the PanelApplet widget for the work the applet should do. This can include filling the widget, connecting to signals, etc.

While the previous example is simple, it can be useful to directly subclass the PanelApplet type. This makes it easy to have a per-applet instance private structure, among other benefits.

#### Hello World applet, with a PanelApplet subclass

    #include <gtk/gtk.h>
    #include <panel-applet.h>

    #define HELLO_WORLD_TYPE_APPLET (hello_world_applet_get_type ())

    typedef struct _HelloWorldApplet        HelloWorldApplet;
    typedef struct _HelloWorldAppletClass   HelloWorldAppletClass;
    typedef struct _HelloWorldAppletPrivate HelloWorldAppletPrivate;

    struct _HelloWorldApplet {
        PanelApplet parent_object;

        /*< private >*/
        HelloWorldAppletPrivate *priv;
    };

    struct _HelloWorldAppletClass {
        PanelAppletClass parent_class;
    };

    struct _HelloWorldAppletPrivate
    {
        GtkWidget *label;
    };

    static GType hello_world_applet_get_type (void) G_GNUC_CONST;

    G_DEFINE_TYPE (HelloWorldApplet, hello_world_applet, PANEL_TYPE_APPLET);

    static void
    hello_world_applet_init (HelloWorldApplet *applet)
    {
        applet->priv = G_TYPE_INSTANCE_GET_PRIVATE (applet, HELLO_WORLD_TYPE_APPLET,
                                                    HelloWorldAppletPrivate);

        applet->priv->label = gtk_label_new ("Hello World");
        gtk_container_add (GTK_CONTAINER (applet), applet->priv->label);
        gtk_widget_show (applet->priv->label);
    }

    static void
    hello_world_applet_class_init (HelloWorldAppletClass *klass)
    {
      g_type_class_add_private (class, sizeof (HelloWorldAppletPrivate));
    }

    static gboolean
    hello_world_applet_start (HelloWorldApplet *applet)
    {
        gtk_widget_show (GTK_WIDGET (applet));

        return TRUE;
    }

    static gboolean
    hello_world_factory_callback (HelloWorldApplet *applet,
                                  const gchar      *iid,
                                  gpointer          data)
    {
        gboolean retval = FALSE;

        if (g_strcmp0 (iid, "HelloWorldApplet") == 0)
            retval = hello_world_applet_start (applet);

        return retval;
    }

    PANEL_APPLET_OUT_PROCESS_FACTORY ("HelloWorldFactory",
                                      HELLO_WORLD_TYPE_APPLET,
                                      (PanelAppletFactoryCallback) hello_world_factory_callback,
                                      NULL)

### Using a Context Menu

The Panel Applet library uses GtkAction to define menu items appearing in the context menu of the applet.

#### Guidelines for Context Menu

To help guarantee consistency in the interaction with applets, there are some guidelines that are recommended to follow:

- Do not make the context menu too long: if you have more than five or six menu items, then it might be worth investing efforts on rethinking what is important in the menu.

- For the menu item that will enable the user to configure the applet, use "Preferences" for the label, and try to avoid "Configure", "Configuration", "Settings", etc.

- Avoid putting a "Help" menu item. The user will usually explicitly add the applet, so it is expected that he knows what the applet is about. Putting a "Help" menu item in the context menu is therefore too prominent. It might make sense to add a "Help" button in the Preferences dialog, though.

- If you agree, avoid putting a "About" menu item. To the user, applets are not different applications but elements of one global application, the panel. Of course, this means that credits for working on the applet are not visible to the user.

#### Setting Up the Menu

The only part of dealing with a context menu that is specific to applets is how to setup the context menu. Once it is setup, this is really just a matter of using GtkAction.

To setup the context menu of the applet, the `panel_applet_setup_menu_from_file()` function should be used, with a path to a menu XML file and a GtkActionGroup object containing all actions that are used in the menu XML file. The example below shows how to achieve this:

##### Hello World applet, with a context menu

    #include <glib/gi18n.h>
    #include <gtk/gtk.h>
    #include <panel-applet.h>

    /* This would usually be defined in config.h */
    #define GETTEXT_PACKAGE "hello-world"
    /* This would usually be defined in Makefile.am */
    #define HELLO_WORLD_UI_DIR "/usr/share/hello-world"

    static void hello_world_applet_prefs (GtkAction   *action,
                                          PanelApplet *applet);
    static void hello_world_applet_say   (GtkAction   *action,
                                          PanelApplet *applet);

    static const GtkActionEntry hello_world_menu_actions [] = {
            { "HelloWorldPrefs", GTK_STOCK_HELP, N_("_Preferences"),
              NULL, NULL,
              G_CALLBACK (hello_world_applet_prefs) },
            { "HelloWorldSay", GTK_STOCK_ABOUT, N_("_Say Hello"),
              NULL, NULL,
              G_CALLBACK (hello_world_applet_say) }
    };

    static void
    hello_world_applet_prefs (GtkAction   *action,
                              PanelApplet *applet)
    {
        GtkWidget *dialog;
        dialog = gtk_message_dialog_new (NULL, 0,
                                        GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE,
                                        "Preferences");
        g_signal_connect (dialog, "response",
                          G_CALLBACK (gtk_widget_destroy), NULL);
        gtk_widget_show (dialog);
    }

    static void
    hello_world_applet_say (GtkAction   *action,
                            PanelApplet *applet)
    {
        GtkWidget *dialog;
        dialog = gtk_message_dialog_new (NULL, 0,
                                        GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE,
                                        "Hello World!");
        g_signal_connect (dialog, "response",
                          G_CALLBACK (gtk_widget_destroy), NULL);
        gtk_widget_show (dialog);
    }

    static gboolean
    hello_world_applet_start (PanelApplet *applet)
    {
        GtkWidget *label;
        GtkActionGroup *action_group;
        gchar *ui_path;

        label = gtk_label_new ("Hello World");
        gtk_container_add (GTK_CONTAINER (applet), label);

        action_group = gtk_action_group_new ("Hello World Applet Actions");
        gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE);
        gtk_action_group_add_actions (action_group,
                                      hello_world_menu_actions,
                                      G_N_ELEMENTS (hello_world_menu_actions),
                                      applet);

        ui_path = g_build_filename (HELLO_WORLD_UI_DIR, "hello-world-menu.xml", NULL);
        panel_applet_setup_menu_from_file (applet, ui_path, action_group);

        g_free (ui_path);
        g_object_unref (action_group);

        gtk_widget_show_all (GTK_WIDGET (applet));

        return TRUE;
    }

    static gboolean
    hello_world_factory_callback (PanelApplet  *applet,
                                  const gchar  *iid,
                                  gpointer      data)
    {
        gboolean retval = FALSE;

        if (g_strcmp0 (iid, "HelloWorldApplet") == 0)
            retval = hello_world_applet_start (applet);

        return retval;
    }

    PANEL_APPLET_OUT_PROCESS_FACTORY ("HelloWorldFactory",
                                      PANEL_TYPE_APPLET,
                                      hello_world_factory_callback,
                                      NULL)

Here are the changes compared to the simple example with no context menu:

- We define a list of GtkActionEntry entries: `hello_world_menu_actions`. This will be used later on to build GtkAction objects, with their label and callback. We obviously implement the callbacks.

- We change `hello_world_applet_start()` to define a GtkActionGroup object, to which we add, with `gtk_action_group_add_actions()`, GtkAction objects based on the GtkActionEntry entries. Note that the the last argument to `gtk_action_group_add_actions()` will be passed as user data to the callbacks.

- We also change `hello_world_applet_start()` to add this GtkActionGroup object to the context menu of the applet, by calling `panel_applet_setup_menu_from_file()`. This function takes as argument a path to the menu XML file that will define how to display the GtkAction objects in the context menu.

#### Menu XML File

The file that is used to setup menu with the `panel_applet_setup_menu_from_file()` function is a GtkUIManager UI definition file, without the top-level `<ui>` tag. It must only contain menuitem entries and separators. For example:

    <menuitem name="Hello World Prefs" action="HelloWorldPrefs" />
    <separator/>
    <menuitem name="Hello World Say"   action="HelloWorldSay" />

Alternatively, the `panel_applet_setup_menu()` function can be used with a string containing directly the XML.

### Settings

There are two main design patterns to store settings for an applet:

- Global settings: those are settings that should be shared among all instances of the same applets. For instance, the preferred unit for temperature is something that the user will want to set only one. The way to store those settings is in no way specific to the Panel Applet library, as it should work like in any other application.

- Per-applet instance settings: those are settings that might be different depending on the instance of an applet. For instance, an applet whose goal is to display a picture should make it possible for the user to choose a different picture for each instance of the applet. The Panel Applet library provides API to help with this.

There is actually a third case, which is rather rare, though: per-screen settings. It might be that some applets control some per-screen settings, like the layout of the workspaces. There is currently no API to help with this as it is a very specific case that you will probably never have to deal with for a usual applet.

TODO: describe settings-oriented API

### Making the Applet Visible to the Panel

Simply installing the applet binary will obviously not be enough to make this applet working in the panel. Two files should be installed for this:

- a `.panel-applet` file so that the panel knows that the applet.

- a D-Bus service file to autostart the binary when the panel wants to create an applet.

#### Panel Applet File

The `.panel-applet` file is a key file about the applet binary, describing the applet factory from the binary and the applet types this factory can create.

##### Example

    [Applet Factory]
    Id=HelloWorldFactory
    Name=Hello World Applet Factory
    Description=Factory for the window navigation related applets

    [HelloWorldApplet]
    Name=Hello World
    Description=Factory for the Hello World applet example
    Icon=hello-world-icon

##### Format

The file must contain a `Applet Factory` group with the following keys:

- `Id` (string): the identifier of the applet factory. This must be the same name that will be used as the first parameter to `PANEL_APPLET_OUT_PROCESS_FACTORY()` or `PANEL_APPLET_IN_PROCESS_FACTORY()`.

- `InProcess` (boolean, optional): whether the applet should be in-process or out-of-process. By default, the applet is out-of-process.

- `Location` (string): the path to the applet binary. Only mandatory if `InProcess` is `true`.

- `Name` (localized string, optional): the name of the applet factory. For example: `Hello World Factory`.

- `Description` (localized string, optional): the description of the applet factory. For example: `Factory for the Hello World applet example`.

For each applet type, it must also contain a group named with the applet type identifier. Such a group must have the following keys:

- `Name` (localized string): the name of the applet type. For example: `Hello World`.

- `Description` (localized string, optional): the description of the applet type. For example: `Hello World applet example`.

- `Icon` (string, optional): the icon name of the applet type. For example: `hello-world-icon`. It can also be the path to an icon, but this not recommended.

- `BonoboId` (list of strings, optional): a list of bonobo id. This will tell the panel that this applet type can be used instead of a bonobo applet if the bonobo applet id is in this list.

##### Installation

The `.panel-applet` file must be installed in a specific directory to be discoverable by the panel. You can fetch this directory during `configure` withe following code:

    LIBPANEL_APPLET_DIR=`$PKG_CONFIG --variable=libpanel_applet_dir libpanelapplet-4.0`
    AC_SUBST(LIBPANEL_APPLET_DIR)

#### D-Bus Service File

The communication between the panel and the applet factory is done over D-Bus. When creating an applet, the panel will send a message to the D-Bus service of the applet factory. If the D-Bus service is not running yet, it must be started automatically. We use D-Bus activation for this, which requires install a standard D-Bus service file. Please refer to the D-Bus documentation for more information about D-Bus service files.

This is only needed for out-of-process applets, because in-process applets do no need to have their binary autostarted for obvious reasons.

### Proper Integration with the Panel

Since the applets appear as part of the panel to users, it is important that they behave in a consistent way. A few steps can be completed to achieve proper integration.

#### Panel Background

The panel can have different types of background, depending on how the user configures the panel. By default, applets do not respect the background that is configured and can therefore look out of place.

The `panel_applet_set_background_widget()` function can be used to automatically have the right background drawn for a specific widget. Just using this function on the PanelApplet object itself, or its child is usually enough to have proper background integration.

In some rare cases, though, `panel_applet_set_background_widget()` will not be enough. The solution is then to connect to the `"change-background"` signal of the PanelApplet object: it will be emitted when the background has changed, and it will provide the cairo_pattern_t pattern to use as a basis to draw the background.

#### Panel Lockdown

The panel has proper support for lockdown, and when it is locked down, it is expected that all applets behave consistently in a lockdown mode too. This generally means that the preferences of the applet should not be accessible, but it could also imply a restriction on the behavior of the applet.

The `panel_applet_get_locked_down()` function can be used to query the state of the panel lockdown. It is also possible to react to changes by monitoring the `"locked-down"` property of the PanelApplet object. You can achieve this by connecting to the `"notify::locked-down"` event.

In most cases, the GBinding API is enough to respect the panel lockdown: `g_object_bind_property()` can be used to automatically update the visiblity of a menu item in the context menu of the applet. In the following example, the `"HelloWorldPrefs"` action (which is an action from the context menu) will only be displayed if the panel is not locked down.

    action = gtk_action_group_get_action (action_group, "HelloWorldPrefs");
    g_object_bind_property (applet, "locked-down",
                            action, "visible",
                            G_BINDING_DEFAULT|G_BINDING_INVERT_BOOLEAN|G_BINDING_SYNC_CREATE);

It is obviously possible to use `g_object_bind_property()` to change the visibility of widgets that appear outside of the context menu, like a button in a window.

### Out-of-Process vs In-Process

Applets can either live in their own process ("out-of-process") or in the panel process ("in-process"). The decision to choose one or the other is done at build time, with the macro that you use to define the applet factory: `PANEL_APPLET_OUT_PROCESS_FACTORY()` is used for out-of-process applets while `PANEL_APPLET_IN_PROCESS_FACTORY()` is used for in-process applets. Obviously, only one of those two macros can be used.

For most practical matters, from the applet perspective, the two options are the same. In-process applets do offer a slightly better performance when the applet is loaded, but this should not affect much the user experience. However, an in-process applet can potentially affect the whole behavior of the panel, especially in case of crashes or memory corruptions: a crash in an in-process applet will crash the whole panel. It is therefore recommended to use out-of-process applets.

### Writing an applet in languages other than C

The Panel Applet library comes with support for GObject Introspection. This makes it possible to write applets in the languages that have bindings based on GObject Introspection.

Here is a simple example of a python applet:

#### Hello World applet, in Python

    from gi.repository import Gtk
    from gi.repository import PanelApplet

    def applet_fill(applet):
        label = Gtk.Label("Hello World")
        applet.add(label)
        applet.show_all()

    def applet_factory(applet, iid, data):
        if iid != "HelloWorldApplet":
           return False

        applet_fill(applet)

        return True

    PanelApplet.Applet.factory_main("HelloWorldFactory",
                                    PanelApplet.Applet.__gtype__,
                                    applet_factory, None)

The only limitation of writing an applet in a language other than C is that the applet will not be able to run in the panel process: it will have to stay out-of-process. However, since it is recommended to leave applets out-of-process, this limitation is mitigated.

## Porting Applets from GNOME 2.x

### Porting a D-Bus-based Applet

There is no major issue porting a D-Bus-based applet from GNOME 2.x, from a Panel Applet library perspective. It is actually likely that the main issues will be that the applet has to be ported to GTK+ 3. Please refer to the GTK+ 2 to GTK+ 3 migration guide for this.

### Porting a Bonobo-based Applet

Please see the documentation on the wiki.

## API Reference