Introduction

This tutorial is based on AndrewBurton's panel applets tutorial. It sprang from the frustration with insufficient documentation AdamSchreiber had while writing Seahorse's Encryption Applet.

You may also be interested in Davyd's notes on debugging GNOME applets.

What is an Applet?

In Gnome, an applet is a small application, designed to sit in the Gnome panel, providing quick and easy access to a control, such as a volume control, a network status display, or even a weather gauge.

Applets require the libpanel-applet library to run and if you want to develop them, you'll need to install the development package (usually marked as -dev or -devel, depending on the distribution). Due to their small nature, they're often less complex, and so easier to master for the developer new to the Gnome environment.

A fresh install of a Gnome desktop will have the date and a volume applet in the top right corner. Laptop users find it useful to have an applet displaying the battery life.

Applet API Notes

The following are the objects an applet inherits from

  GObject
   +----GtkObject
         +----GtkWidget
               +----GtkContainer
                     +----GtkBin
                           +----GtkEventBox
                                 +----PanelApplet

Because PanelApplet inherits from GtkBin, you can place any widget into your applet as you would with any container widget. Likewise you can connect your applet to any signals GtkEventBox provides.

To have the applet behave properly in the panel, it is necessary to implement signal handlers for

"change-background"
            void        user_function      (PanelApplet *panelapplet,
                                            PanelAppletBackgroundType arg1,
                                            gpointer arg2,
                                            GdkPixmap *arg3,
                                            gpointer user_data);
"change-size"
            void        user_function      (PanelApplet *panelapplet,
                                            gint arg1,
                                            gpointer user_data);

Implementing "change-background" allows your applet to have the same appearance as the panel whether the user wants a solid color, transparency, or a background picture. Implementing "change-size" will allow your icon to change size as the panel does (certain applets do not change size with the panel, in general those that display info or are non-static i.e. GEyes, CpuFreq, BattStat, etc).

If you want to pop up a menu on a left mouse click, you should implement a handler for "button-press-event" that builds the appropriate menu. To have your popup menu appear in the correct place, it will be necessary to pass a position function to gtk_menu_popup. Right click menus are implemented with xml included in a separate file or in the code itself and are handled by the panel applet library. An example of the xml for a right click menu will be provided below.

In panel applets you will notice that a main function is never defined. This is because the macro PANEL_APPLET_BONOBO_FACTORY defines the main function for all applets based on the panel applet library.

Infrastructure

Technically, applets are Bonobo controls embedded in the Gnome panel. This means that there are a few slight differences to stand-alone Gnome programs. The first difference is that each applet requires a 'server' file, which contains a description of the Bonobo capabilities. If this doesn't make much sense, don't worry. The only thing most developers need to do is edit a file, replacing some fields with the specifics of their applet.

Let's have a look at a sample .server file.

<oaf_info>
<oaf_server iid="OAFIID:ExampleApplet_Factory" type="exe"
            location="/usr/lib/gnome-panel/myexample">

        <oaf_attribute name="repo_ids" type="stringv">
                <item value="IDL:Bonobo/GenericFactory:1.0"/>
                <item value="IDL:Bonobo/Unknown:1.0"/>
        </oaf_attribute>
        <oaf_attribute name="name" type="string" value="Example Applet Factory"/>
        <oaf_attribute name="description" type="string" value="Factory to create the example applet"/>
</oaf_server>

<oaf_server iid="OAFIID:ExampleApplet" type="factory"
            location="OAFIID:ExampleApplet_Factory">

        <oaf_attribute name="repo_ids" type="stringv">
                <item value="IDL:GNOME/Vertigo/PanelAppletShell:1.0"/>
                <item value="IDL:Bonobo/Control:1.0"/>
                <item value="IDL:Bonobo/Unknown:1.0"/>
        </oaf_attribute>
        <oaf_attribute name="name" type="string" value="Example Applet"/>
        <oaf_attribute name="description" type="string" value="An example applet"/>
        <oaf_attribute name="panel:category" type="string" value="Amusements"/>
        <oaf_attribute name="panel:icon" type="string" value="myicon.png"/>
</oaf_server>
</oaf_info>

A few things to note. One is the location of our executable file, defined by the oaf_server tag in the location value, where the type is "exe". In this example, our executable file is called myexample and is placed in /usr/lib/gnome-panel/. Secondly, we define the name of our applet 'factory', ExampleApplet_Factory. This is the name of the .server file, and is usually placed in /usr/lib/bonobo/servers/.

To build a right click menu from a file, it is necessary to create an xml file that is passed to panel_applet_setup_menu_from_file GNOME_ExampleApplet.xml

<Root>
   <popups>
      <popup name="button1">
         <menuitem name="Item 1" verb="Prefs" _label="_Preferences"
                   pixtype="stock" pixname="gtk-properties"/>
         <menuitem name="Item 2" verb="Help" _label="_Help"
                   pixtype="stock" pixname="gtk-help"/>
         <menuitem name="Item 3" verb="About" _label="_About"
                   pixtype="stock" pixname="gnome-stock-about"/>
      </popup>
   </popups>
</Root>

It is also possible to include the xml in your code and use panel_applet_setup_menu:

static const char Context_menu_xml [] =
   "<popup name=\"button1\">\n"
   "   <menuitem name=\"Item1\" "
   "             verb=\"Prefs\" "
   "           _label=\"_Preferences...\"\n"
   "          pixtype=\"stock\" "
   "          pixname=\"gtk-properties\"/>\n"
   "   <menuitem name=\"Item2\" "
   "             verb=\"Help\" "
   "           _label=\"_Help\"\n"
   "          pixtype=\"stock\" "
   "          pixname=\"gnome-help\"/>\n"
   "   <menuitem name=\"Item3\" "
   "             verb=\"About\" "
   "           _label=\"_About\"\n"
   "          pixtype=\"stock\" "
   "          pixname=\"gnome-stock-about\"/>\n"
   "</popup>\n";

