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 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 element: ... ... ... Note that the lifetime of your plugin is dependent upon whether you put the tag inside a page or in the section. If it is within a , it will be in memory only while the page is being displayed. If you put it in the 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 element within the section) rather than the Page OnLoad event (or a element within a ) 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 element within in a 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.