How to write plugins for LXPanel

From LXDE.org
Jump to: navigation, search

This article explains how to write plug-ins for LXPanel.

Preconditions

Package assumptions made during the development of this how-to:

- LXPanel version 0.5.8
- gtk+ version 2.24.8
- pkg-config 0.25

All examples/development were done on a Fedora 16 x86_64 machine. The Fedora 16 distribution comes with LXPanel 0.5.6 by default -- LXPanel 0.5.8 was built and installed from source.

Libraries necessary to successfully compile a plugin for LXPanel (installed from source or using a package manager such as apt-get or yum – as in the case of the development machine):

- lxpanel-devel (kept at 0.5.6 on the development machine)
- gtk2-devel
- libXpm-devel
- menu-cache-devel
- libwnck-devel
- libXres-devel
- startup-notification-devel

Additional dependencies required by LXPanel:

- wireless-tools-devel (required by the netstat plugin)

Also assumed:

- intermediate knowledge of the C programming language (structures, pointers and pointers to functions)
- corresponding level of knowledge of GTK+ (depending on the complexity of the plugin)

Guidelines

An LXPanel plugin can be thought of as having 3 levels – API/class level, GTK level and private level.

API/class level.

On the highest level, there are two C structures to be aware of: PluginClass and Plugin.

The PluginClass structure contains a set of functions allowing the plugin to be manipulated. During its lifetime, the LXPanel instantiates one (and only one) class for each of the plugins available – this happens either at LXPanel start-up, or when 'Add/Remove Items from Panel' is selected via a right-click on the panel. Once a plugin class is instantiated, all interactions with it are done through the available functions: 'constructor', 'destructor', 'config', 'save', and 'panel_configuration_changed'.

The PluginClass structure is defined as follows (directly from /usr/include/lxpanel/plugin.h):

typedef struct {
   unsigned short structure_size;     /* Structure size, for versioning support */	
   unsigned short structure_version;  /* Structure version, for versioning support */  
   char * fname;		       /* Plugin file pathname */
   int count;			       /* Reference count */
   GModule * gmodule;	               /* Associated GModule structure */
   int dynamic : 1;		       /* True if dynamically loaded */
   int unused_invisible : 1;          /* Unused; reserved bit */
   int not_unloadable : 1;	       /* Not unloadable due to GModule restriction */
   int one_per_system : 1;	       /* Special: only one possible per system, such as system tray */
   int one_per_system_instantiated : 1;/* True if one instance exists */
   int expand_available : 1;	       /* True if "stretch" option is available */
   int expand_default : 1;	       /* True if "stretch" option is default */
   /* These fields point within the plugin image. [Defined at 'private' level] */
   char * type;	        /* Internal name of plugin, to match external filename */
   char * name;		/* Display name of plugin for selection UI */
   char * version;		/* Version of plugin */
   char * description;	        /* Brief textual description of plugin for selection UI */
   int (*constructor)(struct _Plugin * plugin, char ** fp);     /* Create an instance of the plugin */
   void (*destructor)(struct _Plugin * plugin);                 /* Destroy an instance of the plugin */
   void (*config)(struct _Plugin * plugin, GtkWindow * parent); /* Request the plugin to display its configuration dialog */ 
   void (*save)(struct _Plugin * plugin, FILE * fp); 	         /* Request the plugin to save its configuration to a file */
   void (*panel_configuration_changed)(struct _Plugin * plugin);/* Request the plugin to do a full redraw after a panel configuration change */
 } PluginClass;

The Plugin structure is a container used by LXPanel to handle a plugin and all three of its levels. The API level is referred to via the 'class' variable, the GTK level is referred to via the 'pwid' variable and the private level is referred to via the 'priv' variable. This structure also contains GTKWidget-specific options used by LXPanel ('expand', 'padding', and 'border').

The Plugin structure is defined as follows:

