The eboxy Extension Guide

Paul Eggleton

   Friday, 23 January 2004

   Abstract

   This manual describes how to extend the eboxy software
   package.
     _________________________________________________________

   Table of Contents

   I. Generic plugins

        1. Introduction

              Purpose
              Plugin Usage

        2. Technical

              Requirements

                    int ebplugin_init(void)
                    int ebplugin_message(int msgcode, void
                            *msgdata1, void *msgdata2)

                    void ebplugin_deinit(void)

              Plugin API
              Guidelines & Hints
              Converting Old Plugins

        3. Dynamic GUI system

              Introduction
              Adding widgets
              Adding pages
              Tidying up
              Enumerating objects

                    Enumerating object properties, methods and
                            events

                    Enumerating widgets
                    Enumerating pages

        4. Examples

              Available plugins

                    Simplemusic
                    Filebrowser
                    TestBench
                    Others

              Plugin skeleton code

   II. Interface plugins

        5. Introduction

              Purpose

Generic plugins

   Table of Contents

   1. Introduction

        Purpose
        Plugin Usage

   2. Technical

        Requirements

              int ebplugin_init(void)
              int ebplugin_message(int msgcode, void *msgdata1,
                      void *msgdata2)

              void ebplugin_deinit(void)

        Plugin API
        Guidelines & Hints
        Converting Old Plugins

   3. Dynamic GUI system

        Introduction
        Adding widgets
        Adding pages
        Tidying up
        Enumerating objects

              Enumerating object properties, methods and events
              Enumerating widgets
              Enumerating pages

   4. Examples

        Available plugins

              Simplemusic
              Filebrowser
              TestBench
              Others

        Plugin skeleton code

Chapter 1. Introduction

   Table of Contents

   Purpose
   Plugin Usage

Purpose

   The eboxy generic plugin framework was designed so that
   optional features can be added to eboxy, without adding
   features to the core eboxy codebase that won't be needed by
   everyone, and to make it easier for other developers to add
   features that they want without having to understand and
   modify the eboxy core itself.

   Plugins are extremely flexible. You can access and modify
   widgets and pages, create script-accessible objects that call
   your own code, and even create widgets and pages on the fly.

   The plugin framework is in "beta" stage. This means that
   barring any really serious problems it will likely not be
   changed in future versions, although new functions may be
   added.

Plugin Usage

   Plugins are loaded by either using the loadplugin script
   command or alternatively by putting a <plugin> element into
   the XML file. The latter is the recommended method, because it
   is simpler and allows you to attach event scripts to objects
   that the plugin registers. Here's a simple example of loading
   a plugin using a <plugin> element:
...
<page ...>
  <plugin module="myplugin"/>
  ...
</page>
...

   Note that the lifetime of your plugin is dependent upon
   whether you put the <plugin> tag inside a page or in the
   <system> section. If it is within a <page>, it will be in
   memory only while the page is being displayed. If you put it
   in the <system> section (or loaded by using the loadplugin
   script command) it will be loaded for as long as eboxy is
   running, unless the plugin requests to be unloaded.

   eboxy will look for plugins in LIBDIR/eboxy only (where LIBDIR
   is picked up at compile time, usually /usr/local/lib). Also,
   the .so extension is always omitted. So in the above example,
   when the page is loaded eboxy will attempt to load the plugin
   file /usr/local/lib/eboxy/myplugin.so into memory (actually,
   some loading and pre-initialisation is done when the XML file
   is read in, and the plugin is fully initialised when the page
   is loaded).

Chapter 2. Technical

   Table of Contents

   Requirements

        int ebplugin_init(void)
        int ebplugin_message(int msgcode, void *msgdata1, void
                *msgdata2)

        void ebplugin_deinit(void)

   Plugin API
   Guidelines & Hints
   Converting Old Plugins

Requirements

   eboxy plugins are shared libraries that are linked to
   libeboxyplugin.a and implement all of the following C style
   functions:

int ebplugin_init(void)

   Put code in this function to initialise your plugin. Other
   than your own initialisation code (if any) you should call
   setPluginInfo() somewhere in here with the full name of your
   plugin and a version string. When you're done, return an exit
   status: 0 for success, or any other value to indicate failure
   (which will result in the plugin not being loaded).

