Difference between revisions of "LXPanel plugin development"

From LXDE.org
Jump to: navigation, search
m (Bot: Adding category Spam)
(not spam)
Line 437: Line 437:
  
 
[[Category:Development]]
 
[[Category:Development]]
[[Category:Spam]]
 
  
 
[[fr:Développement de greffons pour LXPanel]]
 
[[fr:Développement de greffons pour LXPanel]]

Revision as of 04:09, 5 November 2016

This page or file is licensed under the GNU Free Documentation License. See the license details.


LXPanel plugin development HowTo

Introduction

This is a HowTo for writing LXPanel plugins (applets), written by 2noob2banoob. This HowTo is licensed under the GNU Free Documentation License, so it is open content, feel free to modify and redistribute it. This HowTo is intended to help newbies write their first simple LXPanel plugin. It is far from perfect though, mainly because I am new to LXPanel plugin development myself. Therefore I will sometimes say I do not know something or maybe be wrong. If you have any suggestions on how to improve this HowTo, you can send an e-mail to 2noob2banoob@gmail.com or the LXDE mailing list (lxde-list@lists.sourceforge.net).

Prerequisities

For as far as I know, there are no prerequisities other than having LXPanel installed, and of course basic programming and command-line skills. The only needed development header is already available in the lxpanel package. Of course you do need a C compiler, I am going to assume you have gcc which most probably is already installed on your system.

Choosing a method

There are several methods for compilation:

  • Using autotools: this is the way most parts of LXDE are compiled. Though this is the most standard way, it makes the compilation process unneccessarily long and generates makefiles which are hardly human-readable, thereby preventing you from knowing what exactly is happening during the compilation process. In order to make modifications to the way the code is compiled, you will have to try/learn to understand the autotools input files, which may be a real pain. This is one of the methods elaborated in this document.
  • Using some autotools alternative: I believe PCMan is currently experimenting with CMake, a system which does approximately the same thing as autotools. I have absolutely no experience with CMake or any other autotools alternative and I will not elaborate it in this tutorial, but it is good to know they exist.
  • Using a handwritten makefile: though this method is not standard within the LXDE project, I personally prefer it over the use of autotools. This method is simple and does not clutter your project folder with makefile generating files, and editing the makefile is easy because it's much like a shell script. This is one of the methods elaborated in this document.
  • Not using a makefile: you can also manually enter the entire build and installation commands in your terminal. I do not recommend this method because the build command is very long, but if you are a real die-hard, go ahead. This method is not further elaborated in this document, but the build commands are essentially the same as the commands which are put in the handwritten makefile.

Building the plugin by using autotools

Getting started

A good starting point would be the example_plug plugin, which comes with the lxpanel-plugins source.

Download it with the following command:

$ git clone git://lxde.git.sourceforge.net/gitroot/lxde/lxpanel-plugins


Enter the folder lxpanel-plugins, you can now delete all folders here except example_plug. It might be wise to make a copy of example_plug in case you screw up. Although this plugin is the smallest when it comes to source code, it is still not completely barebone. To start with a barebone plugin, replace src/example.c with the barebone example.c I made:

/**
* file: example.c
* description: A completely barebone lxpanel plugin doing nothing
* author: 2noob2banoob
* based on example_plug by lxde-develloppers
* GPL v2.0
*/

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <glib/gi18n.h>

#include <string.h>
#include <lxpanel/plugin.h>

#define EVENT_MOUSE_BUTTON_PRESS_LEFT 1
#define EVENT_MOUSE_BUTTON_PRESS_MIDDLE 2
#define EVENT_MOUSE_BUTTON_PRESS_RIGHT 3

typedef struct {
Plugin * plugin;
GtkWidget *main;
GtkWidget *widget;
GtkTooltips *tip;
} Example;

gboolean button_press_event( GtkWidget *widget, GdkEventButton* event, Plugin* plugin)
{
if( event->button == EVENT_MOUSE_BUTTON_PRESS_LEFT )
{

}
else if( event->button == EVENT_MOUSE_BUTTON_PRESS_MIDDLE )
{

}
else if( event->button == EVENT_MOUSE_BUTTON_PRESS_RIGHT )
{
GtkMenu* popup = (GtkMenu*)lxpanel_get_panel_menu( plugin->panel, plugin, FALSE ); /* lxpanel_menu, can be reimplemented */
gtk_menu_popup( popup, NULL, NULL, NULL, NULL, event->button, event->time );
return TRUE;
}

RET2(TRUE);
}

static gint update_tooltip(Example *egz)
{
char *tooltip;
ENTER;

tooltip = g_strdup_printf("LXPanel Example plugin");
gtk_tooltips_set_tip(egz->tip, egz->main, tooltip, NULL);
g_free(tooltip);

RET(TRUE);
}

