Karma Home Page
Next: Graphics without Widgets Up: Karma Programming Manual Previous: Image Display Tool

# Dynamic Extensions

Dynamic extensions are a mechanism whereby you can have your own code automatically loaded and executed by parts of the Karma library. This requires that your operating system supports building shared objects (often called shared libraries) and that loading of shared objects is supported by the dlopen() function.

# Rationale

An alternative to dynamic extensions is to modify the source code for a Karma module or some part of the library. Modifying the code is appropriate for a generic change (if you submit the code I can include it in the mainstream Karma release), but if it's something specific to your own work environment it might not be appropriate to include it in the mainstream Karma. Maintaining your own private versions of pieces of Karma can quickly become a maintenance nightmare, since newer versions of Karma will contain bugfixes and improvements but don't include your private changes. Simply replacing the Karma version of some module or part of the library with your own version may not work, since other parts of Karma may depend on new features which your code does not have. Merging in your changes with each new source release of Karma involves work, and does not solve the problem if you want to install a new beta (binary-only) release of Karma, since you won't have access to the source code changes until the next source release.

Other changes you may wish to make to Karma may require linking with extra libraries. This is something that is unlikely to be included in the mainstream Karma release, since it introduces dependencies on libraries not commonly available.

To solve these problems, dynamic extensions are supported. These provide a way to have your own changes integrated into the library or a module, maintaining binary compatibility between different releases of Karma. The aim is to allow you to include your own code without having to worry about maintenance when you install a newer version of Karma.

# Architecture

Karma defines many ``services'' which can be extended in defined ways. A service may be a module (i.e. the entire application), a widget or some other part of the library. Each service will call certain known functions within the relevant extension at defined points in the execution of the service. There are three main classes of services:

- packages
- widgets
- modules

## Package Services

The service name for a package is the name of the package itself. For example, the foreign package defines the `foreign` service. Each package will call known extension functions specific to that package. You will need to read the package-specific documentation to determine which extension functions are defined for that package.

## Widget Services

The service name for a widget is the class name of the widget. For example, the Filepopup widget (section 13.17.2) defines the service `Filepopup`. Widgets may call the following known functions:

- ClassInitialise this is called only once when the widget class is initialised
- WidgetInitialise this is called as the last stage of initialisation of a widget instance
- WidgetRealise this is called as the last stage of realisation of a widget instance
- WidgetDestroy this is called as the last stage of destruction of a widget instance

Each of these functions has the same interface definition as the generic Xt class member functions. Note that not all widgets currently call these extension functions. The widgets will be changed as required (most widgets will probably never have extensions written for them by anybody). See the chapter on widgets (section 13) where each widget documents any extension functions it calls.

## Module Services

The service name for a module is the name of the module itself. Modules may call the following known functions:

- Xt_AppInitialise this is called only once when the Xt application context is created (this only applies to Xt-based programmes)
- Xt_InEventLoop this is called only once when the Xt main event loop starts (this only applies to Xt-based programmes)

# Creating an Extension

You will need to compile your C source code with whatever option your compiler uses for generating position-independent code. You may wish to include `$KARMAINCLUDEPATH/gmakedefs/general.h1` in your GNUmakefile because it defines make variables with the correct compiler flags that you require for your platform. Once you have compiled your C code, you will need to use the linker to generate a shared object with extension `.so` which may then be dynamically loaded. An example `GNUmakefile` is shown below:

    include $(KARMAINCLUDEPATH)/gmakedefs/general.h1

    myobject.so: mysource.o
            $(LDso) -o myobject.so mysource.o

This uses the implicit rule in <gmake> to compile the source file into an object. If necessary, you may define the `CFLAGS` variable to provide extra compile flags (the implicit rule uses this variable).

# Configuration

A single file is used to configure the extensions available. Your personal configuration file is scanned first, and is called `/.karma/extensions` which specifies which shared objects are available for the various services. After that the `$KARMABASE/site/share/extensions` file is scanned to load any extensions provided by your local Karma installer. Finally the `$KARMABASE/share/extensions` file is scanned to load any extensions provided with the Karma distribution. The format of this file is as follows:

    SO_DIRS  directories
    SERVICE  servicename objects

