/*
* winrules plugin for compiz
*
* Copyright (C) 2007 Bellegarde Cedric (gnumdk (at) gmail.com)
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <compiz.h>
#include <X11/Xatom.h>
#define WINRULES_TARGET_WINDOWS (CompWindowTypeNormalMask | \
CompWindowTypeDialogMask | \
CompWindowTypeModalDialogMask |\
CompWindowTypeFullscreenMask | \
CompWindowTypeUnknownMask)
#define WINRULES_SCREEN_OPTION_SKIPTASKBAR_MATCH 0
#define WINRULES_SCREEN_OPTION_SKIPPAGER_MATCH 1
#define WINRULES_SCREEN_OPTION_ABOVE_MATCH 2
#define WINRULES_SCREEN_OPTION_BELOW_MATCH 3
#define WINRULES_SCREEN_OPTION_STICKY_MATCH 4
#define WINRULES_SCREEN_OPTION_FULLSCREEN_MATCH 5
#define WINRULES_SCREEN_OPTION_WIDGET_MATCH 6
#define WINRULES_SCREEN_OPTION_NOMOVE_MATCH 7
#define WINRULES_SCREEN_OPTION_NORESIZE_MATCH 8
#define WINRULES_SCREEN_OPTION_NOMINIMIZE_MATCH 9
#define WINRULES_SCREEN_OPTION_NOMAXIMIZE_MATCH 10
#define WINRULES_SCREEN_OPTION_NOCLOSE_MATCH 11
#define WINRULES_SCREEN_OPTION_NOFOCUS_MATCH 12
#define WINRULES_SCREEN_OPTION_SIZE_MATCHES 13
#define WINRULES_SCREEN_OPTION_SIZE_WIDTH_VALUES 14
#define WINRULES_SCREEN_OPTION_SIZE_HEIGHT_VALUES 15
#define WINRULES_SCREEN_OPTION_NUM 16
static CompMetadata winrulesMetadata;
static int displayPrivateIndex;
typedef struct _WinrulesWindow {
unsigned int allowedActions;
unsigned int stateSetMask;
unsigned int protocolSetMask;
Bool widgetSet;
Bool firstMap;
} WinrulesWindow;
typedef struct _WinrulesDisplay {
int screenPrivateIndex;
HandleEventProc handleEvent;
} WinrulesDisplay;
typedef struct _WinrulesScreen {
int windowPrivateIndex;
GetAllowedActionsForWindowProc getAllowedActionsForWindow;
CompOption opt[WINRULES_SCREEN_OPTION_NUM];
} WinrulesScreen;
#define GET_WINRULES_DISPLAY(d) \
((WinrulesDisplay *) (d)->privates[displayPrivateIndex].ptr)
#define WINRULES_DISPLAY(d) \
WinrulesDisplay *wd = GET_WINRULES_DISPLAY (d)
#define GET_WINRULES_SCREEN(s, wd) \
((WinrulesScreen *) (s)->privates[(wd)->screenPrivateIndex].ptr)
#define WINRULES_SCREEN(s) \
WinrulesScreen *ws = GET_WINRULES_SCREEN (s, GET_WINRULES_DISPLAY (s->display))
#define GET_WINRULES_WINDOW(w, ws) \
((WinrulesWindow *) (w)->privates[(ws)->windowPrivateIndex].ptr)
#define WINRULES_WINDOW(w) \
WinrulesWindow *ww = GET_WINRULES_WINDOW (w, \
GET_WINRULES_SCREEN (w->screen, \
GET_WINRULES_DISPLAY (w->screen->display)))
#define NUM_OPTIONS(s) (sizeof ((s)->opt) / sizeof (CompOption))
static void
winrulesSetProtocols (CompDisplay *display,
unsigned int protocols,
Window id)
{
Atom *protocol = NULL;
int count = 0;
if (protocols & CompWindowProtocolDeleteMask)
{
protocol = realloc (protocol, (count + 1) * sizeof(Atom));
protocol[count++] = display->wmDeleteWindowAtom;
}
if (protocols & CompWindowProtocolTakeFocusMask)
{
protocol = realloc (protocol, (count + 1) * sizeof(Atom));
protocol[count++] = display->wmTakeFocusAtom;
}
if (protocols & CompWindowProtocolPingMask)
{
protocol = realloc (protocol, (count + 1) * sizeof(Atom));
protocol[count++] = display->wmPingAtom;
}
if (protocols & CompWindowProtocolSyncRequestMask)
{
protocol = realloc (protocol, (count + 1) * sizeof(Atom));
protocol[count] = display->wmSyncRequestAtom;
}
XSetWMProtocols (display->display,
id,
protocol,
count);
XFree (protocol);
}
/* FIXME? Directly set inputHint, not a problem for now */
static void
winrulesSetNoFocus (CompWindow *w,
int optNum)
{
unsigned int newProtocol = w->protocols;
WINRULES_SCREEN (w->screen);
WINRULES_WINDOW (w);
if (matchEval (&ws->opt[optNum].value.match, w))
{
if (w->protocols & CompWindowProtocolTakeFocusMask)
{
newProtocol = w->protocols & ~CompWindowProtocolTakeFocusMask;
ww->protocolSetMask |= CompWindowProtocolTakeFocusMask;
w->inputHint = FALSE;
}
}
else if (ww->protocolSetMask & CompWindowProtocolTakeFocusMask)
{
newProtocol = w->protocols & CompWindowProtocolTakeFocusMask;
ww->protocolSetMask &= ~CompWindowProtocolTakeFocusMask;
w->inputHint = TRUE;
}
if (newProtocol != w->protocols)
winrulesSetProtocols (w->screen->display,
w->protocols,
w->id);
}
static void
winrulesUpdateState (CompWindow *w,
int optNum,
int mask)
{
unsigned int newState = w->state;
WINRULES_SCREEN (w->screen);
WINRULES_WINDOW (w);
if (matchEval (&ws->opt[optNum].value.match, w))
{
newState |= mask;
newState = constrainWindowState (newState, w->actions);
ww->stateSetMask |= (newState & mask);
}
else if (ww->stateSetMask & mask)
{
newState &= ~mask;
ww->stateSetMask &= ~mask;
}
if (newState != w->state)
{
changeWindowState (w, newState);
recalcWindowType (w);
recalcWindowActions (w);
if (mask & (CompWindowStateFullscreenMask |
CompWindowStateAboveMask |
CompWindowStateBelowMask ))
updateWindowAttributes (w, CompStackingUpdateModeNormal);
else
updateWindowAttributes (w, CompStackingUpdateModeNone);
}
}
static void
winrulesUpdateWidget (CompWindow *w)
{
Atom compizWidget = XInternAtom (w->screen->display->display,
"_COMPIZ_WIDGET",
FALSE);
WINRULES_SCREEN (w->screen);
WINRULES_WINDOW (w);
if (matchEval
(&ws->opt[WINRULES_SCREEN_OPTION_WIDGET_MATCH].value.match, w))
{
if (w->inShowDesktopMode || w->mapNum ||
w->attrib.map_state == IsViewable || w->minimized)
{
if (w->minimized || w->inShowDesktopMode)
unminimizeWindow (w);
XChangeProperty (w->screen->display->display, w->id, compizWidget,
XA_STRING, 8, PropModeReplace,
(unsigned char *)(int[]){-2}, 1);
ww->widgetSet = TRUE;
}
}
else if (ww->widgetSet)
{
XDeleteProperty (w->screen->display->display, w->id, compizWidget);
ww->widgetSet = FALSE;
}
}
static void
winrulesSetAllowedActions (CompWindow *w,
int optNum,
int action)
{
WINRULES_SCREEN (w->screen);
WINRULES_WINDOW (w);
if (matchEval (&ws->opt[optNum].value.match, w))
ww->allowedActions &= ~action;
else if ( !(ww->allowedActions & action))
ww->allowedActions |= action;
recalcWindowActions (w);
}
static Bool
winrulesMatchSizeValue (CompWindow *w,
CompOption *matches,
CompOption *widthValues,
CompOption *heightValues,
int *width,
int *height)
{
int i, min;
if (w->type & CompWindowTypeDesktopMask)
return FALSE;
min = MIN (matches->value.list.nValue, widthValues->value.list.nValue);
min = MIN (min, heightValues->value.list.nValue);
for (i = 0; i < min; i++)
{
if (matchEval (&matches->value.list.value[i].match, w))
{
*width = widthValues->value.list.value[i].i;
*height = heightValues->value.list.value[i].i;
return TRUE;
}
}
return FALSE;
}
static Bool
winrulesMatchSize (CompWindow *w,
int *width,
int *height)
{
WINRULES_SCREEN (w->screen);
return winrulesMatchSizeValue (w,
&ws->opt[WINRULES_SCREEN_OPTION_SIZE_MATCHES],
&ws->opt[WINRULES_SCREEN_OPTION_SIZE_WIDTH_VALUES],
&ws->opt[WINRULES_SCREEN_OPTION_SIZE_HEIGHT_VALUES],
width,
height);
}
static void
winrulesUpdateWindowSize (CompWindow *w,
int width,
int height)
{
XWindowChanges xwc;
unsigned int xwcm = 0;
if (width != w->serverWidth)
xwcm |= CWWidth;
if (height != w->serverHeight)
xwcm |= CWHeight;
xwc.x = w->serverX;
xwc.y = w->serverY;
xwc.width = width;
xwc.height = height;
configureXWindow (w, xwcm, &xwc);
}
static CompOption *
winrulesGetScreenOptions (CompPlugin *plugin,
CompScreen *screen,
int *count)
{
WINRULES_SCREEN (screen);
*count = NUM_OPTIONS (ws);
return ws->opt;
}
static Bool
winrulesSetScreenOption (CompPlugin *plugin,
CompScreen *screen,
char *name,
CompOptionValue *value)
{
CompOption *o;
CompWindow *w;
int index;
WINRULES_SCREEN (screen);
o = compFindOption (ws->opt, NUM_OPTIONS (ws), name, &index);
if (!o)
return FALSE;
switch (index)
{
case WINRULES_SCREEN_OPTION_SKIPTASKBAR_MATCH:
if (compSetMatchOption (o, value))
{
for (w = screen->windows; w; w = w->next)
{
if (!w->type & WINRULES_TARGET_WINDOWS)
continue;
winrulesUpdateState (w,
WINRULES_SCREEN_OPTION_SKIPTASKBAR_MATCH,
CompWindowStateSkipTaskbarMask);
}
return TRUE;
}
break;
case WINRULES_SCREEN_OPTION_SKIPPAGER_MATCH:
if (compSetMatchOption (o, value))
{
for (w = screen->windows; w; w = w->next)
{
if (!w->type & WINRULES_TARGET_WINDOWS)
continue;
winrulesUpdateState (w,
WINRULES_SCREEN_OPTION_SKIPPAGER_MATCH,
CompWindowStateSkipPagerMask);
}
return TRUE;
}
break;
case WINRULES_SCREEN_OPTION_ABOVE_MATCH:
if (compSetMatchOption (o, value))
{
for (w = screen->windows; w; w = w->next)
{
if (!w->type & WINRULES_TARGET_WINDOWS)
continue;
winrulesUpdateState (w,
WINRULES_SCREEN_OPTION_ABOVE_MATCH,
CompWindowStateAboveMask);
}
return TRUE;
}
break;
case WINRULES_SCREEN_OPTION_BELOW_MATCH:
if (compSetMatchOption (o, value))
{
for (w = screen->windows; w; w = w->next)
{
if (!w->type & WINRULES_TARGET_WINDOWS)
continue;
winrulesUpdateState (w,
WINRULES_SCREEN_OPTION_BELOW_MATCH,
CompWindowStateBelowMask);
}
return TRUE;
}
break;
case WINRULES_SCREEN_OPTION_STICKY_MATCH:
if (compSetMatchOption (o, value))
{
for (w = screen->windows; w; w = w->next)
{
if (!w->type & WINRULES_TARGET_WINDOWS)
continue;
winrulesUpdateState (w,
WINRULES_SCREEN_OPTION_STICKY_MATCH,
CompWindowStateStickyMask);
}
return TRUE;
}
break;
case WINRULES_SCREEN_OPTION_FULLSCREEN_MATCH:
if (compSetMatchOption (o, value))
{
for (w = screen->windows; w; w = w->next)
{
if (!w->type & WINRULES_TARGET_WINDOWS)
continue;
winrulesUpdateState (w,
WINRULES_SCREEN_OPTION_FULLSCREEN_MATCH,
CompWindowStateFullscreenMask);
}
return TRUE;
}
break;
case WINRULES_SCREEN_OPTION_NOMOVE_MATCH:
if (compSetMatchOption (o, value))
{
for (w = screen->windows; w; w = w->next)
{
if (!w->type & WINRULES_TARGET_WINDOWS)
continue;
winrulesSetAllowedActions (w,
WINRULES_SCREEN_OPTION_NOMOVE_MATCH,
CompWindowActionMoveMask);
}
return TRUE;
}
break;
case WINRULES_SCREEN_OPTION_NORESIZE_MATCH:
if (compSetMatchOption (o, value))
{
for (w = screen->windows; w; w = w->next)
{
if (!w->type & WINRULES_TARGET_WINDOWS)
continue;
winrulesSetAllowedActions (w,
WINRULES_SCREEN_OPTION_NORESIZE_MATCH,
CompWindowActionResizeMask);
}
return TRUE;
}
break;
case WINRULES_SCREEN_OPTION_NOMINIMIZE_MATCH:
if (compSetMatchOption (o, value))
{
for (w = screen->windows; w; w = w->next)
{
if (!w->type & WINRULES_TARGET_WINDOWS)
continue;
winrulesSetAllowedActions (w,
WINRULES_SCREEN_OPTION_NOMINIMIZE_MATCH,
CompWindowActionMinimizeMask);
}
return TRUE;
}
break;
case WINRULES_SCREEN_OPTION_NOMAXIMIZE_MATCH:
if (compSetMatchOption (o, value))
{
for (w = screen->windows; w; w = w->next)
{
if (!w->type & WINRULES_TARGET_WINDOWS)
continue;
winrulesSetAllowedActions (w,
WINRULES_SCREEN_OPTION_NOMAXIMIZE_MATCH,
CompWindowActionMaximizeVertMask|
CompWindowActionMaximizeHorzMask);
}
return TRUE;
}
break;
case WINRULES_SCREEN_OPTION_NOCLOSE_MATCH:
if (compSetMatchOption (o, value))
{
for (w = screen->windows; w; w = w->next)
{
if (!w->type & WINRULES_TARGET_WINDOWS)
continue;
winrulesSetAllowedActions (w,
WINRULES_SCREEN_OPTION_NOCLOSE_MATCH,
CompWindowActionCloseMask);
}
return TRUE;
}
break;
case WINRULES_SCREEN_OPTION_NOFOCUS_MATCH:
if (compSetMatchOption (o, value))
{
for (w = screen->windows; w; w = w->next)
{
if (!w->type & WINRULES_TARGET_WINDOWS)
continue;
winrulesSetNoFocus (w, WINRULES_SCREEN_OPTION_NOFOCUS_MATCH);
}
return TRUE;
}
break;
case WINRULES_SCREEN_OPTION_WIDGET_MATCH:
if (compSetMatchOption (o, value))
{
for (w = screen->windows; w; w = w->next)
{
if (!w->type & WINRULES_TARGET_WINDOWS)
continue;
winrulesUpdateWidget (w);
}
return TRUE;
}
break;
case WINRULES_SCREEN_OPTION_SIZE_MATCHES:
if (compSetOptionList (o, value))
{
int i;
for (i = 0; i < o->value.list.nValue; i++)
matchUpdate (screen->display, &o->value.list.value[i].match);
return TRUE;
}
break;
default:
if (compSetOption (o, value))
return TRUE;
break;
}
return FALSE;
}
static void
winrulesHandleEvent (CompDisplay *d,
XEvent *event)
{
CompWindow *w;
WINRULES_DISPLAY (d);
if (event->type == MapNotify)
{
w = findWindowAtDisplay (d, event->xmap.window);
if (w && w->type & WINRULES_TARGET_WINDOWS)
{
WINRULES_WINDOW (w);
/* Only apply at window creation.
* Using CreateNotify not working.
*/
if (ww->firstMap)
{
int width, height;
winrulesUpdateState (w,
WINRULES_SCREEN_OPTION_SKIPTASKBAR_MATCH,
CompWindowStateSkipTaskbarMask);
winrulesUpdateState (w,
WINRULES_SCREEN_OPTION_SKIPPAGER_MATCH,
CompWindowStateSkipPagerMask);
winrulesUpdateState (w,
WINRULES_SCREEN_OPTION_ABOVE_MATCH,
CompWindowStateAboveMask);
winrulesUpdateState (w,
WINRULES_SCREEN_OPTION_BELOW_MATCH,
CompWindowStateBelowMask);
winrulesUpdateState (w,
WINRULES_SCREEN_OPTION_STICKY_MATCH,
CompWindowStateStickyMask);
winrulesUpdateState (w,
WINRULES_SCREEN_OPTION_FULLSCREEN_MATCH,
CompWindowStateFullscreenMask);
winrulesUpdateWidget (w);
winrulesSetAllowedActions (w,
WINRULES_SCREEN_OPTION_NOMOVE_MATCH,
CompWindowActionMoveMask);
winrulesSetAllowedActions (w,
WINRULES_SCREEN_OPTION_NORESIZE_MATCH,
CompWindowActionResizeMask);
winrulesSetAllowedActions (w,
WINRULES_SCREEN_OPTION_NOMINIMIZE_MATCH,
CompWindowActionMinimizeMask);
winrulesSetAllowedActions (w,
WINRULES_SCREEN_OPTION_NOMAXIMIZE_MATCH,
CompWindowActionMaximizeVertMask|
CompWindowActionMaximizeHorzMask);
winrulesSetAllowedActions (w,
WINRULES_SCREEN_OPTION_NOCLOSE_MATCH,
CompWindowActionCloseMask);
if (winrulesMatchSize (w, &width, &height))
winrulesUpdateWindowSize (w, width, height);
}
ww->firstMap = FALSE;
}
}
else if (event->type == MapRequest)
{
w = findWindowAtDisplay (d, event->xmap.window);
if (w && w->type & WINRULES_TARGET_WINDOWS)
winrulesSetNoFocus (w,WINRULES_SCREEN_OPTION_NOFOCUS_MATCH);
}
UNWRAP (wd, d, handleEvent);
(*d->handleEvent) (d, event);
WRAP (wd, d, handleEvent, winrulesHandleEvent);
}
static void
winrulesGetAllowedActionsForWindow (CompWindow *w,
unsigned int *setActions,
unsigned int *clearActions)
{
WINRULES_SCREEN (w->screen);
WINRULES_WINDOW (w);
UNWRAP (ws, w->screen, getAllowedActionsForWindow);
(*w->screen->getAllowedActionsForWindow) (w, setActions, clearActions);
WRAP (ws, w->screen, getAllowedActionsForWindow,
winrulesGetAllowedActionsForWindow);
*clearActions |= ~ww->allowedActions;
}
static Bool
winrulesInitDisplay (CompPlugin *p,
CompDisplay *d)
{
WinrulesDisplay *wd;
wd = malloc (sizeof (WinrulesDisplay));
if (!wd)
return FALSE;
wd->screenPrivateIndex = allocateScreenPrivateIndex (d);
if (wd->screenPrivateIndex < 0)
{
free (wd);
return FALSE;
}
WRAP (wd, d, handleEvent, winrulesHandleEvent);
d->privates[displayPrivateIndex].ptr = wd;
return TRUE;
}
static void
winrulesFiniDisplay (CompPlugin *p,
CompDisplay *d)
{
WINRULES_DISPLAY (d);
freeScreenPrivateIndex (d, wd->screenPrivateIndex);
UNWRAP (wd, d, handleEvent);
free (wd);
}
static const CompMetadataOptionInfo winrulesScreenOptionInfo[] = {
{ "skiptaskbar_match", "match", 0, 0, 0 },
{ "skippager_match", "match", 0, 0, 0 },
{ "above_match", "match", 0, 0, 0 },
{ "below_match", "match", 0, 0, 0 },
{ "sticky_match", "match", 0, 0, 0 },
{ "fullscreen_match", "match", 0, 0, 0 },
{ "widget_match", "match", 0, 0, 0 },
{ "no_move_match", "match", 0, 0, 0 },
{ "no_resize_match", "match", 0, 0, 0 },
{ "no_minimize_match", "match", 0, 0, 0 },
{ "no_maximize_match", "match", 0, 0, 0 },
{ "no_close_match", "match", 0, 0, 0 },
{ "no_focus_match", "match", 0, 0, 0 },
{ "size_matches", "list", "<type>match</type>", 0, 0 },
{ "size_width_values", "list", "<type>int</type>", 0, 0 },
{ "size_height_values", "list", "<type>int</type>", 0, 0 }
};
static Bool
winrulesInitScreen (CompPlugin *p,
CompScreen *s)
{
WinrulesScreen *ws;
WINRULES_DISPLAY (s->display);
ws = malloc (sizeof (WinrulesScreen));
if (!ws)
return FALSE;
if (!compInitScreenOptionsFromMetadata (s,
&winrulesMetadata,
winrulesScreenOptionInfo,
ws->opt,
WINRULES_SCREEN_OPTION_NUM))
{
free (ws);
return FALSE;
}
ws->windowPrivateIndex = allocateWindowPrivateIndex(s);
if (ws->windowPrivateIndex < 0)
{
compFiniScreenOptions (s, ws->opt, WINRULES_SCREEN_OPTION_NUM);
free (ws);
return FALSE;
}
WRAP (ws, s, getAllowedActionsForWindow,
winrulesGetAllowedActionsForWindow);
s->privates[wd->screenPrivateIndex].ptr = ws;
return TRUE;
}
static void
winrulesFiniScreen (CompPlugin *p,
CompScreen *s)
{
WINRULES_SCREEN (s);
UNWRAP (ws, s, getAllowedActionsForWindow);
freeWindowPrivateIndex(s, ws->windowPrivateIndex);
compFiniScreenOptions (s, ws->opt, WINRULES_SCREEN_OPTION_NUM);
free (ws);
}
static Bool
winrulesInitWindow (CompPlugin *p,
CompWindow *w)
{
WINRULES_SCREEN (w->screen);
WinrulesWindow *ww = malloc (sizeof (WinrulesWindow));
if (!ww)
{
return FALSE;
}
ww->widgetSet = FALSE;
ww->stateSetMask = 0;
ww->protocolSetMask = 0;
ww->allowedActions = ~0;
ww->firstMap = TRUE;
w->privates[ws->windowPrivateIndex].ptr = ww;
return TRUE;
}
static void
winrulesFiniWindow (CompPlugin *p,
CompWindow *w)
{
WINRULES_WINDOW (w);
free (ww);
}
static Bool
winrulesInit (CompPlugin *p)
{
if (!compInitPluginMetadataFromInfo (&winrulesMetadata,
p->vTable->name,
0, 0,
winrulesScreenOptionInfo,
WINRULES_SCREEN_OPTION_NUM))
return FALSE;
displayPrivateIndex = allocateDisplayPrivateIndex ();
if (displayPrivateIndex < 0)
{
compFiniMetadata (&winrulesMetadata);
return FALSE;
}
compAddMetadataFromFile (&winrulesMetadata, p->vTable->name);
return TRUE;
}
static void
winrulesFini (CompPlugin *p)
{
freeDisplayPrivateIndex (displayPrivateIndex);
compFiniMetadata (&winrulesMetadata);
}
static int
winrulesGetVersion (CompPlugin *plugin,
int version)
{
return ABIVERSION;
}
static CompMetadata *
winrulesGetMetadata (CompPlugin *plugin)
{
return &winrulesMetadata;
}
static CompPluginVTable winrulesVTable = {
"winrules",
winrulesGetVersion,
winrulesGetMetadata,
winrulesInit,
winrulesFini,
winrulesInitDisplay,
winrulesFiniDisplay,
winrulesInitScreen,
winrulesFiniScreen,
winrulesInitWindow,
winrulesFiniWindow,
0, /* winrulesGetDisplayOptions, */
0, /* winrulesSetDisplayOption, */
winrulesGetScreenOptions,
winrulesSetScreenOption
};
CompPluginVTable *
getCompPluginInfo (void)
{
return &winrulesVTable;
}
syntax highlighted by Code2HTML, v. 0.9.1