static int example_constructor(Plugin *p, char** fp)
{
Example *egz;

/* initialization */
egz = g_new0(Example, 1);
egz->plugin = p;
p->priv = egz;

p->pwid = gtk_event_box_new(); //you may want to change this to a hbox or vbox if you want to use multiple widgets
GTK_WIDGET_SET_FLAGS( p->pwid, GTK_NO_WINDOW );
gtk_container_set_border_width( GTK_CONTAINER(p->pwid), 2 );

egz->widget = gtk_label_new("Hello World");
gtk_container_add(GTK_CONTAINER(p->pwid), egz->widget);

egz->main = p->pwid;
egz->tip = gtk_tooltips_new();
update_tooltip(egz);

g_signal_connect (G_OBJECT (p->pwid), "button_press_event", G_CALLBACK (button_press_event), (gpointer) p);
}

static void applyConfig(Plugin* p)
{
Example *egz = (Example *) p->priv;

update_tooltip(egz);
}

static void config(Plugin *p, GtkWindow* parent) {
GtkWidget *dialog;
Example *egz = (Example *) p->priv;
dialog = create_generic_config_dlg(_(p->class->name),
GTK_WIDGET(parent),
(GSourceFunc) applyConfig, (gpointer) p,
//_("displayed_varname"), &egz->variable, datatype, //datatype can be CONF_TYPE_STR, CONF_TYPE_INT, CONF_TYPE_BOOL etc.
NULL);
gtk_window_present(GTK_WINDOW(dialog));
}

static void example_destructor(Plugin *p)
{
ENTER;
Example *egz = (Example *)p->priv;
g_free(egz);
}

static void save_config( Plugin* p, FILE* fp )
{

}

PluginClass example_plugin_class = {

PLUGINCLASS_VERSIONING,

type : "example",
name : N_("Example plugin"),
version: "0.1",
description : N_("Example plugin: barebone doing nothing"),

constructor : example_constructor,
destructor  : example_destructor,
config  : config,
save  : save_config,
panel_configuration_changed : NULL
};

Before you start coding, it is a good idea to have a buildscript, unless you want to do everything the default way. In general if you want to install in a non-standard prefix or have dependencies installed in a non-standard prefix you will have to pass parameters to the configure script. If you put these in your buildscript you do not risk forgetting this when re-running the configure script. If both the sources, the folder collecting LXPanel and your destination folder are somewhere inside your home directory, your buildscript could look like this:

#!/bin/bash
#Edit these variables
LXDEBUILD_SRC_ROOTDIR="$HOME/random_stuff/src"
LXDEBUILD_LXDE_SRC_ROOTDIR="$LXDEBUILD_SRC_ROOTDIR/lxde"
LXDEBUILD_PREFIX="$HOME/opt/lxde"
#Environment variables
export PKG_CONFIG_PATH=$LXDEBUILD_PREFIX/lib/pkgconfig
export LD_LIBRARY_PATH=$LXDEBUILD_PREFIX/lib
export LD_RUN_PATH=$LXDEBUILD_PREFIX/lib
export CFLAGS="-I$LXDEBUILD_PREFIX/include"
export CPPFLAGS="-I$LXDEBUILD_PREFIX/include"
#Now execute the commands needed to build
./autogen.sh && ./configure --enable-man --prefix=$LXDEBUILD_PREFIX --oldincludedir=$LXDEBUILD_PREFIX/include --with-libdir=$LXDEBUILD_PREFIX/lib --sysconfdir=$LXDEBUILD_PREFIX/etc && make && make install

You may have noticed a comment "Edit these variables". In most cases those variables are indeed the only things you need to edit in order to adapt the buildscript to your situation. After you have saved your buildscript, be sure to make it executable

with

$ chmod +x

followed by the path to your buildscript (or just the name if your terminal window is in the same directory as the script).

Your first modification: a rename

Now it is time to give your plugin a name. You will have to think of three names:

  • A full name, this may contain any character but you may have to excape spaces or quotation marks.
  • A shortened name, this may only contain regular characters (A-Z, a-z, 0-9 and _) and may equal the full name if it doesn't contain any illegal characters.
  • The class name, this will be used in the source file. Coding conventions suggest that it starts with a capital letter.