where:

- `directories` is a colon-separated list of directories containing shared objects. Simple Bourne Shell-like expansion of environment variables is supported (such as the ``'', `$variable`, `${variable}` and `${variable:-word}` notations). This aids the maintenance of extensions for different platforms
- `servicename` is the name of a service
- `objects` is the name of one or more objects (separated by whitespace) without the trailing `.so` extension

It is legal to list the same service multiple times: this simply concatenates the list of objects for that service. Functions will be called in the same order in which the configuration files were scanned.

# Example

The following is an example of an extension for the Filepopup widget (section 13.17.2) which defines the service `Filepopup`. It adds a few accelerator buttons to the file browser. Note how there is no state defined in the extension: using static variables must be done with caution, since the same `WidgetInitialise` function is called several times, once for each widget that is created.

To use this, my `/.karma/extensions` file contains the following:

    SO_DIRS  ~/.karma/lib/$MACHINE_OS
    SERVICE  Filepopup myobject

And the code to implement this follows:

    /*  This extension adds some accelerator buttons to a Filepopup widget
        Copyright (C) 1997-1998  Richard Gooch
        This code is distributed under the GNU General Public License
    */
    #include <stdio.h>
    #include <math.h>
    #include <errno.h>
    #include <X11/Intrinsic.h>
    #include <X11/Xos.h>
    #include <X11/StringDefs.h>
    #include <X11/Xaw/Command.h>
    #include <karma.h>
    #include <karma_a.h>
    #include <Xkw/Filepopup.h>
    #include <Xkw/Filewin.h>

    /*  Private functions  */
    STATIC_FUNCTION (Widget add_button,
                     (Widget box, CONST char *label, CONST char *directory) );
    STATIC_FUNCTION (void chdir_cbk, (Widget w, XtPointer client_data,
                                      XtPointer call_data) );

    /*  Public functions follow  */

    flag WidgetInitialise (Widget Request, Widget New)
    {
        Widget box;

        XtVaGetValues (New, XkwNboxWidget, &box, NULL);
        /*  Add the various personalised buttons  */
        add_button (box, "/", "/");
        add_button (box, "/data", "/data");
        add_button (box, "/cdrom", "/cdrom");
        return (TRUE);
    }   /*  End Function WidgetInitialise  */

    /*  Private functions follow */

    static Widget add_button (Widget box, CONST char *label, CONST char *directory)
    /*  [SUMMARY] Add a button to a box.
        <box> The enclosing box widget.
        <upper> The widget under which the new widget should be placed.
        <left> The widget to the left of where the widget should be placed.
        <label> The label to appear in the button. The pointer must remain valid.
        <directory> The directory to change to when the button is pressed. The
        pointer must remain valid.
        [RETURNS] The new button widget.
    */
    {
        Widget w;

        w = XtVaCreateManagedWidget ("button", commandWidgetClass, box,
                                     XtNlabel, label,
                                     NULL);
        XtAddCallback (w, XtNcallback, chdir_cbk, (XtPointer) directory);
        return (w);
    }   /*  End Function add_button  */

    static void chdir_cbk (Widget w, XtPointer client_data, XtPointer call_data)
    {
        Widget filewin, parent, filepopup;
        CONST char *dirname = (CONST char *) client_data;
        static char function_name[] = "Filepopup-ext_chdir_cbk";

        for ( parent = XtParent (w), filepopup = NULL;
              (filepopup == NULL) && (parent != NULL);
              parent = XtParent (parent) )
        {
            if ( XtIsFilepopup (parent) ) filepopup = parent;
        }
        if (filepopup == NULL)
        {
            fprintf (stderr, "Could not find a Filepopup ancestor\n");
            a_prog_bug (function_name);
        }
        XtVaGetValues (filepopup, XkwNfilewinWidget, &filewin, NULL);
        XkwFilewinChangeDirectory (filewin, dirname);
    }   /*  End Function chdir_cbk  */

Note how the exported function `WidgetInitialise` returns a boolean value. All extension functions must return either `TRUE` or `FALSE`.

Karma Home Page
Next: Graphics without Widgets Up: Karma Programming Manual Previous: Image Display Tool