A Simple Compiz Plugin Walkthrough

Introduction

Those living on the bleeding edge of Linux will have heard of Compiz, the fancy OpenGL-based window manager penned by Reveman and others at Novell. Compiz provides various plugins for different effects and utilities, many of which are copies of similar things on Mac OS X (such as Switcher, a copy of Expose).

It's possible to write your own plugins, too. I spent some time getting to know the basics of the plugin architecture, and produced probably one of the simplest plugins you could want: a plugin to change the opacity of a window. You can download the new plugin and source. (If you have a Compiz installation that's new, i.e. you're building from CVS or you used a package which was built very recently, and you're on a 32-bit machine, then putting the libraries in Compiz's plugins directory should work fine; if not, then it may not work until you compile it yourself with a new Compiz.)

This document is a walkthrough of the plugin. Hopefully this will make them seem a little less impenetrable for those interested in messing around with Compiz.

Compiling Plugins

If you want to compile this plugin (or indeed, your own plugin) first put the source file in with the other plugin source files. Move compiz-opacity.patch (from the archive) into this directory and, from within it, run
patch -p0 < compiz-opacity.patch
This will alter Makefile.am so that the opacity plugin is built along with the others. You can then run autogen.sh, make, sudo make install and try out the new plugin. (Thanks to Mike "PsyberOne" Sylvester for the Makefile.am patch.) Now let's go through the code.

Plugin Initialisation

Let's start from scratch. When a plugin is loaded by compiz, it calls a function to get a list of function pointers for various pass-throughs to be called when screen elements are created:
static CompPluginVTable opacityVTable = {
    "opacity",
    "Change Window Opacity",
    "Change window opacity in steps",
    opacityInit,
    opacityFini,
    opacityInitDisplay,
    opacityFiniDisplay,
    opacityInitScreen,
    opacityFiniScreen,
    0, /* InitWindow */
    0, /* FiniWindow */
    0, /* GetDisplayOptions */
    0, /* SetDisplayOption */
    opacityGetScreenOptions,
    opacitySetScreenOption,
    opacityDeps,
    sizeof (opacityDeps) / sizeof (opacityDeps[0])
};

CompPluginVTable *
getCompPluginInfo (void)
{
    return &opacityVTable;
}
The first three items in opacityVTable are the plugin name, short description and long description, followed by optional function pointers for functions that should be called when things are initialised and killed. The opacity plugin doesn't need to do anything special for windows or display-specific options, so those entries are left blank. The last two entries determine rules as to which plugins this one must come before or after; the dependencies are specified in a simple structure:
CompPluginDep opacityDeps[] = {
    { CompPluginRuleAfter, "decoration" },
    { CompPluginRuleAfter, "fade" },
    { CompPluginRuleAfter, "switcher" }
};
As you can probably guess, you can also specify CompPluginRuleBefore if you need a plugin to come before another one.

Now, as specified in the VTable, opacityInit and opacityFini are called when the plugin is loaded and unloaded, respectively:

static Bool
opacityInit (CompPlugin *p)
{
    displayPrivateIndex = allocateDisplayPrivateIndex ();
    if (displayPrivateIndex < 0)
	return FALSE;

    return TRUE;
}

static void
opacityFini (CompPlugin *p)
{
    if (displayPrivateIndex >= 0)
	freeDisplayPrivateIndex (displayPrivateIndex);
}
In opacityInit, we allocate an index into an array that exists for each display which allows us to store a pointer to a custom structure, and that index is stored. I'm sure you can then guess what happens in opacityFini.

But what is this mysterious custom structure? Well, the data we store for each display is defined in OpacityDisplay:

typedef struct _OpacityDisplay {
    int		    screenPrivateIndex;
    HandleEventProc handleEvent;
} OpacityDisplay;
It stores another custom array index, this time into an array of custom data structs for each screen, and a function pointer for the HandleEvent procedure which is used to look for keyboard interaction. The actual memory space for the data structure is allocated when each display is created:
static Bool
opacityInitDisplay (CompPlugin  *p,
		 CompDisplay *d)
{
    OpacityDisplay *fd;

    fd = malloc (sizeof (OpacityDisplay));
    if (!fd)
	return FALSE;

    fd->screenPrivateIndex = allocateScreenPrivateIndex (d);
    if (fd->screenPrivateIndex < 0)
    {
	free (fd);
	return FALSE;
    }

    WRAP (fd, d, handleEvent, opacityHandleEvent);

    d->privates[displayPrivateIndex].ptr = fd;

    return TRUE;
}