int ebplugin_message(int msgcode, void *msgdata1, void *msgdata2)

   This function will be called when certain other events occur.
   Currently this exists just to let your plugin know when the
   page or file is changed.

   This function may be extended for other events in future. For
   now, msgcode can be one of the following events (see
   pluginconstants.h for constants):
   Message constant Description
   PLMSG_PLUGINSTART issued when your plugin has been loaded
   PLMSG_BEFOREPAGECHANGE issued before the current page is to be
   changed
   PLMSG_AFTERPAGECHANGE issued after a new page has been
   displayed
   PLMSG_BEFOREFILELOAD issued before a new XML file is to be
   loaded
   PLMSG_AFTERFILELOAD issued after a new XML file has been
   loaded

   The return value is currently unused, but you should return 0
   for compatibility with future usage. Also, any msgcode values
   your plugin does not understand (ie, that do not match any of
   the current PLMSG_ constants, or at least none of them that
   you're interested in) should be ignored silently by your code.

void ebplugin_deinit(void)

   In this function, put any code you need to clean up after your
   plugin. eboxy will call this before it unloads the plugin. You
   must not call any plugin API functions from this procedure.

   Plugins must define all three of these functions, and must be
   linked to libeboxyplugin.a (the eboxy plugin client library),
   or they will not be loaded. Beyond these, you may define any
   other functions and use any libraries you need (however,
   please read the guidelines section).

   The plugin system has an built-in version checking mechanism.
   eboxy can read the version of libeboxyplugin.a that your
   plugin was linked with, and so can determine if your plugin is
   too new or too old to work with it. Future versions of eboxy
   will be designed with as much backwards compatibility as
   possible.

Plugin API

   The plugin API documentation has been generated in HTML using
   Doxygen, and you can view it here (in
   eboxy/docs/en/pluginapi).

Guidelines & Hints

     * Don't take too much time responding to messages in
       ebplugin_message - these aren't yet threaded by eboxy so
       the GUI is effectively "frozen" while code in your plugin
       runs. Create your own threads if appropriate.
     * Be careful what values you pass to eboxy functions - there
       is very little checking code at the moment. Particularly,
       you should never pass NULL in place of a char * unless it
       is explicitly allowed.
     * If you request objects that don't exist, try to set
       properties that are read-only, etc. then an error will be
       printed on stderr. In this case, API functions that return
       a char * will return NULL, and functions that return an
       integer result code will return a non-zero value.
     * If your plugin was loaded from the System OnLoad event (or
       a <plugin> element within the <system> section) rather
       than the Page OnLoad event (or a <plugin> element within a
       <page>) then the current page has not been created yet
       when your plugin receives the PLMSG_PLUGINSTART message,
       so you should not try to look for any widgets.
     * Do not call any plugin API functions in ebplugin_deinit().
       Doing so will likely result in deadlock. Similarly, you
       must not do anything that might result in a plugin message
       being sent to your plugin during ebplugin_init() (eg.
       changing the page) - this will result in deadlock as well.
       Note that it is not necessary for you to unregister
       objects, event handlers etc. at the time your plugin is
       deinitialised - this happens automatically. However,
       widgets and pages your plugins create (if any) should be
       deleted if you don't wish them to stay in existence until
       eboxy exits.
     * Bugs in external libraries that you use can cause odd
       things to happen in eboxy. Because plugins execute within
       eboxy itself, there is nothing eboxy can do to prevent bad
       code in a plugin (or libraries a plugin uses) from writing
       garbage all over its memory. If you are having trouble
       with a plugin, it may be worth checking if there is a bug
       in any libraries you are using in the plugin. Of course,
       it could just as likely be a bug in eboxy. I recommend
       Valgrind, an excellent memory debugger that can debug an
       executable without recompilation. Note however that as
       Valgrind does not support the fancy instructions available
       on newer processors (P4, Athlon) you will need to make
       sure you compile eboxy and SDL without optimising for a
       newer architecture if you wish to use Valgrind (use
       -march=i386 or i486).
     * Unless you want it to remain loaded until eboxy exits
       (which may be fine, depending on what you want to do) you
       are responsible for unloading your plugin, or allowing the
       user of your plugin to ask it to unload (through an object
       you have registered). When you're ready to unload, call
       requestUnload(). Note that unloading may not happen
       immediately, since it is scheduled to avoid deadlock and
       other nasty situations. However, you should avoid doing
       anything after calling requestUnload() (that is, having
       any statements after it in the same function). Of course,
       as previously mentioned, if the plugin is loaded from a
       <plugin> element within in a <page> in the XML file it
       will be unloaded automatically when the page stops being
       displayed.
     * Document your plugin properly, so that users understand
       how to use it. At least provide snippets of XML or script
       to show how to load and use it, or (even better) provide
       an example XML skin that demonstrates the plugin in
       action.
     * Do not under any circumstances open up a service on an
       untrusted network (eg. the internet) that allows you to
       pass script code directly to the runScript() API function
       to be executed, unless you are willing to accept the
       possibility your system's security may be compromised by
       doing so. eboxy's security has not been fully tested, and
       the trusted flag of runScript() is only designed to give a
       small measure of protection. You have been warned! (If you
       wish to test eboxy for security, please do so and report
       any bugs you find).
     * Try to make the function names that will be exposed to
       eboxy unique (those functions which will be passed to the
       *DL functions, eg. property get/set functions). C (well,
       to be precise, the runtime linker) does not allow two
       functions of the same name in the same program, so nasty
       things will happen if there is a clash with another
       plugin. The easiest way to avoid this is by using a unique
       prefix for all of the functions that will be exposed. For
       non-exposed functions, you can use any name you like as
       normal.
     * New in 0.4: Method and property getter functions should
       always return either NULL or a string that can be freed.
       Property setter functions must return 0 on success or
       non-zero on failure (if returning a non-zero value, do not
       internally set the property). See Converting Old Plugins
       for details.

Converting Old Plugins

   In version 0.4.0, eboxy's plugin API has changed in a way that
   breaks backwards compatibility. This was necessary due to a
   design flaw in the way strings were returned in 0.3.x
   (property getter and method functions returned const char *
   pointers that could never be freed, resulting in memory
   leaks). The solution was to change these functions to return
   char *, and make the rule that the caller always frees the
   returned string if the pointer isn't null. At the same time
   the opportunity was taken to make property setter functions
   return an error code, so that eboxy can determine if a plugin
   object property was set correctly or not.

   To bring your own plugins up to date with the changes, you
   need to do the following:
     * All property getter and method functions must now return
       char * instead of const char *. The convention is now to
       return NULL if you don't have anything to return. Any
       returned string should be allocated with malloc() or
       strdup() - do not under any circumstances return a string
       literal.
     * All property setter functions must return an int. Return 0
       to indicate the value was valid, non-zero to indicate an
       invalid value (do not actually set the value internally in
       this case).
     * If you are calling getPropertyValue() or callMethod()
       anywhere, make sure you check if the returned value is not
       NULL, and if so, call free() on it. In cases where you
       don't care about the return value of callMethod(), use
       callMethodNoReturn() instead.

Chapter 3. Dynamic GUI system

   Table of Contents

   Introduction
   Adding widgets
   Adding pages
   Tidying up
   Enumerating objects

        Enumerating object properties, methods and events
        Enumerating widgets
        Enumerating pages

Introduction

   eboxy plugins can query, modify and even create the GUI at
   runtime inside eboxy. This allows complex manipulation of the
   GUI at runtime - for example, you could create the GUI from an
   external file, from a stream over a network connection, or
   create pseudo-widgets by combining other widgets together.

Adding widgets

   To create a new widget at runtime, you can either create it
   from scratch, create one based on a template, or clone an
   existing widget. The createWidget() or cloneWidget() API
   functions are used to do this. When a new widget is created or
   cloned, it goes into a constructed widget "holding area". Set
   any properties you wish to set before using it, and then add
   the widget to a page, using the AddWidget() method of the page
   (supplying the name of the widget to add). You can add it to
   the current page or to any other page, including pages you
   create at runtime (more on this below).

Adding pages

   You can also create new pages at runtime. Use the createPage()
   API function to do this. To actually display the page, use the
   changePage() API function to change to it.

Tidying up

   The deletePage() and deleteWidget() API functions should be
   used to remove pages and widgets from memory when you no
   longer need them. Note that deletePage and deleteWidget may
   only be used on pages/widgets that you dynamically create (the
   same applies to the RemoveWidget method of page objects).
   Also, using deletePage() on a page that contains widgets you
   have created does not absolve you of the responsibility of
   deleting the created widgets - these remain in the holding
   area, and you must run deleteWidget() on each of them to clean
   them up - so you should keep track of them. You should not use
   deleteWidget() to delete a widget that is still on a page -
   use the page's RemoveWidget() method to remove the widget
   first, then delete it.

   Note that unlike custom objects, widgets are not owned by
   plugins - they will remain if your plugin is unloaded before
   eboxy quits. If you do not want them to be persistent, you
   should explicitly remove them. However, at this present time,
   if you change to a different XML file, the created pages will
   be cleared out - this is considered to be a bug and will be
   fixed in subsequent versions of eboxy.

Enumerating objects

   eboxy provides ways to enumerate various elements of the GUI
   at runtime. Enumeration is provided in the form of a "get
   count, get item at index" interface.

Enumerating object properties, methods and events

   To enumerate properties on an object, first retrieve the value
   of the propertycount property, and then call the getproperty
   method in a loop, passing the index (from 0 to count-1) on
   each iteration. In this way you can retrieve the name of each
   property on the object. You can do likewise for methods and
   events by using methodcount/getmethod and eventcount/getevent
   respectively. These can be used on most types of eboxy object
   accessible through the plugin API. There is a demonstration of
   doing this in the testbench plugin.

Enumerating widgets

   Page objects have a property widgetcount and a method
   getwidget which can be used to enumerate the widgets on a
   particular page in the manner described previously.

Enumerating pages

   To enumerate available pages, use the pagecount property and
   getpage method of the system object.

Chapter 4. Examples

   Table of Contents

   Available plugins

        Simplemusic
        Filebrowser
        TestBench
        Others

   Plugin skeleton code

Available plugins

Simplemusic

   Simplemusic, as the name suggests, is a very simple plugin
   that plays music files. It exposes a simplemusic object that
   you can use in scripts, and uses the SDL_mixer library to do
   the playing (thus it supports MP3, OGG, MOD etc.). It also
   demonstrates how easy it is to make a plugin. See
   simplemusic.txt and simplemusic.c in plugins/simplemusic for
   more information.

Filebrowser

   Filebrowser is another simple plugin that operates in
   conjunction with a listbox widget in order to display a list
   of files for the user to select from. Filebrowser automates
   the listbox so that selecting a directory browses into that
   directory, and selecting a file generates an event. See
   filebrowser.txt and filebrowser.c in plugins/filebrowser for
   more information.

TestBench

   The testbench plugin is a good example of creating widgets at
   runtime, and also serves to explore the properties available
   for various different types of widget. This plugin is
   available for download separately from the eboxy homepage.

Others

   Other plugins are available from the eboxy website. The latest
   in-development versions are available from the eboxy CVS
   repository, in the eboxy-plugins CVS module.

Plugin skeleton code

   Here is a skeleton plugin, containing the bare minimum of code
   (fill in where appropriate):
#include "eboxyplugin.h"
#include "pluginconstants.h"

int ebplugin_init(void) {
  /* Change these two strings to whatever you want for your plugin (nam
e and
   * version)
   */
  setPluginInfo("my plugin", "1.0");

  /* Do anything else here that you need to do to initialise your plugi
n
   * Note: if your own initialisation stuff fails here, you should retu
rn
   * a non-zero value.
   */

  return 0;     /* returning 0 means plugin has successfully initialise
d */
}

int ebplugin_message(int msgcode, void *msgdata) {
  /* If your plugin manages objects on the current page, you should che
ck
   * here for when the current page/file is changed. This will also be
called
   * under other circumstances (see pluginconstants.h for values of msg
code)
   */
  return 0;     /* return 0 here for future compatibility */
}

void ebplugin_deinit(void) {
  /* Any cleanup code you need for your plugin goes here */
}

Interface plugins

   Table of Contents

   5. Introduction

        Purpose

Chapter 5. Introduction

   Table of Contents

   Purpose

Purpose

   As of version 0.4, eboxy's input/output layer is separate from
   the core program itself. All of the specific interface
   operations are contained in an "interface plugin" which is
   chosen at start time. This means that almost any input/output
   device combination could be used with eboxy, and you don't
   have to recompile the eboxy core to change between them.

   Developing interface plugins requires a good grounding in C++
   - in particular you should understand how multiple inheritance
   works. You will also need to acquire a fairly good
   understanding of how eboxy works internally - its inheritance
   hierarchy, and what each class and function does.

   Interface plugins are dynamically linked libraries (.so files)
   which contain inherited versions of each widget that the
   plugin supports. They also contain initialisation and
   uninitialisation procedures and the main event loop.

   For now it is suggested that you take a look at the eboxy
   source code if you are interested in developing an interface
   plugin. Documentation for this will be improved in future
   versions.
