/*
* 9menu.c
*
* This program puts up a window that is just a menu, and executes
* commands that correspond to the items selected.
*
* Initial idea: Arnold Robbins
* Version using libXg: Matty Farrow (some ideas borrowed)
* This code by: David Hogan and Arnold Robbins
*
* Copyright (c), Arnold Robbins and David Hogan
*
* Arnold Robbins
* arnold@skeeve.com
* October, 1994
*
* Code added to cause pop-up (unIconify) to move menu to mouse.
* Christopher Platt
* platt@coos.dartmouth.edu
* May, 1995
*
* Said code moved to -teleport option, and -warp option added.
* Arnold Robbins
* June, 1995
*
* Code added to allow -fg and -bg colors.
* John M. O'Donnell
* odonnell@stpaul.lampf.lanl.gov
* April, 1997
*
* Code added for -file and -path optioins.
* Peter Seebach
* seebs@plethora.net
* October, 2001
*
* Code added to allow up and down arrow keys to go up
* and down menu and RETURN to select an item.
* Matthias Bauer
* bauerm@immd1.informatik.uni-erlangen.de
* June 2003
*
* Changed a couple of minor details, renamed it to larsmenu
* so as to not cause conflicts when using package managers
* to install both larswm and 9menu, and changed function
* prototypes to ANSI C model. Added code to prevent it from
* being started more than once.
* Lars Bernhardsson
* lab@fnurt.net
* May 2004.
*/
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <X11/X.h>
#include <X11/Xatom.h>
#include <X11/Xlib.h>
#include <X11/Xproto.h>
#include <X11/Xutil.h>
#include <X11/keysymdef.h>
#include <X11/keysym.h>
#include <X11/Xmu/WinUtil.h>
Display *dpy; /* lovely X stuff */
int screen;
Window root;
Window menuwin;
GC gc;
unsigned long black;
unsigned long white;
char *fgcname = NULL;
char *bgcname = NULL;
Colormap defcmap;
XColor color;
XFontStruct *font;
Atom wm_protocols;
Atom wm_delete_window;
int g_argc; /* for XSetWMProperties to use */
char **g_argv;
int f_argc; /* for labels read from files */
char **f_argv;
char *geometry = "";
int savex, savey;
Window savewindow;
char *fontlist[] = { /* default font list if no -font */
"fixed",
NULL
};
/* the 9menu icon, for garish window managers */
#define nine_menu_width 40
#define nine_menu_height 40
static unsigned char nine_menu_bits[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xff, 0xff, 0x00, 0x00, 0x04, 0x00,
0x80, 0x00, 0x00, 0x04, 0x00, 0x80, 0x00, 0x00, 0xfc, 0xff, 0xff, 0x00,
0x00, 0xfc, 0xff, 0xff, 0x00, 0x00, 0x04, 0x00, 0x80, 0x00, 0x00, 0x04,
0x00, 0x80, 0x00, 0x00, 0xfc, 0xff, 0xff, 0x00, 0x00, 0xfc, 0xff, 0xff,
0x00, 0x00, 0x04, 0x00, 0x80, 0x00, 0x00, 0x04, 0x00, 0x80, 0x00, 0x00,
0xfc, 0xff, 0xff, 0x00, 0x00, 0xfc, 0xff, 0xff, 0x00, 0x00, 0x04, 0x00,
0x80, 0xe0, 0x01, 0x04, 0x00, 0x80, 0xe0, 0x00, 0xfc, 0xff, 0xff, 0xe0,
0x01, 0xfc, 0xff, 0xff, 0x20, 0x03, 0x04, 0x00, 0x80, 0x00, 0x06, 0x04,
0x00, 0x80, 0x00, 0x0c, 0xfc, 0xff, 0xff, 0x00, 0x08, 0xfc, 0xff, 0xff,
0x00, 0x00, 0x04, 0x00, 0x80, 0x00, 0x00, 0x04, 0x00, 0x80, 0x00, 0x00,
0xfc, 0xff, 0xff, 0x00, 0x00, 0xfc, 0xff, 0xff, 0x00, 0x00, 0x04, 0x00,
0x80, 0x00, 0x00, 0x04, 0x00, 0x80, 0x00, 0x00, 0xfc, 0xff, 0xff, 0x00,
0x00, 0xfc, 0xff, 0xff, 0x00, 0x00, 0x04, 0x00, 0x80, 0x00, 0x00, 0x04,
0x00, 0x80, 0x00, 0x00, 0xfc, 0xff, 0xff, 0x00, 0x00, 0xfc, 0xff, 0xff,
0x00, 0x00, 0x04, 0x00, 0x80, 0x00, 0x00, 0x04, 0x00, 0x80, 0x00, 0x00,
0xfc, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
/* Modify this to your liking */
#define CONFIG_MENU_UP_KEY XK_Up
#define CONFIG_MENU_DOWN_KEY XK_Down
#define CONFIG_MENU_SELECT_KEY XK_Return
#define CONFIG_MENU_CANCEL_KEY XK_Escape
#define PADX 100
#define PADY 30
char *progname; /* my name */
char *displayname; /* X display */
char *fontname; /* font */
char *labelname; /* window and icon name */
char *filename; /* file to read options or labels from */
int popup; /* true if we're a popup window */
int popdown; /* autohide after running a command */
int iconic; /* start iconified */
int teleport; /* teleport the menu */
int warp; /* warp the mouse */
char **labels; /* list of labels and commands */
char **commands;
int numitems;
char *shell = "/bin/sh"; /* default shell */
extern void usage (), run_menu (), spawn (), ask_wm_for_delete ();
extern void reap (), set_wm_hints ();
extern void redraw (), teleportmenu (), warpmouse (), restoremouse ();
extern void memory ();
extern int args ();
/* scanwins --- check if a larsmenu window is already open.
* Yes, this way is ugly. I know. Really, I know. =)
*/
int
larsmenu_open (void)
{
unsigned int i, nwins, do_exit = 0;
Window dw1, dw2, *wins;
XQueryTree (dpy, root, &dw1, &dw2, &wins, &nwins);
for (i = 0; i < nwins; i++)
{
Window client;
XClassHint clh;
if ((client = XmuClientWindow (dpy, wins[i])) != None)
{
if (XGetClassHint (dpy, client, &clh))
{
if (clh.res_class && clh.res_name)
{
if (!strcmp (clh.res_class, "larsmenu") &&
!strcmp (clh.res_name, "larsmenu"))
do_exit = 1;
}
if (clh.res_class)
XFree (clh.res_class);
if (clh.res_name)
XFree (clh.res_name);
}
}
if (do_exit)
break;
}
XFree ((void *) wins);
return do_exit;
}
/* memory --- print the out of memory message and die */
void
memory (char *s)
{
fprintf (stderr, "%s: couldn't allocate memory for %s\n", progname, s);
exit (1);
}
/* args --- go through the argument list, set options */
int
args (int argc, char **argv)
{
int i;
if (argc == 0 || argv == NULL || argv[0] == '\0')
return -1;
for (i = 0; i < argc && argv[i] != NULL; i++)
{
if (strcmp (argv[i], "-display") == 0)
{
displayname = argv[i + 1];
i++;
}
else if (strcmp (argv[i], "-file") == 0)
{
filename = argv[i + 1];
i++;
}
else if (strcmp (argv[i], "-font") == 0)
{
fontname = argv[i + 1];
i++;
}
else if (strcmp (argv[i], "-geometry") == 0)
{
geometry = argv[i + 1];
i++;
}
else if (strcmp (argv[i], "-label") == 0)
{
labelname = argv[i + 1];
i++;
}
else if (strcmp (argv[i], "-shell") == 0)
{
shell = argv[i + 1];
i++;
}
else if (strcmp (argv[i], "-popup") == 0)
popup++;
else if (strcmp (argv[i], "-popdown") == 0)
popdown++;
else if (strcmp (argv[i], "-fg") == 0)
fgcname = argv[++i];
else if (strcmp (argv[i], "-bg") == 0)
bgcname = argv[++i];
else if (strcmp (argv[i], "-iconic") == 0)
iconic++;
else if (strcmp (argv[i], "-path") == 0)
{
char pathbuf[MAXPATHLEN];
char *s, *t;
s = getenv ("PATH");
if (s != NULL)
{
/* append current dir to PATH */
getcwd (pathbuf, MAXPATHLEN);
t = malloc (strlen (s) + strlen (pathbuf) + 7);
sprintf (t, "PATH=%s:%s", pathbuf, s);
putenv (t);
}
}
else if (strcmp (argv[i], "-teleport") == 0)
teleport++;
else if (strcmp (argv[i], "-warp") == 0)
warp++;
else if (argv[i][0] == '-')
usage ();
else
break;
}
return i;
}
/* main --- crack arguments, set up X stuff, run the main menu loop */
int
main (int argc, char **argv)
{
int i, j;
char *cp;
XGCValues gv;
unsigned long mask;
int nlabels = 0;
g_argc = argc;
g_argv = argv;
/* set default label name */
if ((cp = strrchr (argv[0], '/')) == NULL)
labelname = argv[0];
else
labelname = ++cp;
++argv;
--argc;
/* and program name for diagnostics */
progname = labelname;
i = args (argc, argv);
numitems = argc - i;
if (numitems <= 0 && filename == NULL)
usage ();
if (filename)
{
/* Read options and labels from file */
char fbuf[1024];
FILE *fp;
fp = fopen (filename, "r");
if (fp == NULL)
{
fprintf (stderr, "%s: couldn't open '%s'\n", progname, filename);
exit (1);
}
while (fgets (fbuf, sizeof fbuf, fp))
{
char *s = fbuf;
strtok (s, "\n");
if (s[0] == '-')
{
char *temp[3];
temp[0] = s;
temp[1] = strchr (s, ' ');
if (temp[1])
{
*(temp[1]++) = '\0';
s = malloc (strlen (temp[1]) + 1);
if (s == NULL)
memory ("temporary argument");
strcpy (s, temp[1]);
temp[1] = s;
}
temp[2] = 0;
args (temp[1] ? 2 : 1, temp);
continue;
}
if (s[0] == '#')
continue;
/* allow - in menu items to be escaped */
if (s[0] == '\\')
++s;
/* allocate space */
if (f_argc < nlabels + 1)
{
int k;
char **temp = malloc (sizeof (char *) * (f_argc + 5));
if (temp == 0)
memory ("temporary item");
for (k = 0; k < nlabels; k++)
temp[k] = f_argv[k];
free (f_argv);
f_argv = temp;
f_argc += 5;
}
f_argv[nlabels] = malloc (strlen (s) + 1);
if (f_argv[nlabels] == NULL)
memory ("temporary text");
strcpy (f_argv[nlabels], s);
++nlabels;
}
}
labels = (char **) malloc ((numitems + nlabels) * sizeof (char *));
commands = (char **) malloc ((numitems + nlabels) * sizeof (char *));
if (commands == NULL || labels == NULL)
memory ("command and label arrays");
for (j = 0; j < numitems; j++)
{
labels[j] = argv[i + j];
if ((cp = strchr (labels[j], ':')) != NULL)
{
*cp++ = '\0';
commands[j] = cp;
}
else
commands[j] = labels[j];
}
/*
* Now we no longer need i (our offset into argv) so we recycle it,
* while keeping the old value of j!
*/
for (i = 0; i < nlabels; i++)
{
labels[j] = f_argv[i];
if ((cp = strchr (labels[j], ':')) != NULL)
{
*cp++ = '\0';
commands[j] = cp;
}
else
commands[j] = labels[j];
++j;
}
/* And now we merge the totals */
numitems += nlabels;
dpy = XOpenDisplay (displayname);
if (dpy == NULL)
{
fprintf (stderr, "%s: cannot open display", progname);
if (displayname != NULL)
fprintf (stderr, " %s", displayname);
fprintf (stderr, "\n");
exit (1);
}
screen = DefaultScreen (dpy);
root = RootWindow (dpy, screen);
/*
* This used to be
* black = BlackPixel(dpy, screen);
* white = WhitePixel(dpy, screen);
*/
defcmap = DefaultColormap (dpy, screen);
if (fgcname == NULL
|| XParseColor (dpy, defcmap, fgcname, &color) == 0
|| XAllocColor (dpy, defcmap, &color) == 0)
black = BlackPixel (dpy, screen);
else
black = color.pixel;
if (bgcname == NULL
|| XParseColor (dpy, defcmap, bgcname, &color) == 0
|| XAllocColor (dpy, defcmap, &color) == 0)
white = WhitePixel (dpy, screen);
else
white = color.pixel;
/* try user's font first */
if (fontname != NULL)
{
font = XLoadQueryFont (dpy, fontname);
if (font == NULL)
fprintf (stderr, "%s: warning: can't load font %s\n",
progname, fontname);
}
/* if no user font, try one of our default fonts */
if (font == NULL)
{
for (i = 0; fontlist[i] != NULL; i++)
{
font = XLoadQueryFont (dpy, fontlist[i]);
if (font != NULL)
break;
}
}
if (font == NULL)
{
fprintf (stderr, "%s: fatal: cannot load a font\n", progname);
exit (1);
}
gv.foreground = black ^ white;
gv.background = white;
gv.font = font->fid;
gv.function = GXxor;
gv.line_width = 0;
mask = GCForeground | GCBackground | GCFunction | GCFont | GCLineWidth;
gc = XCreateGC (dpy, root, mask, &gv);
signal (SIGCHLD, reap);
if (!larsmenu_open ())
run_menu ();
XCloseDisplay (dpy);
exit (0);
}
/* spawn --- run a command */
void
spawn (char *com)
{
int pid;
static char *sh_base = NULL;
if (sh_base == NULL)
{
sh_base = strrchr (shell, '/');
if (sh_base != NULL)
sh_base++;
else
sh_base = shell;
}
if (strncmp (com, "exec ", 5) != 0)
{
pid = fork ();
if (pid < 0)
{
fprintf (stderr, "%s: can't fork\n", progname);
return;
}
else if (pid > 0)
return;
}
else
{
com += 5;
}
close (ConnectionNumber (dpy));
execl (shell, sh_base, "-c", com, NULL);
execl ("/bin/sh", "sh", "-c", com, NULL);
_exit (1);
}
/* reap --- collect dead children */
void
reap (int s)
{
(void) wait ((int *) NULL);
signal (s, reap);
}
/* usage --- print a usage message and die */
void
usage (void)
{
fprintf (stderr, "usage: %s [-display displayname] [-font fname] ",
progname);
fprintf (stderr, "[-file filename] [-path]");
fprintf (stderr, "[-geometry geom] [-shell shell] [-label name] ");
fprintf (stderr, "[-popup] [-popdown] [-iconic] [-teleport] ");
fprintf (stderr, "[-warp] menitem:command ...\n");
exit (0);
}
/* run_menu --- put up the window, execute selected commands */
void
run_menu (void)
{
XEvent ev;
XClientMessageEvent *cmsg;
KeySym key;
int i, cur, old, wide, high, ico, dx, dy;
dx = 0;
for (i = 0; i < numitems; i++)
{
wide = XTextWidth (font, labels[i], strlen (labels[i])) + PADX;
if (wide > dx)
dx = wide;
}
wide = dx;
old = cur = -1;
high = font->ascent + font->descent + PADY;
dy = numitems * high;
set_wm_hints (wide, dy);
ask_wm_for_delete ();
#define MenuMask (ButtonPressMask|ButtonReleaseMask\
|LeaveWindowMask|PointerMotionMask|ButtonMotionMask\
|ExposureMask|StructureNotifyMask|KeyPressMask)
XSelectInput (dpy, menuwin, MenuMask);
XMapWindow (dpy, menuwin);
ico = 1; /* warp to first item */
i = 0; /* save menu Item position */
for (;;)
{
XNextEvent (dpy, &ev);
switch (ev.type)
{
default:
fprintf (stderr, "%s: unknown ev.type %d\n", progname, ev.type);
break;
case ButtonRelease:
/* ignore mouse wheel */
if (ev.xbutton.button == Button4 || ev.xbutton.button == Button5)
break;
i = ev.xbutton.y / high;
if (ev.xbutton.x < 0 || ev.xbutton.x > wide)
break;
else if (i < 0 || i >= numitems)
break;
if (warp)
restoremouse ();
if (strcmp (labels[i], "exit") == 0)
{
if (commands[i] != labels[i])
{
spawn (commands[i]);
}
return;
}
spawn (commands[i]);
if (popup)
return;
if (cur >= 0 && cur < numitems)
XFillRectangle (dpy, menuwin, gc, 0, cur * high, wide, high);
if (popdown)
XIconifyWindow (dpy, menuwin, screen);
cur = -1;
break;
case ButtonPress:
case MotionNotify:
old = cur;
cur = ev.xbutton.y / high;
if (ev.xbutton.x < 0 || ev.xbutton.x > wide)
cur = -1;
else if (cur < 0 || cur >= numitems)
cur = -1;
if (cur == old)
break;
if (old >= 0 && old < numitems)
XFillRectangle (dpy, menuwin, gc, 0, old * high, wide, high);
if (cur >= 0 && cur < numitems)
XFillRectangle (dpy, menuwin, gc, 0, cur * high, wide, high);
break;
case KeyPress:
key = XKeycodeToKeysym (dpy, ev.xkey.keycode, 0);
if (key != CONFIG_MENU_UP_KEY && key != CONFIG_MENU_CANCEL_KEY &&
key != CONFIG_MENU_DOWN_KEY && key != CONFIG_MENU_SELECT_KEY)
break;
if (key == CONFIG_MENU_CANCEL_KEY)
return;
if (key == CONFIG_MENU_UP_KEY)
{
old = cur;
cur--;
}
else if (key == CONFIG_MENU_DOWN_KEY)
{
old = cur;
cur++;
}
while (cur < 0)
cur += numitems;
cur %= numitems;
if (key == CONFIG_MENU_UP_KEY || key == CONFIG_MENU_DOWN_KEY)
{
if (cur == old)
break;
if (old >= 0 && old < numitems && cur != -1)
XFillRectangle (dpy, menuwin, gc, 0, old * high, wide, high);
if (cur >= 0 && cur < numitems && cur != -1)
XFillRectangle (dpy, menuwin, gc, 0, cur * high, wide, high);
break;
}
if (warp)
restoremouse ();
if (key == CONFIG_MENU_SELECT_KEY)
{
if (strcmp (labels[cur], "exit") == 0)
{
if (commands[cur] != labels[cur])
{
spawn (commands[cur]);
}
return;
}
spawn (commands[cur]);
}
if (popup)
return;
if (popdown)
XIconifyWindow (dpy, menuwin, screen);
break;
case LeaveNotify:
cur = old = -1;
XClearWindow (dpy, menuwin);
redraw (cur, high, wide);
break;
case ReparentNotify:
case ConfigureNotify:
/*
* ignore these, they come from XMoveWindow
* and are enabled by Struct..
*/
break;
case UnmapNotify:
ico = 1;
XClearWindow (dpy, menuwin);
break;
case MapNotify:
if (ico)
{
if (teleport)
teleportmenu (i, wide, high);
else if (warp)
warpmouse (i, wide, high);
}
XClearWindow (dpy, menuwin);
redraw (cur = i, high, wide);
ico = 0;
break;
case Expose:
XClearWindow (dpy, menuwin);
redraw (cur, high, wide);
break;
case ClientMessage:
cmsg = &ev.xclient;
if (cmsg->message_type == wm_protocols
&& cmsg->data.l[0] == wm_delete_window)
return;
case MappingNotify: /* why do we get this? */
break;
}
}
}
/* set_wm_hints --- set all the window manager hints */
void
set_wm_hints (int wide, int high)
{
Pixmap iconpixmap;
XWMHints *wmhints;
XSizeHints *sizehints;
XClassHint *classhints;
XTextProperty wname, iname;
if ((sizehints = XAllocSizeHints ()) == NULL)
memory ("size hints");
if ((wmhints = XAllocWMHints ()) == NULL)
memory ("window manager hints");
if ((classhints = XAllocClassHint ()) == NULL)
memory ("class hints");
/* fill in hints in order to parse geometry spec */
sizehints->width = sizehints->min_width = sizehints->max_width = wide;
sizehints->height = sizehints->min_height = sizehints->max_height = high;
sizehints->flags = USSize | PSize | PMinSize | PMaxSize;
if (XWMGeometry (dpy, screen, geometry, "", 1, sizehints,
&sizehints->x, &sizehints->y,
&sizehints->width, &sizehints->height,
&sizehints->win_gravity) & (XValue | YValue))
sizehints->flags |= USPosition;
/* override -geometry for size of window */
sizehints->width = sizehints->min_width = sizehints->max_width = wide;
sizehints->height = sizehints->min_height = sizehints->max_height = high;
if (XStringListToTextProperty (&labelname, 1, &wname) == 0)
memory ("window name structure");
if (XStringListToTextProperty (&labelname, 1, &iname) == 0)
memory ("icon name structure");
menuwin = XCreateSimpleWindow (dpy, root, sizehints->x, sizehints->y,
sizehints->width, sizehints->height, 1,
black, white);
iconpixmap = XCreateBitmapFromData (dpy, menuwin,
(char *) nine_menu_bits,
nine_menu_width, nine_menu_height);
wmhints->icon_pixmap = iconpixmap;
wmhints->input = False; /* no keyboard input */
if (iconic)
wmhints->initial_state = IconicState;
else
wmhints->initial_state = NormalState;
wmhints->flags = IconPixmapHint | StateHint | InputHint;
classhints->res_name = progname;
classhints->res_class = "larsmenu";
#ifdef SET_PROPERTIES_MANUALLY
/*
* For some reason, XSetWMProperties (see below) is failing
* John O'Donnell replaces it with the following commands
* (this leaves out XSetWMClientMachine,
* and also environment variable checking from ClassHint)
*/
XSetWMName (dpy, menuwin, &wname);
XSetWMIconName (dpy, menuwin, &iname);
XSetCommand (dpy, menuwin, g_argv, g_argc);
XSetWMHints (dpy, menuwin, wmhints);
XSetClassHint (dpy, menuwin, classhints);
XSetWMNormalHints (dpy, menuwin, sizehints);
#else
XSetWMProperties (dpy, menuwin, &wname, &iname,
g_argv, g_argc, sizehints, wmhints, classhints);
#endif
}
/* ask_wm_for_delete --- jump through hoops to ask WM to delete us */
void
ask_wm_for_delete (void)
{
int status;
wm_protocols = XInternAtom (dpy, "WM_PROTOCOLS", False);
wm_delete_window = XInternAtom (dpy, "WM_DELETE_WINDOW", False);
status = XSetWMProtocols (dpy, menuwin, &wm_delete_window, 1);
if (status != True)
fprintf (stderr, "%s: could not ask for clean delete\n", progname);
}
/* redraw --- actually redraw the menu */
void
redraw (int cur, int high, int wide)
{
int tx, ty, i;
for (i = 0; i < numitems; i++)
{
tx = (wide - XTextWidth (font, labels[i], strlen (labels[i]))) / 2;
ty = i * high + font->ascent + (PADY / 2);
XDrawString (dpy, menuwin, gc, tx, ty, labels[i], strlen (labels[i]));
}
if (cur >= 0 && cur < numitems)
XFillRectangle (dpy, menuwin, gc, 0, cur * high, wide, high);
}
/* teleportmenu --- move the menu to the right place */
void
teleportmenu (int cur, int wide, int high)
{
int x, y, dummy;
Window wdummy;
if (XQueryPointer (dpy, menuwin, &wdummy, &wdummy, &x, &y,
&dummy, &dummy, (unsigned int *) &dummy))
XMoveWindow (dpy, menuwin, x - wide / 2, y - cur * high - high / 2);
}
/* warpmouse --- bring the mouse to the menu */
void
warpmouse (int cur, int wide, int high)
{
int dummy;
Window wdummy;
int offset;
/* move tip of pointer into middle of menu item */
offset = (font->ascent + font->descent + 1) / 2;
offset += 6; /* fudge factor */
if (XQueryPointer (dpy, menuwin, &wdummy, &wdummy, &savex, &savey,
&dummy, &dummy, (unsigned int *) &dummy))
XWarpPointer (dpy, None, menuwin, 0, 0, 0, 0,
wide / 2, cur * high - high / 2 + offset);
}
/* restoremouse --- put the mouse back where it was */
void
restoremouse (void)
{
XWarpPointer (dpy, menuwin, root, 0, 0, 0, 0, savex, savey);
}
syntax highlighted by Code2HTML, v. 0.9.1