How to write plugins for LXPanel

From LXDE.org
Jump to: navigation, search

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

Note that this HowTo is for LXPanel version 0.7.0 or newer, older versions had another plugins API.

Preconditions

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

- LXPanel version at least 0.7.0
- gtk+ version at least 2.18.0

Libraries necessary to successfully compile a plugin for LXPanel (installed from source or using a package manager such as apt-get or yum):

- lxpanel-devel
- gtk2-devel

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 is a C structure which should be filled: LXPanelPluginInit.

The LXPanelPluginInit structure contains a set of functions (otherwise known as callbacks) allowing the plugin to be manipulated, and some important common properties of plugins of that type. During its lifetime, the LXPanel registers class for each type of the plugins available. It can be done implicitly scanning modules directory or explicitly using panel API call. All interactions with plugin instances are done through the available functions defined in the LXPanelPluginInit structure.

The LXPanelPluginInit structure is defined as follows (directly from /usr/include/lxpanel/plugin.h) as of version 0.7.0:

typedef struct {
    void (*init)(void);         /* optional startup */
    void (*finalize)(void);     /* optional finalize */
    char *name;                 /* name to represent in lists */
    char *description;          /* tooltip text */
    GtkWidget *(*new_instance)(LXPanel *panel, config_setting_t *settings);
    GtkWidget *(*config)(LXPanel *panel, GtkWidget *instance);
    void (*reconfigure)(LXPanel *panel, GtkWidget *instance);
    gboolean (*button_press_event)(GtkWidget *widget, GdkEventButton *event, LXPanel *panel);
    void (*show_system_menu)(GtkWidget *widget);
    gboolean (*update_context_menu)(GtkWidget *plugin, GtkMenu *menu);
    gboolean (*control)(GtkWidget *plugin, const char *cmd); /* not implemented */
    int one_per_system : 1;     /* True to disable more than one instance */
    int expand_available : 1;   /* True if "stretch" option is available */
    int expand_default : 1;     /* True if "stretch" option is default */
    int superseded : 1;         /* True if plugin was superseded by another */
} LXPanelPluginInit;

Read detailed descriptions for each field of that structure in the file /usr/include/lxpanel/plugin.h.

GTK level.

Once a plugin class is registered, plugin of that class 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, 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 LXPanelPluginInit structure provide the plugin developer with a placeholder for any plugin-specific state information. This is where a developer defines his/her own structure and binds it, on a per-instance basis, to the plugin instance (which is a GtkWidget) using function lxpanel_plugin_set_data(). 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) LXPanelPluginInit specific to the plugin is registered.
2) The plugin is added to the panel – plugin's 'new_instance' callback is called.
3) Modifications to the panel are made based on normal user interactions.  
   At this point the plugin's 'config', and 'reconfigure' callbacks can be called in any order and should be handled appropriately.
4) The plugin is removed from the panel – destructor function for data (if set to not NULL on lxpanel_plugin_set_data() call) 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 'new_instance' callback (it should create the appropriate GtkWidget for the plugin) until the plugin is removed from the panel. The private level of a plugin is 'alive' from the execution of the 'new_instance' callback (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 <glib/gi18n.h>

#include <lxpanel/plugin.h>

#include <stdio.h>

Next, let's take advantage of the private data 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 LXPanelPluginInit's 'new_instance' callback which creates a widget.

GtkWidget *test_constructor(LXPanel *panel, config_setting_t *settings)
{
 /* panel is a pointer to the panel and
     settings is a pointer to the configuration data
     since we don't use it, we'll make sure it doesn't
     give us an error at compilation time */
 (void)panel;
 (void)settings;

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

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

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

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

 // create a widget instance 
 GtkWidget *pLabel = gtk_label_new(cIdBuf);

 // need to create a container to be able to set a border
 GtkWidget *p = gtk_event_box_new();

 // our widget doesn't have a window...
 // it is usually illegal to call gtk_widget_set_has_window() from application but for GtkEventBox it doesn't hurt
 gtk_widget_set_has_window(p, FALSE);

 // bind private structure to the widget assuming it should be freed using g_free()
 lxpanel_plugin_set_data(p, pTest, g_free);

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

 // add the label to the container
 gtk_container_add(GTK_CONTAINER(p), pLabel);

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

 // success!!!
 return p;
}

Next step: define our plugin type name (test):

FM_DEFINE_MODULE(lxpanel_gtk, test)

Finally, let's define our plugin's LXPanelPluginInit structure:

/* Plugin descriptor. */
LXPanelPluginInit fm_module_init_lxpanel_gtk = {
   .name = N_("TestPlugin"),
   .description = N_("Run a test plugin."),

   // assigning our functions to provided pointers.
   .new_instance = test_constructor
};

That's it.

Notice that we did not provide an implementation for other callbacks. LXPanel is kind enough not to call any function that is not defined inside a plugin. Having said that, only mandatory settings in LXPanelPluginInit are 'name' and 'new_instance' so that the plugin can be represented in plugin selection dialog and can be created at all.

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

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

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, required for lxpanel plugins.

After placing the resulting test.so file in /usr/lib/lxpanel/plugins (the place may be different depending on your distro, look where LXPanel puts other dynamic plugins), restart the panel (using lxpanelctl restart), then 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