Example Applet Code

example-applet.h

#ifndef __EXAMPLE_APPLET_H__
#define __EXAMPLE_APPLET_H__

#include <glib-object.h>
#include <panel-applet.h>

G_BEGIN_DECLS

#define EXAMPLE_TYPE_APPLET                example_applet_get_type()

#define EXAMPLE_APPLET(obj)                (G_TYPE_CHECK_INSTANCE_CAST ((obj), EXAMPLE_TYPE_APPLET, ExampleApplet))

#define EXAMPLE_APPLET_CLASS(klass)        (G_TYPE_CHECK_CLASS_CAST ((klass), EXAMPLE_TYPE_APPLET, ExampleAppletClass))

#define EXAMPLE_IS_APPLET(obj)             (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EXAMPLE_TYPE_APPLET))

#define EXAMPLE_IS_APPLET_CLASS(klass)     (G_TYPE_CHECK_CLASS_TYPE ((klass), EXAMPLE_TYPE_APPLET))

#define EXAMPLE_APPLET_GET_CLASS(obj)      (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_APPLET, ExampleAppletClass))

typedef struct {
        PanelApplet parent;
} ExampleApplet;

typedef struct {
        PanelAppletClass parent_class;
} ExampleAppletClass;

GType example_applet_get_type (void);

ExampleApplet* example_applet_new (void);

G_END_DECLS
#endif

example-applet.c

#include "example-applet.h"

G_DEFINE_TYPE (ExampleApplet, example_applet, PANEL_TYPE_APPLET);

static void example_applet_change_background (PanelApplet *applet,
                                              PanelAppletBackgroundType type,
                                              GdkColor  *colour,
                                              GdkPixmap *pixmap);

static void
example_applet_class_init (ExampleAppletClass *klass)
{
}

static void
example_applet_init (ExampleApplet *self)
{
    g_signal_connect(self, "change-background", G_CALLBACK (example_applet_change_background) NULL);
}

ExampleApplet*
example_applet_new (void)
{
        return g_object_new (EXAMPLE_TYPE_APPLET, NULL);
}

static void
example_applet_change_background (PanelApplet *applet,
                                   PanelAppletBackgroundType type,
                                   GdkColor  *colour,
                                   GdkPixmap *pixmap)
{
  GtkRcStyle *rc_style;
  GtkStyle *style;

  /* reset style */
  gtk_widget_set_style (GTK_WIDGET (applet), NULL);
  rc_style = gtk_rc_style_new ();
  gtk_widget_modify_style (GTK_WIDGET (applet), rc_style);
  gtk_rc_style_unref (rc_style);

  switch (type){
    case PANEL_NO_BACKGROUND:
      break;
    case PANEL_COLOR_BACKGROUND:
      gtk_widget_modify_bg (GTK_WIDGET (applet),
                            GTK_STATE_NORMAL, colour);
      break;
    case PANEL_PIXMAP_BACKGROUND:
      style = gtk_style_copy (GTK_WIDGET (applet)->style);

      if (style->bg_pixmap[GTK_STATE_NORMAL])
        g_object_unref (style->bg_pixmap[GTK_STATE_NORMAL]);

      style->bg_pixmap[GTK_STATE_NORMAL] = g_object_ref (pixmap);
      gtk_widget_set_style (GTK_WIDGET (applet), style);
      g_object_unref (style);
      break;
  }
}


static const BonoboUIVerb example_applet_menu_verbs [] = {
        BONOBO_UI_UNSAFE_VERB ("Prefs", preferences_cb),
        BONOBO_UI_UNSAFE_VERB ("Help", help_cb),
        BONOBO_UI_UNSAFE_VERB ("About", about_cb),

        BONOBO_UI_VERB_END
};

static gboolean
example_applet_fill (PanelApplet *applet)
{
    ExampleApplet *example_applet;    
                
    g_return_val_if_fail (PANEL_IS_APPLET (applet), FALSE);

    gtk_widget_show_all (GTK_WIDGET (applet));
        
    panel_applet_setup_menu_from_file (applet,
                                       UIDIR,
                                       "GNOME_ExampleApplet.xml",
                                       NULL,
                                       example_applet_menu_verbs,
                                       example_applet);

        if (panel_applet_get_locked_down (applet)) {
                BonoboUIComponent *popup_component;

                popup_component = panel_applet_get_popup_component (applet);

                bonobo_ui_component_set_prop (popup_component,
                                              "/commands/Props",
                                              "hidden", "1",
                                              NULL);
        }

        return TRUE;
}

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

        if (!strcmp (iid, "OAFIID:GNOME_ExampleApplet"))
                retval = example_applet_fill (applet); 
   
        if (retval == FALSE) {
                exit (-1);
        }

        return retval;
}

PANEL_APPLET_BONOBO_FACTORY ("OAFIID:GNOME_ExampleApplet_Factory",
                             EXAMPLE_TYPE_APPLET,
                             "example-applet",
                             "0",
                             example_applet_factory,
                             NULL)

Resources

Introduction to GObjects

GObject Boiler Plate Generator

GObjects Reference


CategoryDeveloperTutorial CategoryDeveloperTutorial

Newcomers/PanelAppletTutorial (last edited 2015-09-16 22:58:29 by CarlosSoriano)