From 95f709fa60acbf900bddc4284682e9dfcf314697 Mon Sep 17 00:00:00 2001 From: runge Date: Fri, 18 Dec 2009 23:38:28 -0500 Subject: [PATCH] Add tag=... to unixpw opts to set FD_TAG. Prefer Xvfb over Xdummy. Reduce wait time for https. Add 'Login succeeded' output to unixpw panel. --- x11vnc/ChangeLog | 4 +- x11vnc/README | 22 ++++++---- x11vnc/help.c | 16 +++++-- x11vnc/sslhelper.c | 6 ++- x11vnc/ssltools.h | 2 +- x11vnc/unixpw.c | 62 ++++++++++++++++++++------- x11vnc/user.c | 99 ++++++++++++++++++++++++++++++++++++++++---- x11vnc/x11vnc.1 | 18 +++++--- x11vnc/x11vnc.c | 5 ++- x11vnc/x11vnc_defs.c | 2 +- 10 files changed, 190 insertions(+), 46 deletions(-) diff --git a/x11vnc/ChangeLog b/x11vnc/ChangeLog index d86750d..ccaba93 100644 --- a/x11vnc/ChangeLog +++ b/x11vnc/ChangeLog @@ -3,7 +3,9 @@ DIRECT: with an extra XFlush and other safety measures. fflush(stderr) much in su_verify. Make the -unixpw env. vars UNIXPW_DISABLE_SSL and UNIXPW_DISABLE_LOCALHOST work correctly. - Make -loopbg actually imply -bg. + Make -loopbg actually imply -bg. Add tag=... to unixpw opts + to set FD_TAG. Prefer Xvfb over Xdummy. Reduce wait time + for https. Add 'Login succeeded' output fo unixpw panel. 2009-12-15 Karl Runge * x11vnc: X11VNC_REMOTE, X11VNC_TICKER, and VNC_CONNECT properties diff --git a/x11vnc/README b/x11vnc/README index 65393d7..597e2ef 100644 --- a/x11vnc/README +++ b/x11vnc/README @@ -2,7 +2,7 @@ Copyright (C) 2002-2009 Karl J. Runge All rights reserved. -x11vnc README file Date: Fri Dec 18 10:27:07 EST 2009 +x11vnc README file Date: Fri Dec 18 22:19:55 EST 2009 The following information is taken from these URLs: @@ -12905,7 +12905,7 @@ x11vnc: a VNC server for real X displays Here are all of x11vnc command line options: % x11vnc -opts (see below for -help long descriptions) -x11vnc: allow VNC connections to real X11 displays. 0.9.9 lastmod: 2009-12-17 +x11vnc: allow VNC connections to real X11 displays. 0.9.9 lastmod: 2009-12-18 x11vnc options: -display disp -auth file -N @@ -13032,7 +13032,7 @@ libvncserver-tight-extension options: % x11vnc -help -x11vnc: allow VNC connections to real X11 displays. 0.9.9 lastmod: 2009-12-17 +x11vnc: allow VNC connections to real X11 displays. 0.9.9 lastmod: 2009-12-18 (type "x11vnc -opts" to just list the options.) @@ -13104,7 +13104,8 @@ Options: to find the XAUTHORITY before anyone has logged into an X session yet, use: x11vnc -env FD_XDM=1 -auth guess ... (This will also find the XAUTHORITY if a user is already - logged into the X session.) + logged into the X session.) When running as root, + FD_XDM=1 will be tried if the initial -auth guess fails. -N If the X display is :N, try to set the VNC display to also be :N This just sets the -rfbport option to 5900+N @@ -14067,7 +14068,8 @@ Options: to find the XAUTHORITY before anyone has logged into an X session yet, use: x11vnc -env FD_XDM=1 -findauth ... (This will also find the XAUTHORITY if a user is already - logged into the X session.) + logged into the X session.) When running as root, + FD_XDM=1 will be tried if the initial -findauth fails. -create First try to find the user's display using FINDDISPLAY, if that doesn't succeed create an X session via the @@ -14264,6 +14266,12 @@ Options: (same as "xterm") to have the created display use that mode for the user session. + Specify "tag=..." to set the unique FD_TAG desktop + session tag described below. Note: this option will + be ignored if the FD_TAG env. var. is already set or + if the viewer-side supplied value is not completely + composed of alphanumeric or '_' or '-' characters. + To disable the option setting set the environment variable X11VNC_NO_UNIXPW_OPTS=1 before starting x11vnc. To set any other options, the user can use the gui @@ -14352,8 +14360,8 @@ Options: It will start looking for an open display number at :20 Override via X11VNC_CREATE_STARTING_DISPLAY_NUMBER=n - By default FINDCREATEDISPLAY will try Xdummy and then - Xvfb: + By default FINDCREATEDISPLAY will try Xvfb and then + Xdummy: The Xdummy wrapper is part of the x11vnc source code (x11vnc/misc/Xdummy) It should be available in PATH and diff --git a/x11vnc/help.c b/x11vnc/help.c index db690e4..d360e7a 100644 --- a/x11vnc/help.c +++ b/x11vnc/help.c @@ -122,7 +122,8 @@ void print_help(int mode) { " to find the XAUTHORITY before anyone has logged into an\n" " X session yet, use: x11vnc -env FD_XDM=1 -auth guess ...\n" " (This will also find the XAUTHORITY if a user is already\n" -" logged into the X session.)\n" +" logged into the X session.) When running as root,\n" +" FD_XDM=1 will be tried if the initial -auth guess fails.\n" "\n" "-N If the X display is :N, try to set the VNC display to\n" " also be :N This just sets the -rfbport option to 5900+N\n" @@ -1102,7 +1103,8 @@ void print_help(int mode) { " to find the XAUTHORITY before anyone has logged into an\n" " X session yet, use: x11vnc -env FD_XDM=1 -findauth ...\n" " (This will also find the XAUTHORITY if a user is already\n" -" logged into the X session.)\n" +" logged into the X session.) When running as root,\n" +" FD_XDM=1 will be tried if the initial -findauth fails.\n" "\n" "-create First try to find the user's display using FINDDISPLAY,\n" " if that doesn't succeed create an X session via the\n" @@ -1299,6 +1301,12 @@ void print_help(int mode) { " (same as \"xterm\") to have the created display use\n" " that mode for the user session.\n" "\n" +" Specify \"tag=...\" to set the unique FD_TAG desktop\n" +" session tag described below. Note: this option will\n" +" be ignored if the FD_TAG env. var. is already set or\n" +" if the viewer-side supplied value is not completely\n" +" composed of alphanumeric or '_' or '-' characters.\n" +"\n" " To disable the option setting set the environment\n" " variable X11VNC_NO_UNIXPW_OPTS=1 before starting x11vnc.\n" " To set any other options, the user can use the gui\n" @@ -1386,8 +1394,8 @@ void print_help(int mode) { " It will start looking for an open display number at :20\n" " Override via X11VNC_CREATE_STARTING_DISPLAY_NUMBER=n\n" "\n" -" By default FINDCREATEDISPLAY will try Xdummy and then\n" -" Xvfb:\n" +" By default FINDCREATEDISPLAY will try Xvfb and then\n" +" Xdummy:\n" "\n" " The Xdummy wrapper is part of the x11vnc source code\n" " (x11vnc/misc/Xdummy) It should be available in PATH and\n" diff --git a/x11vnc/sslhelper.c b/x11vnc/sslhelper.c index b36c7fd..42a5728 100644 --- a/x11vnc/sslhelper.c +++ b/x11vnc/sslhelper.c @@ -2919,7 +2919,7 @@ static int is_ssl_readable(int s_in, double last_https, char *last_get, * for each socket (and some clients send requests * rapid fire). */ - tv.tv_sec = 6; + tv.tv_sec = 4; } /* @@ -2935,14 +2935,16 @@ static int is_ssl_readable(int s_in, double last_https, char *last_get, if (getenv("X11VNC_HTTPS_VS_VNC_TIMEOUT")) { tv.tv_sec = atoi(getenv("X11VNC_HTTPS_VS_VNC_TIMEOUT")); } -if (db) fprintf(stderr, "tv_sec: %d - %s\n", (int) tv.tv_sec, last_get); +if (db) fprintf(stderr, "tv_sec: %d - '%s'\n", (int) tv.tv_sec, last_get); FD_ZERO(&rd); FD_SET(s_in, &rd); + if (db) fprintf(stderr, "is_ssl_readable: begin select(%d secs) %.6f\n", tv.tv_sec, dnow()); do { nfd = select(s_in+1, &rd, NULL, NULL, &tv); } while (nfd < 0 && errno == EINTR); + if (db) fprintf(stderr, "is_ssl_readable: finish select(%d secs) %.6f\n", tv.tv_sec, dnow()); if (db) fprintf(stderr, "https nfd: %d\n", nfd); diff --git a/x11vnc/ssltools.h b/x11vnc/ssltools.h index ebd8683..1d0b1ab 100644 --- a/x11vnc/ssltools.h +++ b/x11vnc/ssltools.h @@ -2282,7 +2282,7 @@ char create_display[] = "\n" "TRY=\"$1\"\n" "if [ \"X$TRY\" = \"X\" ]; then\n" -" TRY=Xdummy,Xvfb\n" +" TRY=Xvfb,Xdummy\n" "fi\n" "\n" "for curr_try in `echo \"$TRY\" | tr ',' ' '`\n" diff --git a/x11vnc/unixpw.c b/x11vnc/unixpw.c index d84ddd9..96a066d 100644 --- a/x11vnc/unixpw.c +++ b/x11vnc/unixpw.c @@ -52,6 +52,7 @@ extern char *crypt(const char*, const char *); #include "connections.h" #include "sslhelper.h" #include "cursor.h" +#include "rates.h" #include #if LIBVNCSERVER_HAVE_FORK @@ -1445,10 +1446,36 @@ int unixpw_verify(char *user, char *pass) { return ok; } +static int skip_it = 0; + +static void progress_skippy(void) { + int i, msec = get_net_latency(); /* probabaly not set yet.. */ + + if (msec > 300) { + msec = 300; + } else if (msec <= 100) { + msec = 100; + } + + skip_it = 1; + for (i = 0; i < 5; i++) { + if (i == 2) { + rfbPE(msec * 1000); + } else { + rfbPE(-1); + } + usleep(10*1000); + } + skip_it = 0; + + usleep(50*1000); +} + void unixpw_verify_screen(char *user, char *pass) { int x, y; char li[] = "Login incorrect"; + char ls[] = "Login succeeded"; char log[] = "login: "; char *colon = NULL; ClientData *cd = NULL; @@ -1481,7 +1508,18 @@ if (db) fprintf(stderr, "unixpw_verify: '%s' '%s'\n", user, db > 1 ? pass : "*** ok = unixpw_verify(user, pass); if (ok) { + char_row++; + char_col = 0; + + x = text_x(); + y = text_y(); + rfbDrawString(pscreen, &default8x16Font, x, y, ls, white_pixel()); + unixpw_mark(); + + progress_skippy(); + unixpw_accept(user); + if (keep_unixpw) { keep_unixpw_user = strdup(user); keep_unixpw_pass = strdup(pass); @@ -1492,6 +1530,7 @@ if (db) fprintf(stderr, "unixpw_verify: '%s' '%s'\n", user, db > 1 ? pass : "*** } } if (colon) *colon = ':'; + return; } if (colon) *colon = ':'; @@ -1536,7 +1575,6 @@ void unixpw_keystroke(rfbBool down, rfbKeySym keysym, int init) { static int echo = 1; char keystr[100]; char *str; - static int skip_it = 0; if (skip_it) { return; @@ -1628,8 +1666,8 @@ void unixpw_keystroke(rfbBool down, rfbKeySym keysym, int init) { char h2[] = " Specify options after a ':' like this: username:opt,opt=val,... Where an opt may be any of:"; char h3[] = " scale=... (n/m); scale_cursor=... (sc=); solid (so); id=; repeat; clear_mods (cm); clear_keys (ck);"; char h4[] = " clear_all (ca); speeds=... (sp=); readtimeout=... (rd=) rotate=... (ro=); noncache (nc) (nc=n);"; - char h5[] = " geom=WxHxD (ge=); nodisplay=... (nd=); viewonly (vo); gnome kde twm fvwm mwm dtwm wmaker xfce"; - char h6[] = " enlightenment Xsession failsafe. Examples: fred:3/4,so,cm wilma:geom=1024x768x16,kde"; + char h5[] = " geom=WxHxD (ge=); nodisplay=... (nd=); viewonly (vo); tag=...; gnome kde twm fvwm mwm dtwm wmaker"; + char h6[] = " xfce enlightenment Xsession failsafe. Examples: fred:3/4,so,cm wilma:geom=1024x768x16,kde"; int ch = 13, p; if (f1_help) { p = black_pixel(); @@ -1673,12 +1711,8 @@ void unixpw_keystroke(rfbBool down, rfbKeySym keysym, int init) { rfbDrawString(pscreen, &default8x16Font, text_x(), text_y(), msg, white_pixel()); unixpw_mark(); - skip_it = 1; - rfbPE(-1); - rfbPE(-1); - rfbPE(-1); - skip_it = 0; - usleep(10*1000); + + progress_skippy(); } unixpw_accept(u); free(u); @@ -1848,12 +1882,8 @@ if (db && db <= 2) fprintf(stderr, "u_cnt: %d %d/%d ks: 0x%x '%s'\n", u_cnt, x, rfbDrawString(pscreen, &default8x16Font, text_x(), text_y(), msg, white_pixel()); unixpw_mark(); - skip_it = 1; - rfbPE(-1); - rfbPE(-1); - rfbPE(-1); - skip_it = 0; - usleep(10*1000); + + progress_skippy(); } in_login = 0; @@ -2119,6 +2149,8 @@ void unixpw_msg(char *msg, int delay) { for (i=0; i<5; i++) { rfbPE(-1); rfbPE(-1); + rfbPE(50 * 1000); + rfbPE(-1); usleep(500 * 1000); if (i >= delay) { break; diff --git a/x11vnc/user.c b/x11vnc/user.c index fb4c604..c459f34 100644 --- a/x11vnc/user.c +++ b/x11vnc/user.c @@ -1262,6 +1262,7 @@ void user_supplied_opts(char *opts) { "noncache", "nc", "nodisplay", "nd", "viewonly", "vo", + "tag", NULL }; @@ -2053,6 +2054,35 @@ static char *build_create_cmd(char *cmd, int *saw_xdmcp, char *usslpeer, char *t sprintf(fdesd, "%d", p); } } + if (!getenv("FD_TAG")) { + char *s = NULL; + + q = strstr(t, "tag="); + if (q) s = strchr(q, ','); + if (s) *s = '\0'; + + if (q && strlen(q) < 120) { + char *p; + int ok = 1; + q = strchr(q, '=') + 1; + p = q; + while (*p != '\0') { + char c = *p; + if (*p == '_' || *p == '-') { + ; + } else if (!isalnum((int) c)) { + ok = 0; + rfbLog("bad tag char: '%c' in '%s'\n", c, q); + break; + } + p++; + } + if (ok) { + sprintf(fdtag, "%s", q); + } + } + if (s) *s = ','; + } free(t); } if (fdgeom[0] == '\0' && getenv("FD_GEOM")) { @@ -2097,6 +2127,20 @@ static char *build_create_cmd(char *cmd, int *saw_xdmcp, char *usslpeer, char *t snprintf(cdout, 120, "CREATE_DISPLAY_OUTPUT='%s'", getenv("CREATE_DISPLAY_OUTPUT")); } + if (strchr(fdgeom, '\'')) fdgeom[0] = '\0'; + if (strchr(fdopts, '\'')) fdopts[0] = '\0'; + if (strchr(fdextra, '\'')) fdextra[0] = '\0'; + if (strchr(fdprog, '\'')) fdprog[0] = '\0'; + if (strchr(fdxsrv, '\'')) fdxsrv[0] = '\0'; + if (strchr(fdcups, '\'')) fdcups[0] = '\0'; + if (strchr(fdesd, '\'')) fdesd[0] = '\0'; + if (strchr(fdnas, '\'')) fdnas[0] = '\0'; + if (strchr(fdsmb, '\'')) fdsmb[0] = '\0'; + if (strchr(fdtag, '\'')) fdtag[0] = '\0'; + if (strchr(fdxdum, '\'')) fdxdum[0] = '\0'; + if (strchr(fdsess, '\'')) fdsess[0] = '\0'; + if (strchr(cdout, '\'')) cdout[0] = '\0'; + set_env("FD_GEOM", fdgeom); set_env("FD_OPTS", fdopts); set_env("FD_EXTRA", fdextra); @@ -2115,6 +2159,9 @@ static char *build_create_cmd(char *cmd, int *saw_xdmcp, char *usslpeer, char *t if (!uu) { uu = keep_unixpw_user; } + if (strchr(uu, '\'')) { + uu = ""; + } create_cmd = (char *) malloc(strlen(tmp)+1 + strlen("env USER='' ") + strlen("FD_GEOM='' ") @@ -2212,29 +2259,59 @@ static char *certret_extract() { return upeer; } -static void check_nodisplay(char **nd) { - if (unixpw && keep_unixpw_opts && keep_unixpw_opts[0] != '\0') { - char *q, *t = keep_unixpw_opts; +static void check_nodisplay(char **nd, char **tag) { + if (unixpw && !getenv("X11VNC_NO_UNIXPW_OPTS") && keep_unixpw_opts && keep_unixpw_opts[0] != '\0') { + char *q, *t2, *t = keep_unixpw_opts; q = strstr(t, "nd="); if (! q) q = strstr(t, "nodisplay="); if (q) { - char *t2; q = strchr(q, '=') + 1; t = strdup(q); q = t; t2 = strchr(t, ','); if (t2) *t2 = '\0'; + while (*t != '\0') { if (*t == '+') { *t = ','; } t++; } - if (!strchr(q, '\'')) { + if (!strchr(q, '\'') && !strpbrk(q, "[](){}`'\"$&*|<>")) { if (! quiet) rfbLog("set X11VNC_SKIP_DISPLAY: %s\n", q); *nd = q; } } + + q = strstr(keep_unixpw_opts, "tag="); + if (getenv("FD_TAG")) { + *tag = strdup(getenv("FD_TAG")); + } else if (q) { + q = strchr(q, '=') + 1; + t = strdup(q); + q = t; + t2 = strchr(t, ','); + if (t2) *t2 = '\0'; + + if (strlen(q) < 120) { + int ok = 1; + while (*t != '\0') { + char c = *t; + if (*t == '_' || *t == '-') { + ; + } else if (!isalnum((int) c)) { + ok = 0; + rfbLog("bad tag char: '%c' in '%s'\n", c, q); + break; + } + t++; + } + if (ok) { + if (! quiet) rfbLog("set FD_TAG: %s\n", q); + *tag = q; + } + } + } } if (unixpw_system_greeter_active == 2) { if (!keep_unixpw_user) { @@ -2360,6 +2437,7 @@ static int do_run_cmd(char *cmd, char *create_cmd, char *users_list_save, int cr if (!strcmp(cmd, "FINDDISPLAY") || strstr(cmd, "FINDCREATEDISPLAY") == cmd) { char *nd = ""; + char *tag = ""; char fdout[128]; internal_cmd = 1; @@ -2388,7 +2466,7 @@ static int do_run_cmd(char *cmd, char *create_cmd, char *users_list_save, int cr if (getenv("X11VNC_SKIP_DISPLAY")) { nd = strdup(getenv("X11VNC_SKIP_DISPLAY")); } - check_nodisplay(&nd); + check_nodisplay(&nd, &tag); fdout[0] = '\0'; if (getenv("FIND_DISPLAY_OUTPUT")) { @@ -2396,8 +2474,13 @@ static int do_run_cmd(char *cmd, char *create_cmd, char *users_list_save, int cr } cmd = (char *) malloc(strlen("env X11VNC_SKIP_DISPLAY='' ") - + strlen(nd) + strlen(tmp) + strlen("/bin/sh ") + strlen(fdout) + 1); - sprintf(cmd, "env X11VNC_SKIP_DISPLAY='%s' %s /bin/sh %s", nd, fdout, tmp); + + strlen(nd) + strlen(" FD_TAG='' ") + strlen(tag) + strlen(tmp) + strlen("/bin/sh ") + strlen(fdout) + 1); + + if (strcmp(tag, "")) { + sprintf(cmd, "env X11VNC_SKIP_DISPLAY='%s' FD_TAG='%s' %s /bin/sh %s", nd, tag, fdout, tmp); + } else { + sprintf(cmd, "env X11VNC_SKIP_DISPLAY='%s' %s /bin/sh %s", nd, fdout, tmp); + } } rfbLog("wait_for_client: running: %s\n", cmd); diff --git a/x11vnc/x11vnc.1 b/x11vnc/x11vnc.1 index 81f2af9..adf87b4 100644 --- a/x11vnc/x11vnc.1 +++ b/x11vnc/x11vnc.1 @@ -2,7 +2,7 @@ .TH X11VNC "1" "December 2009" "x11vnc " "User Commands" .SH NAME x11vnc - allow VNC connections to real X11 displays - version: 0.9.9, lastmod: 2009-12-17 + version: 0.9.9, lastmod: 2009-12-18 .SH SYNOPSIS .B x11vnc [OPTION]... @@ -85,7 +85,8 @@ XDM/GDM/KDM: if you are running x11vnc as root and want to find the XAUTHORITY before anyone has logged into an X session yet, use: x11vnc \fB-env\fR FD_XDM=1 \fB-auth\fR guess ... (This will also find the XAUTHORITY if a user is already -logged into the X session.) +logged into the X session.) When running as root, +FD_XDM=1 will be tried if the initial \fB-auth\fR guess fails. .PP \fB-N\fR .IP @@ -1221,7 +1222,8 @@ XDM/GDM/KDM: if you are running x11vnc as root and want to find the XAUTHORITY before anyone has logged into an X session yet, use: x11vnc \fB-env\fR FD_XDM=1 \fB-findauth\fR ... (This will also find the XAUTHORITY if a user is already -logged into the X session.) +logged into the X session.) When running as root, +FD_XDM=1 will be tried if the initial \fB-findauth\fR fails. .PP \fB-create\fR .IP @@ -1448,6 +1450,12 @@ well. You can also supply "gnome", "kde", "twm", (same as "xterm") to have the created display use that mode for the user session. .IP +Specify "tag=..." to set the unique FD_TAG desktop +session tag described below. Note: this option will +be ignored if the FD_TAG env. var. is already set or +if the viewer-side supplied value is not completely +composed of alphanumeric or '_' or '-' characters. +.IP To disable the option setting set the environment variable X11VNC_NO_UNIXPW_OPTS=1 before starting x11vnc. To set any other options, the user can use the gui @@ -1540,8 +1548,8 @@ The option "\fB-create\fR" is an alias for this mode. It will start looking for an open display number at :20 Override via X11VNC_CREATE_STARTING_DISPLAY_NUMBER=n .IP -By default FINDCREATEDISPLAY will try Xdummy and then -Xvfb: +By default FINDCREATEDISPLAY will try Xvfb and then +Xdummy: .IP The Xdummy wrapper is part of the x11vnc source code (x11vnc/misc/Xdummy) It should be available in PATH and diff --git a/x11vnc/x11vnc.c b/x11vnc/x11vnc.c index 9795dd3..d6429e3 100644 --- a/x11vnc/x11vnc.c +++ b/x11vnc/x11vnc.c @@ -793,7 +793,7 @@ static void check_redir_services(void) { } if (db) fprintf(stderr, "TS_REDIR_PID Atom: %d = '%s'\n", (int) a, prop); - if (getenv("FD_TAG")) { + if (getenv("FD_TAG") && strcmp(getenv("FD_TAG"), "")) { a = XInternAtom(dpy, "FD_TAG", False); if (a != None) { Window rwin = RootWindow(dpy, DefaultScreen(dpy)); @@ -4630,7 +4630,6 @@ int main(int argc, char* argv[]) { rfbLog("If you *actually* want SSL, restart" " with -ssl on the cmdline\n"); - fprintf(stderr, "\n"); if (! nopw) { usleep(2000*1000); } @@ -4647,10 +4646,12 @@ int main(int argc, char* argv[]) { use_stunnel = 1; } } + rfbLog("\n"); } if (use_threads && !getenv("UNIXPW_THREADS")) { if (! quiet) { rfbLog("disabling -threads under -unixpw\n"); + rfbLog("\n"); } use_threads = 0; } diff --git a/x11vnc/x11vnc_defs.c b/x11vnc/x11vnc_defs.c index a3ae5c0..42a8198 100644 --- a/x11vnc/x11vnc_defs.c +++ b/x11vnc/x11vnc_defs.c @@ -47,7 +47,7 @@ int xtrap_base_event_type = 0; int xdamage_base_event_type = 0; /* date +'lastmod: %Y-%m-%d' */ -char lastmod[] = "0.9.9 lastmod: 2009-12-17"; +char lastmod[] = "0.9.9 lastmod: 2009-12-18"; /* X display info */