Basic C Plugin How-To

Introduction

This How-To document is designed to provide a basic overview of writing a plugin for Pidgin, Finch, and/or libpurple in our native language, C. We'll write a basic "Hello, World!" plugin. Let's dig in, shall we?

As we've already stated, C plugins are native plugins. They have complete access to all of the API provided by Pidgin, Finch, and libpurple. This means that a C plugin can do basically anything it wants to do. All our protocol plugins and our loader plugins (Mono, Perl, and Tcl) are written in C.

Getting Started

To develop a plugin, you need to have an installation of libpurple that includes development headers, at minimum. To write a plugin that includes features requiring Pidgin or Finch functionality, you will need to also have Pidgin or Finch installed, complete with development headers.

It will actually be easier for the first-time plugin author to install the development dependencies and then have a copy of the corresponding Pidgin source tarball. For example, if you're running 2.0.2 on a Debian Sid (unstable) or testing system, you would do the following:

# apt-get build-dep pidgin

Similarly, on a Fedora system, you would do this:

# yum install pidgin-devel

People using Windows should read the Building Windows Pidgin page on this wiki to set up the necessary build environment.

The command above for Debian systems will install only the build dependencies of Pidgin, Finch, and libpurple. The command shown for Fedora will install all of those, as well as the development headers for libpurple, Finch, and Pidgin.

Once you have installed these dependencies, download a source tarball. You will want to use the source tarball that matches the version of Pidgin you are running, as it's generally a good idea to compile against the same version you run your plugins with. You can, however, safely build a plugin against Pidgin 2.0.0 and run it with any Pidgin version 2.x.y.

For the purposes of this How-To, we will assume the following:

  • You understand the concept of ~ being your home directory
  • You are working within the directory ~/development.
  • You are using Pidgin 2.0.2.
  • You have at least a basic grasp of the concepts and syntax of the C language, including pointers to both functions and data. Understanding the concept of a pointer to a pointer will be useful later in this How-To series.

If you are using a different version of Pidgin, simply replace references to 2.0.2 with whichever version you are using. If you wish to use a path other than ~/development (for example, Windows users correctly following the instructions will have ~/win32-dev and their Pidgin tarball will be extracted in ~ to match), simply treat our references to ~/development as a reference to the directory in which you extract the tarball.

Now, extract the tarball:

~/development $ tar -jxvf pidgin-2.0.2.tar.bz2

Next, change to the pidgin-2.0.2 directory. If you are using Windows, run make -f Makefile.mingw to build Pidgin. If you are using another platform, run ./configure and then make after the configuration is complete. You may wish to customize the arguments to the configure script, which is beyond the scope of this document. If you are using Windows, DO NOT under any circumstances use the configure script! Any executables or .dll files derived from a Pidgin tree that has been configured via the configure script WILL NOT WORK outside of Cygwin, if they will build at all.

Now that everything is compiled, we can get to the real work of developing your first plugin, but first a point to be well aware of. ALL C plugins must define PURPLE_PLUGINS by using the #define preprocessor directive. This definition must occur before including any libpurple, Pidgin, or Finch header files. Failure to have #define PURPLE_PLUGINS in your source file leads to very strange errors that are difficult to diagnose. Just don't forget to do it!

Hello, World!

Every programming language's tutorial has its own Hello, World! first application. Why should our plugin tutorial be any different? Let's dig in. First, go to ~/development/pidgin-2.0.2/libpurple/plugins and create the file helloworld.c with your favorite editor. The contents of the file will be this:

#define PURPLE_PLUGINS

#include <glib.h>

#include "notify.h"
#include "plugin.h"
#include "version.h"

static gboolean
plugin_load(PurplePlugin *plugin) {
    purple_notify_message(plugin, PURPLE_NOTIFY_MSG_INFO, "Hello World!",
                        "This is the Hello World! plugin :)", NULL, NULL, NULL);

    return TRUE;
}

static PurplePluginInfo info = {
    PURPLE_PLUGIN_MAGIC,
    PURPLE_MAJOR_VERSION,
    PURPLE_MINOR_VERSION,
    PURPLE_PLUGIN_STANDARD,
    NULL,
    0,
    NULL,
    PURPLE_PRIORITY_DEFAULT,

    "core-hello_world",
    "Hello World!",
    "1.1",

    "Hello World Plugin",          
    "Hello World Plugin",          
    "My Name <email@helloworld.tld>",                          
    "http://helloworld.tld",     
    
    plugin_load,                   
    NULL,                          
    NULL,                          
                                   
    NULL,                          
    NULL,                          
    NULL,                        
    NULL,                   
    NULL,                          
    NULL,                          
    NULL,                          
    NULL                           
};                               
    
static void                        
init_plugin(PurplePlugin *plugin)
{                                  
}

PURPLE_INIT_PLUGIN(hello_world, init_plugin, info)

So, what does all this mean? Well, let's go through this with a line by line analysis. The #define PURPLE_PLUGINS is required, as we mentioned above.