static void
opacityFiniDisplay (CompPlugin *p,
		 CompDisplay *d)
{
    OPACITY_DISPLAY (d);

    freeScreenPrivateIndex (d, fd->screenPrivateIndex);

    UNWRAP (fd, d, handleEvent);

    free (fd);
}
You can see that it's mostly the same sort of thing as the main plugin initialisation, except this time we're first allocating memory for our custom per-display data and getting the array index for our own custom screen data. We actually store the custom data when we put it into the privates array for the display, which is just an array of pointers to custom data provided by all plugins.

The new stuff here is the macros. First, WRAP and UNWRAP. They're defined in compiz.h:

#define WRAP(priv, real, func, wrapFunc) \
    (priv)->func = (real)->func;	 \
    (real)->func = (wrapFunc)

#define UNWRAP(priv, real, func) \
    (real)->func = (priv)->func
They look a little odd, but all they do is make it so that instead of the normal handleEvent function being called for the display, our own function is called instead (while storing the pointer to the old one so that we can call it if necessary). It's an ugly C version of polymorphism.

Second, OPACITY_DISPLAY. This is defined in the plugin, as you'd expect:

#define GET_OPACITY_DISPLAY(d)				     \
    ((OpacityDisplay *) (d)->privates[displayPrivateIndex].ptr)

#define OPACITY_DISPLAY(d)			   \
    OpacityDisplay *fd = GET_OPACITY_DISPLAY (d)
When you call OPACITY_DISPLAY, a new variable fd is created which is a pointer to the custom data stored in the bog-standard compiz display you pass to it.

Exactly the same type of thing happens for the screen, too; we have custom data:

typedef struct _OpacityScreen {
    int			   opacityStep;

    CompOption opt[OPACITY_SCREEN_OPTION_NUM];

    int wMask;
} OpacityScreen;
Now things are getting a bit more interesting. All three of these are items used in the plugin's execution.

  • opacityStep is storing the current value of the opacity stepping option; it's how much the opacity changes each time
  • opt stores the proper compiz options data for all the things that can be configured in gconf for this plugin
  • wMask stores a bitfield of the different types of window which the plugin can change, which is again set in the plugin's options and stored here as it changes

    This custom data space is allocated on initialisation of the screen:

    static Bool
    opacityInitScreen (CompPlugin *p,
    		CompScreen *s)
    {
        OpacityScreen *fs;
    
        OPACITY_DISPLAY (s->display);
    
        fs = malloc (sizeof (OpacityScreen));
        if (!fs)
    	return FALSE;
    
        fs->opacityStep = OPACITY_STEP_DEFAULT;
    
        opacityScreenInitOptions (fs);
    
        s->privates[fd->screenPrivateIndex].ptr = fs;
    
        return TRUE;
    }
    
    static void
    opacityFiniScreen (CompPlugin *p,
    		CompScreen *s)
    {
        OPACITY_SCREEN (s);
    
        free (fs);
    }
    
    Again, there's a new macro here, and it does the same sort of thing as the ones for display:
    #define GET_OPACITY_SCREEN(s, fd)					 \
        ((OpacityScreen *) (s)->privates[(fd)->screenPrivateIndex].ptr)
    
    #define OPACITY_SCREEN(s)							\
        OpacityScreen *fs = GET_OPACITY_SCREEN (s, GET_OPACITY_DISPLAY (s->display))
    
    When you call OPACITY_SCREEN with a CompScreen pointer, it creates the variable fs, which is a pointer to the custom data stored for that screen. Not really different from the display.

    One new function is called here, opacityScreenInitOptions. This initialises the options data structure with information about the settings that will exist in the GNOME configuration (gconf). See the next section for information on this.

    At ths point, the initialisation is done. It is possible to go one layer further with custom data structures for the individual windows. The opacity plugin doesn't actually need this level of information, but if you did, it is done in exactly the same way as for displays and screens.

    Plugin Options

    A big part of each plugin is the getting and setting of options as specified in the gconf. Plugins can store options for each screen and each display, although I don't know of any that store display-specific data - convention seems to be that the data is usually screen-specific.

    opacityInitScreen calls a function to set up the options data structure so that we know exactly what options to store and of what type they are. It is as follows:

    static void
    opacityScreenInitOptions (OpacityScreen *fs)
    {
        CompOption *o;
        int	       i;
    
        o = &fs->opt[OPACITY_SCREEN_OPTION_STEP];
        o->name		= "step";
        o->shortDesc	= "Opacity Step";
        o->longDesc		= "Opacity change step";
        o->type		= CompOptionTypeInt;
        o->value.i		= OPACITY_STEP_DEFAULT;
        o->rest.i.min	= OPACITY_STEP_MIN;
        o->rest.i.max	= OPACITY_STEP_MAX;
    
        o = &fs->opt[OPACITY_SCREEN_OPTION_INCREASE];
        o->name			     = "increase";
        o->shortDesc		     = "Increase Opacity";
        o->longDesc			     = "Increase Opacity";
        o->type			     = CompOptionTypeBinding;
        o->value.bind.type		     = CompBindingTypeButton;
        o->value.bind.u.button.modifiers = OPACITY_INCREASE_MODIFIERS_DEFAULT;
        o->value.bind.u.button.button    = OPACITY_INCREASE_BUTTON_DEFAULT;
    
        o = &fs->opt[OPACITY_SCREEN_OPTION_DECREASE];
        o->name			     = "decrease";
        o->shortDesc		     = "Decrease Opacity";
        o->longDesc			     = "Decrease Opacity";
        o->type			     = CompOptionTypeBinding;
        o->value.bind.type		     = CompBindingTypeButton;
        o->value.bind.u.button.modifiers = OPACITY_DECREASE_MODIFIERS_DEFAULT;
        o->value.bind.u.button.button    = OPACITY_DECREASE_BUTTON_DEFAULT;
    
        o = &fs->opt[OPACITY_SCREEN_OPTION_WINDOW_TYPE];
        o->name	         = "window_types";
        o->shortDesc         = "Window Types";
        o->longDesc	         = "Window types that can have opacity changed";
        o->type	         = CompOptionTypeList;
        o->value.list.type   = CompOptionTypeString;
        o->value.list.nValue = N_WIN_TYPE;
        o->value.list.value  = malloc (sizeof (CompOptionValue) * N_WIN_TYPE);
        for (i = 0; i < N_WIN_TYPE; i++)
    	o->value.list.value[i].s = strdup (winType[i]);
        o->rest.s.string     = windowTypeString;
        o->rest.s.nString    = nWindowTypeString;
    
        fs->wMask = compWindowTypeMaskFromStringList (&o->value);
    }
    
    Rather large, isn't it. It should be fairly easy to understand, though - each option has an entry in the opt array, which is contained in the screen's custom data structure. The various settings dictate what kind of option it is; a list, an integer, a floating-point value, a keybinding, and soforth.

    Take note of the last option, which is the list of different window types - this list is generated from a structure defined at the beginning of the file:

    static char *winType[] = {
        "Dock",
        "Toolbar",
        "Menu",
        "Utility",
        "Splash",
        "Normal",
        "Dialog",
        "ModalDialog",
        "Unknown"
    };
    #define N_WIN_TYPE (sizeof (winType) / sizeof (winType[0]))
    
    compWindowTypeMaskFromStringList takes a list such as this and generates a bitmask of the different window types, so that window types can be compared with this list via bitwise-AND operations. This initial mask is also stored in the screen's custom data.

    The functions for the actual getting and setting of options are specified in the VTable given earlier; here are those functions:

    static CompOption *
    opacityGetScreenOptions (CompScreen *screen,
    		      int	 *count)
    {
        OPACITY_SCREEN (screen);
    
        *count = NUM_OPTIONS (fs);
        return fs->opt;
    }
    
    static Bool
    opacitySetScreenOption (CompScreen      *screen,
    		     char	     *name,
    		     CompOptionValue *value)
    {
        CompOption *o;
        int	       index;
    
        OPACITY_SCREEN (screen);
    
        o = compFindOption (fs->opt, NUM_OPTIONS (fs), name, &index);
        if (!o)
    	return FALSE;
    
        switch (index) {
        case OPACITY_SCREEN_OPTION_STEP:
    	if (compSetIntOption (o, value))
    	{
    	    fs->opacityStep = o->value.i;
    	    return TRUE;
    	}
    	break;
        case OPACITY_SCREEN_OPTION_INCREASE:
        case OPACITY_SCREEN_OPTION_DECREASE:
    	if (addScreenBinding (screen, &value->bind))
    	{
    	    removeScreenBinding (screen, &o->value.bind);
    
    	    if (compSetBindingOption (o, value))
    		return TRUE;
    	}
    	break;
        case OPACITY_SCREEN_OPTION_WINDOW_TYPE:
    	if (compSetOptionList (o, value))
    	{
    	    fs->wMask = compWindowTypeMaskFromStringList (&o->value);
    	    fs->wMask &= ~CompWindowTypeDesktopMask;
    	    return TRUE;
    	}
        default:
    	break;
        }
    
        return FALSE;
    }
    
    The first one is fairly self-explanatory, returning the options data struct. The second one actually deals with verifying the options as they're changed. The constants used as the cases are just indices into the options array stored for each screen.

  • The first case should be pretty easy to understand - it just gets the integer value of the option data and puts it into the screen's custom data struct.
  • The second and third cases are for options which specify keyboard shortcuts. First, it tries to tell X to listen for the new keyboard shortcut; if that's fine, it removes the listening for the old shortcut and then saves the new shortcut into the option structure.
  • The fourth case is a list of different window types. The new list is saved into the option data, and Compiz's function to build a bitmask of window types from the list is used. Note that it's made sure you can't do this for a desktop window - things go a little strange when you alter the desktop to be transparent!

    That's all there is to options. Look around the other plugins for different data types such as float (for which you can specify a precision to be enforced, such as one decimal place, or to the nearest 0.2, or somesuch).

    Plugin Operation

    This is the meat of it. How the plugin actually works. And... well, there isn't much for this one, being fairly simple. However, it does demonstrate how to get keyboard input.

    In initialisation, we overrode the default event handling function to call our own using WRAP. This is where that comes in. Here's our function:

    static void
    opacityHandleEvent (CompDisplay *d,
    		 XEvent      *event)
    {
        CompWindow *w;
        CompScreen *s;
    
        OPACITY_DISPLAY (d);
    
        switch (event->type) {
        case KeyPress:
        case KeyRelease:
    	s = findScreenAtDisplay (d, event->xkey.root);
    	if (s)
    	{
    	    OPACITY_SCREEN (s);
    
    	    if (EV_KEY (&fs->opt[OPACITY_SCREEN_OPTION_INCREASE], event))
    		opacityChangeOpacity(s, TRUE);
    
    	    if (EV_KEY (&fs->opt[OPACITY_SCREEN_OPTION_DECREASE], event))
    		opacityChangeOpacity(s, FALSE);
    	}
    	break;
        case ButtonPress:
        case ButtonRelease:
    	s = findScreenAtDisplay (d, event->xbutton.root);
    	if (s)
    	{
    	    OPACITY_SCREEN (s);
    
    	    if (EV_BUTTON (&fs->opt[OPACITY_SCREEN_OPTION_INCREASE], event))
    		opacityChangeOpacity(s, TRUE);
    
    	    if (EV_BUTTON (&fs->opt[OPACITY_SCREEN_OPTION_DECREASE], event))
    		opacityChangeOpacity(s, FALSE);
    	}
    	break;
        default:
    	break;
        }
    
        UNWRAP (fd, d, handleEvent);
        (*d->handleEvent) (d, event);
        WRAP (fd, d, handleEvent, opacityHandleEvent);
    }
    
    Basically, it listens for the different keyboard events, checks to see if the key codes match the options set in gconf to either increase or decrease the opacity, and calls opacityChangeOpacity as necessary. The event then falls through to the original event handler through the UNWRAP-call-WRAP combination (something you'll see a lot in more advanced plugins) - this is just the same sort of thing as calling a base class's overridden function in C++ or Java.

    opacityChangeOpacity is nothing hugely special, although it is the function that executes the core functionality of the plugin:

    static void
    opacityChangeOpacity (CompScreen* s,
    		Bool increase)
    {
        CompWindow* w;
    
        OPACITY_SCREEN (s);
        
        if (s->display->activeWindow)
        {
            w = findWindowAtScreen (s, s->display->activeWindow);
        
            if (w && (fs->wMask & w->type))
    	{
    	    GLushort step = (GLushort)(OPAQUE*(fs->opacityStep / 100.0f));
    
                if (increase)
    	    {
        	        if ((w->paint.opacity + step) < OPAQUE)
    		    w->paint.opacity += step;
    		else
    		    w->paint.opacity = OPAQUE;
    	    }
    	    else
    	    {
    		/* don't let them set opacity to 0 - make step the minimum opacity */
    		if (w->paint.opacity >= 2*step)
    		    w->paint.opacity -= step;
    		else
    		    w->paint.opacity = step;
    	    }
    
    	    addWindowDamage (w);
    	}
        }
    }
    
    This plugin alters the opacity of the window that currently has focus, the ID of which is given by s->display->activeWindow, where s is the CompScreen pointer passed to us by the event handler for that screen. findWindowAtScreen then gets the CompWindow pointer for that window ID, from which we alter its opacity, which is stored in that window's paint object. (Paint also contains saturation and brightness, if you wanted to mess around with those.) Finally, addWindowDamage is called to make X repaint the window - it isn't much good if the transparency's changed but you can't see it happen.

    And that's it. That's a Compiz plugin. Neat.

    More Advanced Plugins

    Obviously this is a very simple plugin. But the source code for the others is available for you to look at. Things like switcher show you how to temporarily move and scale windows, while wobbly shows you how to render the window as something other than just a rectangle. The world is your oyster. In theory.

    Francis Woodhouse
    fwoodhouse at gmail dot com
    http://www.downwithnumbers.com