typedef struct _Plugin {
   PluginClass * class;	/* Back pointer to PluginClass */
   Panel * panel;		/* Back pointer to Panel */
   GtkWidget * pwid;		/* Top level widget; plugin allocates, but plugin mechanism, not plugin itself, destroys this */
   int expand;			/* Expand ("stretch") setting for container */
   int padding;		        /* Padding setting for container */
   int border;			/* Border setting for container */
   gpointer priv;		/* Private context for plugin; plugin frees this in its destructor */
} Plugin;

GTK level.

Once a plugin class is instantiated, it is available to be added to the panel. If the user chooses to add a plugin, the plugin is 'asked' by the panel to create its graphical representation (think the moving graph for the CPU activity plugin or the two computer screens for the network plugin). It is up to the plugin to create everything it needs including, but not limited to, handling mouse button presses, system notifications, icons and anything else that might be necessary. While it's up to the plugin to create the GTKWidget (referenced by 'pwid' inside the Plugin structure), the LXPanel actually destroys it once the user removes the plugin from the panel.

Private level.

At the private level, a plugin is allowed to have an internal representation of its state. In other words: neither LXPanel, nor the PluginClass structure provide the plugin developer with a placeholder for plugin-specific configuration, or any other plugin-specific state information. This is where a developer defines his/her own structure and assigns it, on a per-instance basis, to the Plugin's 'priv' variable. The example in the Guide/HowTo section should clear up any confusion created by this explanation.

Plugin lifetime is also something to keep in mind while developing a plugin. In general, the following sequence is to be expected as to interactions between LXPanel and a plugin:

1) PluginClass specific to the plugin is instantiated.
2) The plugin is added to the panel – plugin's 'constructor' function is called.
3) Modifications to the panel are made based on normal user interactions.  
   At this point the plugin's  'save', 'config', and 'panel_configuration_changed' functions
   can be called in any order and should be handled appropriately.
4) The plugin is removed from the panel – plugin's 'destructor' function is called.

The API level of a plugin is 'alive' for as long as the panel instance is active (and has access to the plugin module [.so file] – unless it's a statically-linked plugin). The GTK level of a plugin is 'alive' from the execution of the 'constructor' function (it is up to the developer to create the appropriate GTKWidget for the plugin) until the plugin is removed from the panel (the panel actually destroys the GTKWidget, if it still exists after the call to the 'destructor' function). The private level of a plugin is 'alive' from the execution of the 'constructor' function (it is the developer's responsibility to allocate the appropriate structure) to the execution of the 'destructor' function (it is the developer's responsibility to clean up after him-/her-self).

Guide / How to

Having gone through the general information about LXPanel and plugins, here's how to create a basic plugin.

As explained in 2noob2banoob's HowTo [see: External Links], there are a few ways to approach the compilation of a plugin. In the example, below, we'll do everything by hand so that it will be up to the developer to generate the appropriate makefile(s) and/or autoconf scripts once he/she feels comfortable with the plugin infrastructure.

The roadmap for this HowTo is as follows:

1) Create the plugin source file
2) Compile the source file into a shared library
3) Test it within the currently-running LXPanel instance.

First, let's include everything that we will need in our example – especially the lxpanel/plugin.h file.

#include <gtk/gtk.h>
#include <gdk/gdk.h>
#include <glib/gi18n.h>

#include <lxpanel/plugin.h>

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

Next, let's take advantage of the Plugin's 'priv' variable to have our plugin keep track of how many instances are currently running.

// internal to the plugin source, not used by the 'priv' variable
static int iInstanceCount = 0;

/* the plugin's id – an instance of this struct 
   is what will be assigned to 'priv' */
typedef struct 
{
  gint iMyId;
} TestPlugin; 

Continuing, let's define our versions of the PluginClass's 'constructor', et.al. functions.

constructor:

