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.
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.
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:
opacityInitDisplay (CompPlugin *p,
CompDisplay *d)
OpacityDisplay *fd;
fd = malloc (sizeof (OpacityDisplay));
if (!fd)
fd->screenPrivateIndex = allocateScreenPrivateIndex (d);
if (fd->screenPrivateIndex < 0)
free (fd);
WRAP (fd, d, handleEvent, opacityHandleEvent);
d->privates[displayPrivateIndex].ptr = fd;
opacityFiniDisplay (CompPlugin *p,
OPACITY_DISPLAY (d);
freeScreenPrivateIndex (d, fd->screenPrivateIndex);
UNWRAP (fd, d, handleEvent);
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:
opacityInitScreen (CompPlugin *p,
CompScreen *s)
OpacityScreen *fs;
OPACITY_DISPLAY (s->display);
fs = malloc (sizeof (OpacityScreen));
if (!fs)
fs->opacityStep = OPACITY_STEP_DEFAULT;
opacityScreenInitOptions (fs);
s->privates[fd->screenPrivateIndex].ptr = fs;
opacityFiniScreen (CompPlugin *p,
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.
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:
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->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;
opacitySetScreenOption (CompScreen *screen,
char *name,
CompOptionValue *value)
int index;
o = compFindOption (fs->opt, NUM_OPTIONS (fs), name, &index);
if (!o)
switch (index) {
case OPACITY_SCREEN_OPTION_STEP:
if (compSetIntOption (o, value))
fs->opacityStep = o->value.i;
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))
case OPACITY_SCREEN_OPTION_WINDOW_TYPE:
if (compSetOptionList (o, value))
fs->wMask &= ~CompWindowTypeDesktopMask;
default:
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).
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:
opacityHandleEvent (CompDisplay *d,
XEvent *event)
CompWindow *w;
CompScreen *s;
switch (event->type) {
case KeyPress:
case KeyRelease:
s = findScreenAtDisplay (d, event->xkey.root);
if (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);
case ButtonPress:
case ButtonRelease:
s = findScreenAtDisplay (d, event->xbutton.root);
if (EV_BUTTON (&fs->opt[OPACITY_SCREEN_OPTION_INCREASE], event))
if (EV_BUTTON (&fs->opt[OPACITY_SCREEN_OPTION_DECREASE], event))
...
e404