The following script will automatically apply these names in all necessary files (again, don't forget to make it executable):

#!/bin/sh

[ $# -lt 3 ] && echo "please pass parameters as follows:
$0 shortname longname classname" && exit 1

sed -i "s/example-plug/$1/g" configure.ac
sed -i "s/example/$1/g" Makefile.am
cd po
sed -i "s/example/$1/g" POTFILES.in
cd ../src
sed -i "s/example/$1/g" Makefile.am
sed -i "s/example/$1/g" example.c
sed -i "s/Example plugin/$2/g" example.c
sed -i "s/Example/$3/g" example.c
mv example.c $1.c

echo "names have been changed"

Building the plugin with a handwritten makefile

First, you need a source file containing the plugin. I will assume you call it lxpanelexample.c. The following could be the contents of a barebone plugin source file:

/**
* file: example.c
* description: A completely barebone lxpanel plugin doing nothing
* author: 2noob2banoob
* based on example_plug by lxde-develloppers
* GPL v2.0
*/

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <glib/gi18n.h>

#include <string.h>
#include <lxpanel/plugin.h>

#define EVENT_MOUSE_BUTTON_PRESS_LEFT 1
#define EVENT_MOUSE_BUTTON_PRESS_MIDDLE 2
#define EVENT_MOUSE_BUTTON_PRESS_RIGHT 3

typedef struct {
Plugin * plugin;
GtkWidget *main;
GtkWidget *widget;
GtkTooltips *tip;
} Example;

gboolean button_press_event( GtkWidget *widget, GdkEventButton* event, Plugin* plugin)
{
if( event->button == EVENT_MOUSE_BUTTON_PRESS_LEFT )
{

}
else if( event->button == EVENT_MOUSE_BUTTON_PRESS_MIDDLE )
{

}
else if( event->button == EVENT_MOUSE_BUTTON_PRESS_RIGHT )
{
GtkMenu* popup = (GtkMenu*)lxpanel_get_panel_menu( plugin->panel, plugin, FALSE ); /* lxpanel_menu, can be reimplemented */
gtk_menu_popup( popup, NULL, NULL, NULL, NULL, event->button, event->time );
return TRUE;
}

RET2(TRUE);
}

static gint update_tooltip(Example *egz)
{
char *tooltip;
ENTER;

tooltip = g_strdup_printf("LXPanel Example plugin");
gtk_tooltips_set_tip(egz->tip, egz->main, tooltip, NULL);
g_free(tooltip);

RET(TRUE);
}

static int example_constructor(Plugin *p, char** fp)
{
Example *egz;

/* initialization */
egz = g_new0(Example, 1);
egz->plugin = p;
p->priv = egz;

p->pwid = gtk_event_box_new(); //you may want to change this to a hbox or vbox if you want to use multiple widgets
GTK_WIDGET_SET_FLAGS( p->pwid, GTK_NO_WINDOW );
gtk_container_set_border_width( GTK_CONTAINER(p->pwid), 2 );

egz->widget = gtk_label_new("Hello World");
gtk_container_add(GTK_CONTAINER(p->pwid), egz->widget);

egz->main = p->pwid;
egz->tip = gtk_tooltips_new();
update_tooltip(egz);

g_signal_connect (G_OBJECT (p->pwid), "button_press_event", G_CALLBACK (button_press_event), (gpointer) p);
}

static void applyConfig(Plugin* p)
{
Example *egz = (Example *) p->priv;

update_tooltip(egz);
}

static void config(Plugin *p, GtkWindow* parent) {
GtkWidget *dialog;
Example *egz = (Example *) p->priv;
dialog = create_generic_config_dlg(_(p->class->name),
GTK_WIDGET(parent),
(GSourceFunc) applyConfig, (gpointer) p,
//_("displayed_varname"), &egz->variable, datatype, //datatype can be CONF_TYPE_STR, CONF_TYPE_INT, CONF_TYPE_BOOL etc.
NULL);
gtk_window_present(GTK_WINDOW(dialog));
}

static void example_destructor(Plugin *p)
{
ENTER;
Example *egz = (Example *)p->priv;
g_free(egz);
}

static void save_config( Plugin* p, FILE* fp )
{

}

PluginClass lxpanelexample_plugin_class = {

PLUGINCLASS_VERSIONING,

type : "lxpanelexample",
name : N_("Barebone LXPanel plugin"),
version: "0.1",
description : N_("Example plugin: barebone doing nothing"),

constructor : example_constructor,
destructor  : example_destructor,
config  : config,
save  : save_config,
panel_configuration_changed : NULL
};

Next you need a makefile. The following makefile is probably a good one to start with:

#Edit these variables.
LXDEBUILD_PREFIX=${HOME}/opt/lxde
LXDEBUILD_PLUGIN_NAME=lxpanelexample
LXDEBUILD_SOURCEFILES=example.c
LXDEBUILD_VERSION_SCRIPT=example.ver

#These variables should probably not be edited, they are for convenience only.
LXDEBUILD_DATA_DIR=${LXDEBUILD_PREFIX}/share
LXDEBUILD_LIB_DIR=${LXDEBUILD_PREFIX}/lib
LXDEBUILD_LXPANEL_PLUGIN_DIR=${LXDEBUILD_LIB_DIR}/lxpanel/plugins
LXDEBUILD_DEPENDENCIES=lxpanel

#Environment variables, these should also not be edited.
export PKG_CONFIG_PATH=${LXDEBUILD_LIB_DIR}/pkgconfig
export LD_LIBRARY_PATH=${LXDEBUILD_LIB_DIR}
export LD_RUN_PATH=${LXDEBUILD_LIB_DIR}

#Phony target, invoked when make is run with no arguments.
#In this case it invokes the lxpanelplugin target without doing anything else.
all: lxpanelplugin

#Target to build your LXPanel plugin.
lxpanelplugin:
gcc -shared -fPIC -g -O2 \
-DPIC -DHAVE_CONFIG_H -DPACKAGE_DATA_DIR=\""${LXDEBUILD_DATA_DIR}"\" \
`pkg-config --libs --cflags ${LXDEBUILD_DEPENDENCIES}` \
-o ${LXDEBUILD_PLUGIN_NAME}.so \
-Wl,-soname -Wl,${LXDEBUILD_PLUGIN_NAME}.so -Wl,-version-script -Wl,${LXDEBUILD_VERSION_SCRIPT} \
${LXDEBUILD_SOURCEFILES}

#Target to install the built plugin.
install: lxpanelplugin
cp ${LXDEBUILD_PLUGIN_NAME}.so ${LXDEBUILD_LXPANEL_PLUGIN_DIR}

This may seem like an overload of variables, but in comparison with generated makefiles it is a relief. In the long run these variables may increase the maintainability of your code and make it easier to rename your plugin, install it to a different prefix or add another dependency.

You may have noticed the mention of a file called a "version script". As you may have guessed this file has to be present in your source folder as well. Since the plugin compiles to a library, some parts will have to be accessible to an application which loads it (typically lxpanel). The version script takes care of telling the compiler which parts should be accessible and which parts are for internal use within the library only. The version script typically looks like this:

{ global:
button_press_event;
lxpanelexample_plugin_class;
scroll_event;
local: *; };

Of course, lxpanelexample_plugin_class should be renamed if you rename the plugin.

Now that the source file, the makefile and the version script are all in place, it is time to build:

$ make && make install


If all went well you should now be able to add the plugin to your LXPanel. It should consist of a label saying Hello World.

Structure of the source file

Every LXPanel plugin source file contains at least the following elements:

  1. Includes.
  2. A struct with typedef defining what data your plugin needs to keep track of in order to function properly. The struct contains both variables which are needed for every LXPanel plugin and variables which are specific to your plugin. The variables required by every plugin are:
    • Plugin * plugin: A pointer to a struct which actually makes your plugin a LXPanel plugin.
    • GtkWidget *main: The main GTK widget, this is a container containing the other widgets.
    • GtkTooltips *tip: The tooltip users get when they hover over your plugin.
  3. GtkWidget *widget is not required, although it is recommended to include your functional widget(s) as variable(s) in your struct. You can name this variable whatever you want. If you are using multiple widgets you can include them in separate variables or in an array, depending on personal preference.
  4. The function gboolean button_press_event( GtkWidget *widget, GdkEventButton* event, Plugin* plugin). This function is called when your plugin is clicked with any mouse button.
  5. The constructor: static int example_constructor(Plugin *p, char** fp).
  6. The destructor: static void example_destructor(Plugin *p).
  7. The function static void applyConfig(Plugin* p), which is called whenever a new configuration is to be applied. This is usually when clicking the apply or ok button in the plugin's preferences window.
  8. The function static void config(Plugin *p, GtkWindow* parent). This function spawns the preferences window.
  9. The function static void save_config( Plugin* p, FILE* fp ), which is called whenever a configuration is to be saved to a file. This is usually when clicking the apply or ok button in the plugin's preferences window.
  10. PluginClass example_plugin_class, which is an instantiation of the PluginClass struct and required to actually have LXPanel recognize your plugin.

What to modify?

The first thing you want to re-implement is the constructor. You want to replace the "Hello World" label with a widget which does what you want your plugin to do. Or, of course with multiple widgets, in which case you will have to turn the main widget into a container of multiple widgets (typically a hbox or vbox). If you are adding events you will have to make callback functions and connect them to the signals indicating your events. Once you've got a plugin you think should have it's basic functionality, it's time to compile and test. To do so, open the folder containing your plugin in a terminal, run

your buildscript in case you use autotools or type

$ make && make install

in case you use a handwritten makefile, and if succesful add the plugin to your LXPanel.

Once it's working you may want to add the possibility to configure the plugin, this is done by modifying the functions with config in their name. You should now be able to figure out by yourself how to do this.