Next, we include glib.h, the header for GLib. We do this mainly for the gboolean typedef and the other GLib wrappers for the standard C types. After this, we include notify.h, plugin.h, and version.h. The plugin.h header includes definitions that all plugins need, such as the type PurplePluginInfo and the macros PURPLE_PLUGIN_MAGIC and PURPLE_INIT_PLUGIN().

The version.h header defines PURPLE_MAJOR_VERSION and PURPLE_MINOR_VERSION. There's not much that you need to know about anything in version.h, except that they are required and will prevent your plugin from crashing Pidgin if it's probed by an older or newer Pidgin, such as 1.5.1 or 3.0.0 (which of course does not yet exist at the time of this writing), where things are different and your plugin doesn't know about the differences.

The notify.h header defines a bunch of notification functions. For the scope of this part of the tutorial, it's not important to know anything about the contents of the file. We'll use it for one function call to prove that our plugin works. Later sections of the tutorial will come back to the notify API and flesh it out a bit more for readers.

Moving on, the plugin_load function is defined. This is not a required function to implement, but if you choose to implement it, libpurple will call the function when the plugin is loaded. You can use it to initialize any global variables in your source file, to connect to signals, and so on. Here, we'll use it just to display a message by calling a function from notify.h.

Next in our source file we have the declaration and initialization of a global variable. We called this variable info, and it is of the type PurplePluginInfo. Every plugin is required to declare and initialize a variable of this type in order to tell libpurple about the plugin. This next code snippet is the exact same struct declaration and initialization from the snippet above, but with some comments included to explain the function of each member of the struct.

static PurplePluginInfo info = {
    PURPLE_PLUGIN_MAGIC,    /* Plugin magic, this must always be
                               PURPLE_PLUGIN_MAGIC.*/

    PURPLE_MAJOR_VERSION,   /* This is also defined in libpurple.  It helps
                               libpurple's plugin system determine which version
                               of libpurple this plugin was compiled for, and
                               whether loading it will cause problems. */

    PURPLE_MINOR_VERSION,   /* See previous */

    PURPLE_PLUGIN_STANDARD, /* PurplePluginType: There are 4 different values for
                               this field.  The first is PURPLE_PLUGIN_UNKNOWN,
                               which should not be used.  The second is
                               PURPLE_PLUGIN_STANDARD; this is the value most
                               plugins will use. Next, we have PURPLE_PLUGIN_LOADER;
                               this is the type you want to use if your plugin will
                               make it possible to load non-native plugins.  For
                               example, the Perl and Tcl loader plugins are of this
                               type.  Last, we have PURPLE_PLUGIN_PROTOCOL.  If your
                               plugin is going to allow the user to connect to
                               another network, this is the type you'd want to use. */

    NULL,                   /* This field is the UI requirement.  If you're writing
                               a core plugin, this must be NULL and the plugin must
                               not contain any UI code.  If you're writing a Pidgin
                               plugin, you need to use PIDGIN_PLUGIN_TYPE.  If you
                               are writing a Finch plugin, you would use
                               FINCH_PLUGIN_TYPE.*/

    0,                      /* This field is for plugin flags.  Currently, the only
                               flag available to plugins is invisible
                               (PURPLE_PLUGIN_FLAG_INVISIBLE). It causes the plugin
                               NOT to appear in the list of plugins. */

    NULL,                   /* This is a GList of plugin dependencies.  In other words,
                               it's a GList of plugin id's that your plugin depends on.
                               Set this value to NULL no matter what.  If your plugin
                               has dependencies, set them at run-time in the
                               plugin_init function. */

    PURPLE_PRIORITY_DEFAULT,/* This is the priority libpurple will give your plugin.
                               There are three possible values for this field,
                               PURPLE_PRIORITY_DEFAULT, PURPLE_PRIORITY_HIGHEST, and
                               PURPLE_PRIORITY_LOWEST. */

    "core-hello_world",     /* This is your plugin's id.  There is a whole page dedicated
                               to this in the Related Pages section of the API docs. */

    "Hello World!",         /* This is your plugin's name.  This is what will be
                               displayed for your plugin in the UI. */

    "1.1",                  /* This is the version of your plugin. */

    "Hello World Plugin",   /* This is the summary of your plugin.  It should be a short
                               blurb.  The UI determines where, if at all, to display
                               this. */

    "Hello World Plugin",   /* This is the description of your plugin. It can be as long
                               and as descriptive as you like.  And like the summary,
                               it's up to the UI where, if at all, to display this (and
                               how much to display). */

    "My Name <email@helloworld.tld>", /* This is where you can put your name and e-mail
                                         address. */

    "http://helloworld.tld",/* This is the website for the plugin.  This tells users
                               where to find new versions, report bugs, etc. */

    plugin_load,            /* This is a pointer to a function for libpurple to call when
                               it is loading the plugin.  It should be of the type:

                                  gboolean plugin_load(PurplePlugin *plugin)

                               Returning FALSE will stop the loading of the plugin.
                               Anything else would evaluate as TRUE and the plugin will
                               continue to load. */

    NULL,                   /* Same as above except it is called when libpurple tries to
                               unload your plugin.  It should be of the type:

                                  gboolean plugin_unload(PurplePlugin *plugin)

                               Returning TRUE will tell libpurple to continue unloading
                               while FALSE will stop the unloading of your plugin. */

    NULL,                   /* Similar to the two above members, except this is called
                               when libpurple tries to destory the plugin.  This is
                               generally only called when for some reason or another the
                               plugin fails to probe correctly.  It should be of the type:

                                  void plugin_destroy(PurplePlugin *plugin) */

    NULL,                   /* This is a pointer to a UI-specific struct.  For a Pidgin
                               plugin it will be a pointer to a PidginPluginUiInfo
                               struct, for example. */

    NULL,                   /* This is a pointer to either a PurplePluginLoaderInfo
                               struct or a PurplePluginProtocolInfo struct, and is
                               beyond the scope of this document. */

    NULL,                   /* This is a pointer to a PurplePluginUiInfo struct.  It is
                               a core/ui split way for core plugins to have a UI
                               configuration frame.  You can find an example of this
                               code in libpurple/plugins/pluginpref_example.c */

    NULL,                   /* This is a function pointer where you can define "plugin
                               actions".  The UI controls how they're displayed.  It
                               should be of the type:

                                  GList *function_name(PurplePlugin *plugin,
                                                       gpointer context)

                               It must return a GList of PurplePluginActions. */

    NULL,                   /* This is a pointer reserved for future use.  We set it to
                               NULL to indicate we don't need it. */

    NULL,                   /* This is a pointer reserved for future use.  We set it to
                               NULL to indicate we don't need it. */

    NULL,                   /* This is a pointer reserved for future use.  We set it to
                               NULL to indicate we don't need it. */

    NULL                    /* This is a pointer reserved for future use.  We set it to
                               NULL to indicate we don't need it. */
};

