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.
patch -p0 < compiz-opacity.patchThis 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.
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.
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.
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.
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).
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.
Francis Woodhouse
fwoodhouse at gmail dot com
http://www.downwithnumbers.com