static int 
test_constructor(Plugin * p, char ** fp)
{
 /* fp is a pointer to the configuration file stream
     since we don't use it, we'll make sure it doesn't
     give us an error at compilation time */
 (void)fp;

 // allocate our private structure instance
 TestPlugin *pTest = g_new0(TestPlugin, 1);

 // put it where it belongs
 p->priv = pTest;

 // update the instance count
 pTest->iMyId = ++iInstanceCount;

 // make a label out of the ID
 char cIdBuf[10] = {'\0'};

 sprintf(cIdBuf, "TP-%d", pTest->iMyId);

 GtkWidget *pLabel = gtk_label_new(cIdBuf);

 // need to create a widget to show
 p->pwid = gtk_event_box_new();

 // set border width
 gtk_container_set_border_width(GTK_CONTAINER(p->pwid), 1);

 // add the label to the container
 gtk_container_add(GTK_CONTAINER(p->pwid), GTK_WIDGET(pLabel));

 // set the size we want
 gtk_widget_set_size_request(p->pwid, 40, 25);

 // our widget doesn't have a window...
 gtk_widget_set_has_window(p->pwid, FALSE);

 // show our widget
 gtk_widget_show_all(p->pwid);

 // success!!!
 return 1;
}

destructor:

static void 
test_destructor(Plugin * p)
{
  // decrement our local instance count
  --iInstanceCount;

  // find our private structure instance
  TestPlugin *pTest = (TestPlugin *)p->priv;

  // free it  -- no need to worry about freeing 'pwid', the panel does it
  g_free(pTest);
}

configure:

static void test_configure(Plugin * p, GtkWindow * parent)
{
  // doing nothing here, so make sure neither of the parameters
  // emits a warning at compilation
  (void)p;
  (void)parent;
}

save_configuration:

static void test_save_configuration(Plugin * p, FILE * fp)
{
  // doing nothing here, so make sure neither of the parameters
  // emits a warning at compilation
  (void)p;
  (void)fp;
}

Finally, let's define our plugin's PluginClass:

/* Plugin descriptor. */
PluginClass test_plugin_class = {

   // this is a #define taking care of the size/version variables
   PLUGINCLASS_VERSIONING,

   // type of this plugin
   type : "test",
   name : N_("TestPlugin"),
   version: "1.0",
   description : N_("Run a test plugin."),

   // we can have many running at the same time
   one_per_system : FALSE,

   // can't expand this plugin
   expand_available : FALSE,

   // assigning our functions to provided pointers.
   constructor : test_constructor,
   destructor  : test_destructor,
   config : test_configure,
   save : test_save_configuration
};

That's it.

Notice that we did not provide an implementation for the 'panel_configuration_changed' function. LXPanel is kind enough not to call any function that is not defined inside a plugin. Having said that, it's a good idea to at least provide a plugin's 'constructor' and 'destructor' functions so that the plugin knows how to create- and cleanup after- itself.

One more thing, before we proceed (this is important): LXPanel expects the following:

- the 'type' variable inside a plugin definition to be the same as the name of the shared library file in which it is found.  
  In other words: even though our source file will be named testplugin.c, since we defined the 'type' to be “test”, 
  we have to create a shared library named test.so.
- the name of the plugin's class to correspond to the 'type' variable plus the string "_plugin_class". 
  In other words, if we define our plugin's type to be "test", then the structure we define must be named "test_plugin_class".

Now, let's try to make a shared library for our plugin (source saved as testplugin.c):

gcc -Wall `pkg-config --cflags lxpanel gtk+-2.0` -shared -fPIC testplugin.c -o test.so `pkg-config --libs lxpanel gtk+-2.0`

The above line uses gcc to compile testplugin.c into a shared object file named test.so using pkg-config to provide C compiler flags and, at the end, library flags, for lxpanel and gtk+-2.0 (since both of those are dependencies for our plugin).

After placing the resulting test.so file in /usr/lib/lxpanel/plugins (this is where LXPanel looks when it wants to find dynamic plugins), we right-click on the panel and attempt to add a plugin to the panel:

Screenshot-AddTestPlugin.png

After adding two TestPlugins, the panel looks like this:

Screenshot-TestPluginInAction.png

and panel preferences look like this:

Screenshot-PanelPreferences.png

Now go forth and plug-away!

See also