Finally in our source file, we have the init_plugin function and the call to the macro PURPLE_INIT_PLUGIN. The init_plugin function is called whenever libpurple probes the plugin. Most plugins will add their prefs to the prefs tree here (more on prefs in another section of the tutorials), as well as do the necessary initialization for translation support (more on that in other documentation).

Every plugin MUST call the PURPLE_INIT_PLUGIN macro. The call to this macro tells libpurple some very basic things about your plugin, like what name to use if someone compiles your plugin statically into a binary, which function is the init_plugin function (because you can call it anything!), and the name of the PurplePluginInfo structure (which also can be called by any name you want). Libpurple reads this information when probing the plugin. If this call is missing, the plugin will not successfully probe and cannot ever load.

Compile, Install, and Load the Hello, World! Plugin

Now that we have a complete plugin source file, we need to compile it. From ~/development/pidgin-2.0.2/libpurple/plugins, run make helloworld.so (if using Windows, make -f Makefile.mingw helloworld.dll). If you copied and pasted the code correctly, this should compile without incident. You can move the resulting .so file to ~/.purple/plugins (move the resulting .dll file to %APPDATA%\.purple\plugins on Windows). Now when you open the Plugins dialog in Pidgin or Finch, the plugin should show up. When you load it you should see a message pop up.

Having Trouble Compiling?

Note that this section has been added after the initial completion of this document and as such may break continuity with the preceeding sections and the closing section. Apologies if this makes the document harder to understand.

If you're having trouble compiling and the error messages being spit out reference G_GNUC_NULL_TERMINATED, you are compiling against Pidgin and libpurple version 2.1.0 or newer. In that case, add the following code to your helloworld.c file after the #include <glib.h> directive:

#ifndef G_GNUC_NULL_TERMINATED
#  if __GNUC__ >= 4
#    define G_GNUC_NULL_TERMINATED __attribute__((__sentinel__))
#  else
#    define G_GNUC_NULL_TERMINATED
#  endif /* __GNUC__ >= 4 */
#endif /* G_GNUC_NULL_TERMINATED */

This section of preprocessor code is only needed if you're building against version 2.1.0 or newer. There were some additions to some function prototypes in libpurple to add GCC 4.x's NULL sentinel attribute. This will cause issues on many platforms, including Windows, if the version of GLib in use is rather old (2.6.x or older). There is an additional wrinkle to the problem in that only GCC 4.x have this function attribute, so the workaround is a more complex set of preprocessor directives than we'd like.

In libpurple we have fixed this internally, and this fix is technically available to plugins built within the Pidgin source tree. It is not used here, however, because no third-party plugins are able to use this internal fix, and the point of this series of How-To documents is to help budding third-party plugin developers. These developers will need to include the fix themselves.

If you run into this issue in the future installments of this How-To document series, this same snippet of preprocessor code can be added to the later example plugins in the same place. It should yield the same effect there as it did here.

Going Beyond Hello, World!

Consider C Plugins 101 complete. We have more to get to in this tutorial series. See the other documents listed on the C Plugin How-To.

Last modified 10 years ago Last modified on 08/05/07 22:32:49
All information, including names and email addresses, entered onto this website or sent to mailing lists affiliated with this website will be public. Do not post confidential information, especially passwords!