/*
   Copyright (C) 2002-2010 Karl J. Runge <runge@karlrunge.com> 
   All rights reserved.

This file is part of x11vnc.

x11vnc 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.

x11vnc 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 x11vnc; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA
or see <http://www.gnu.org/licenses/>.

In addition, as a special exception, Karl J. Runge
gives permission to link the code of its release of x11vnc with the
OpenSSL project's "OpenSSL" library (or with modified versions of it
that use the same license as the "OpenSSL" library), and distribute
the linked executables.  You must obey the GNU General Public License
in all respects for all of the code used other than "OpenSSL".  If you
modify this file, you may extend this exception to your version of the
file, but you are not obligated to do so.  If you do not wish to do
so, delete this exception statement from your version.
*/

/* -- appshare.c -- */

#include "x11vnc.h"

extern int pick_windowid(unsigned long *num);
extern char *get_xprop(char *prop, Window win);
extern int set_xprop(char *prop, Window win, char *value);
extern void set_env(char *name, char *value);
extern double dnow(void);

static char *usage =
"\n"
"  x11vnc -appshare: an experiment in application sharing via x11vnc.\n"
"\n"
#if !SMALL_FOOTPRINT
"  Usage:   x11vnc -appshare -id windowid -connect viewer_host:0\n"
"           x11vnc -appshare -id pick     -connect viewer_host:0\n"
"\n"
"  Both the -connect option and the -id (or -sid) option are required.\n"
"  (However see the -control option below that can replace -connect.)\n"
"\n"
"  The VNC viewer at viewer_host MUST be in 'listen' mode.  This is because\n"
"  a new VNC connection (and viewer window) is established for each new\n"
"  toplevel window that the application creates.  For example:\n"
"\n"
"       vncviewer -listen 0\n"
"\n"
"  The '-connect viewer_host:0' indicates the listening viewer to connect to.\n"
"\n"
"  No password should be used, otherwise it will need to be typed for each\n"
"  new window (or one could use vncviewer -passwd file if the viewer supports\n"
"  that.)  For security an SSH tunnel can be used:\n"
"\n"
"       ssh -R 5500:localhost:5500 user@server_host\n"
"\n"
"  (then use -connect localhost:0)\n"
"\n"
"  The -id/-sid option is as in x11vnc(1).  It is either a numerical window\n"
"  id or the string 'pick' which will ask the user to click on an app window.\n"
"  To track more than one application at the same time, list their window ids\n"
"  separated by commas (see also the 'add_app' command below.)\n"
"\n"
"  Additional options:\n"
"\n"
"      -h, -help      Print this help.\n"
"      -debug         Print debugging output (same as X11VNC_APPSHARE_DEBUG=1)\n"
"      -showmenus     Create a new viewer window even if a new window is\n"
"                     completely inside of an existing one.  Default is to\n"
"                     try to not show them in a new viewer window.\n"
"      -noexit        Do not exit if the main app (windowid/pick) window\n"
"                     goes away.  Default is to exit.\n"
"      -display dpy   X DISPLAY to use.\n"
"      -trackdir dir  Set tracking directory to 'dir'. x11vnc -appshare does\n"
"                     better if it can communicate with the x11vnc's via a\n"
"                     file channel. By default a dir in /tmp is used, -trackdir\n"
"                     specifies another directory, or use 'none' to disable.\n"
"      -args 'string' Pass options 'string' to x11vnc (e.g. -scale 3/4,\n"
"                     -viewonly, -wait, -once, etc.)\n"
"      -env VAR=VAL   Set environment variables on cmdline as in x11vnc.\n"
"\n"
"      -control file  This is a file that one edits to manage the appshare\n"
"                     mode.  It replaces -connect.  Lines beginning with '#'\n"
"                     are ignored.  Initially start off with all of the\n"
"                     desired clients in the file, one per line.  If you add\n"
"                     a new client-line, that client is connected to. If you\n"
"                     delete (or comment out) a client-line, that client is\n"
"                     disconnected (for this to work, do not disable trackdir.)\n"
"\n"
"                     You can also put cmd= lines in the control file to perform\n"
"                     different actions.  These are supported:\n"
"\n"
"                         cmd=quit            Disconnect all clients and exit.\n"
"                         cmd=restart         Restart all of the x11vnc's.\n"
"                         cmd=noop            Do nothing (e.g. ping)\n"
"                         cmd=x11vnc          Run ps(1) looking for x11vnc's\n"
"                         cmd=help            Print out help text.\n"
"                         cmd=add_window:win  Add a window to be watched.\n"
"                         cmd=del_window:win  Delete a window.\n"
"                         cmd=add_app:win     Add an application to be watched.\n"
"                         cmd=del_app:win     Delete an application.\n"
"                         cmd=add_client:host Add client ('internal' mode only)\n"
"                         cmd=del_client:host Del client ('internal' mode only)\n"
"                         cmd=list_windows    List all tracked windows.\n"
"                         cmd=list_apps       List all tracked applications.\n"
"                         cmd=list_clients    List all connected clients.\n"
"                         cmd=list_all        List all three.\n"
"                         cmd=print_logs      Print out the x11vnc logfiles.\n"
"                         cmd=debug:n         Set -debug to n (0 or 1).\n"
"                         cmd=showmenus:n     Set -showmenus to n (0 or 1).\n"
"                         cmd=noexit:n        Set -noexit to n (0 or 1).\n"
"\n"
"                     See the '-command internal' mode described below for a way\n"
"                     that tracks connected clients internally (not in a file.)\n"
"\n"
"                     In '-shell' mode (see below) you can type in the above\n"
"                     without the leading 'cmd='.\n"
"\n"
"                     For 'add_window' and 'del_window' the 'win' can be a\n"
"                     numerical window id or 'pick'.  Same for 'add_app'.  Be\n"
"                     sure to remove or comment out the add/del line quickly\n"
"                     (e.g. before picking) or it will be re-run the next time\n"
"                     the file is processed.\n"
"\n"
"                     If a file with the same name as the control file but\n"
"                     ending with suffix '.cmd' is found, then commands in it\n"
"                     (cmd=...) are processed and then the file is truncated.\n"
"                     This allows 'one time' command actions to be run.  Any\n"
"                     client hostnames in the '.cmd' file are ignored.  Also\n"
"                     see below for the X11VNC_APPSHARE_COMMAND X property\n"
"                     which is similar to '.cmd'\n"
"\n"
"      -control internal   Manage connected clients internally, see below.\n"
"      -control shell      Same as: -shell -control internal\n"
"\n"
"      -delay secs    Maximum timeout delay before re-checking the control file.\n"
"                     It can be a fraction, e.g. -delay 0.25  Default 0.5\n"
"\n"
"      -shell         Simple command line for '-control internal' mode (see the\n"
"                     details of this mode below.)  Enter '?' for command list.\n"
"\n"
"  To stop x11vnc -appshare press Ctrl-C, or (if -noexit not supplied) delete\n"
"  the initial app window or exit the application. Or cmd=quit in -control mode.\n"
"\n"
#if 0
"  If you want your setup to survive periods of time where there are no clients\n"
"  connected you will need to supply -args '-forever' otherwise the x11vnc's\n"
"  will exit when the last client disconnects.  Howerver, _starting_ with no\n"
"  clients (e.g. empty control file) will work without -args '-forever'.\n"
"\n"
#endif
"  In addition to the '.cmd' file channel, for faster response you can set\n"
"  X11VNC_APPSHARE_COMMAND X property on the root window to the string that\n"
"  would go into the '.cmd' file.  For example:\n"
"\n"
" xprop -root -f X11VNC_APPSHARE_COMMAND 8s -set X11VNC_APPSHARE_COMMAND cmd=quit\n"
"\n"
"  The property value will be set to 'DONE' after the command(s) is processed.\n"
"\n"
"  If -control file is specified as 'internal' then no control file is used\n"
"  and client tracking is done internally.  You must add and delete clients\n"
"  with the cmd=add_client:<client> and cmd=del_client:<client> commands.\n"
"  Note that '-control internal' is required for '-shell' mode.  Using\n"
"  '-control shell' implies internal mode and -shell.\n"
"\n"
"  Limitations:\n"
"\n"
"     This is a quick lash-up, many things will not work properly.\n"
"\n"
"     The main idea is to provide simple application sharing for two or more\n"
"     parties to collaborate without needing to share the entire desktop.  It\n"
"     provides an improvement over -id/-sid that only shows a single window.\n"
"\n"
"     Only reverse connections can be done.  (Note: one can specify multiple\n"
"     viewing hosts via: -connect host1,host2,host3 or add/remove them\n"
"     dynamically as described above.)\n"
"\n"
"     If a new window obscures an old one, you will see some or all of the\n"
"     new window in the old one.  The hope is this is a popup dialog or menu\n"
"     that will go away soon.  Otherwise a user at the physical display will\n"
"     need to move it. (See also the SSVNC viewer features described below.) \n"
"\n"
"     The viewer side cannot resize or make windows move on the physical\n"
"     display.  Again, a user at the physical display may need to help, or\n"
"     use the SSVNC viewer (see Tip below.)\n"
"\n"
"     Tip: If the application has its own 'resize corner', then dragging\n"
"          it may successfully resize the application window.\n"
"     Tip: Some desktop environments enable moving a window via, say,\n"
"          Alt+Left-Button-Drag.  One may be able to move a window this way.\n"
"          Also, e.g., Alt+Right-Button-Drag may resize a window.\n"
"     Tip: Clicking on part of an obscured window may raise it to the top.\n"
"          Also, e.g., Alt+Middle-Button may toggle Raise/Lower.\n"
"\n"
"     Tip: The SSVNC 1.0.25 unix and macosx vncviewer has 'EscapeKeys' hot\n"
"          keys that will move, resize, raise, and lower the window via the\n"
"          x11vnc -remote_prefix X11VNC_APPSHARE_CMD: feature.  So in the\n"
"          viewer while holding down Shift_L+Super_L+Alt_L the arrow keys\n"
"          move the window, PageUp/PageDn/Home/End resize it, and - and +\n"
"          raise and lower it.  Key 'M' or Button1 moves the remote window\n"
"          to the +X+Y of the viewer window.  Key 'D' or Button3 deletes\n"
"          the remote window.\n"
"\n"
"          You can run the SSVNC vncviewer with options '-escape default',\n"
"          '-multilisten' and '-env VNCVIEWER_MIN_TITLE=1'; or just run\n"
"          with option '-appshare' to enable these and automatic placement.\n"
"\n"
"     If any part of a window goes off of the display screen, then x11vnc\n"
"     may be unable to poll it (without crashing), and so the window will\n"
"     stop updating until the window is completely on-screen again.\n"
"\n"
"     The (stock) vnc viewer does not know where to best position each new\n"
"     viewer window; it likely centers each one (including when resized.)\n"
"     Note: The SSVNC viewer in '-appshare' mode places them correctly.\n"
"\n"
"     Deleting a viewer window does not delete the real window.\n"
"     Note: The SSVNC viewer Shift+EscapeKeys+Button3 deletes it.\n"
"\n"
"     Sometimes new window detection fails.\n"
"\n"
"     Sometimes menu/popup detection fails.\n"
"\n"
"     Sometimes the contents of a menu/popup window have blacked-out regions.\n"
"     Try -sid or -showmenus as a workaround.\n"
"\n"
"     If the application starts up a new application (a different process)\n"
"     that new application will not be tracked (but, unfortunately, it may\n"
"     cover up existing windows that are being tracked.) See cmd=add_window\n"
"     and cmd=add_app described above.\n"
"\n"
#endif
;

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define WMAX 192
#define CMAX 128
#define AMAX 32

static Window root = None;
static Window watch[WMAX];
static Window apps[WMAX];
static int state[WMAX];
static char *clients[CMAX];
static XWindowAttributes attr;
static char *ticker_atom_str = "X11VNC_APPSHARE_TICKER";
static Atom ticker_atom = None;
static char *cmd_atom_str = "X11VNC_APPSHARE_COMMAND";
static Atom cmd_atom = None;
static char *connect_to = NULL;
static char *x11vnc_args = "";
static char *id_opt = "-id";
static int skip_menus = 1;
static int exit_no_app_win = 1;
static int shell = 0;
static int tree_depth = 3;
static char *prompt = "appshare> ";
static char *x11vnc = "x11vnc";
static char *control = NULL;
static char *trackdir = "unset";
static char *trackpre = "/tmp/x11vnc-appshare-trackdir-tmp";
static char *tracktmp = NULL;
static char unique_tag[100];
static int use_forever = 1;
static int last_event_type = 0;
static pid_t helper_pid = 0;
static pid_t parent_pid = 0;
static double helper_delay = 0.5;
static int appshare_debug = 0;
static double start_time = 0.0;

static void get_wm_name(Window win, char **name);
static int win_attr(Window win);
static int get_xy(Window win, int *x, int *y);
static Window check_inside(Window win);
static int ours(Window win);
static void destroy_win(Window win);
static int same_app(Window win, Window app);

static void ff(void) {
	fflush(stdout);
	fflush(stderr);
}

static int find_win(Window win) {
	int i;
	for (i=0; i < WMAX; i++) {
		if (watch[i] == win) {
			return i;
		}
	}
	return -1;
}

static int find_app(Window app) {
	int i;
	for (i=0; i < AMAX; i++) {
		if (apps[i] == app) {
			return i;
		}
	}
	return -1;
}

static int find_client(char *cl) {
	int i;
	for (i=0; i < CMAX; i++) {
		if (cl == NULL) {
			if (clients[i] == NULL) {
				return i;
			}
			continue;
		}
		if (clients[i] == NULL) {
			continue;
		}
		if (!strcmp(clients[i], cl)) {
			return i;
		}
	}
	return -1;
}

static int trackdir_pid(Window win) {
	FILE *f;
	int ln = 0, pid = 0;
	char line[1024];

	if (!trackdir) {
		return 0;
	}
	sprintf(tracktmp, "%s/0x%lx.log", trackdir, win);
	f = fopen(tracktmp, "r");
	if (!f) {
		return 0;
	}
	while (fgets(line, sizeof(line), f) != NULL) {
		if (ln++ > 30) {
			break;
		}
		if (strstr(line, "x11vnc version:")) {
			char *q = strstr(line, "pid:");
			if (q) {
				int p;
				if (sscanf(q, "pid: %d", &p) == 1) {
					if (p > 0) {
						pid = p;
						break;
					}
				}
			}
		}
	}
	fclose(f);
	return pid;
}

static void trackdir_cleanup(Window win) {
	char *suffix[] = {"log", "connect", NULL};
	int i=0;
	if (!trackdir) {
		return;
	}
	while (suffix[i] != NULL) {
		sprintf(tracktmp, "%s/0x%lx.%s", trackdir, win, suffix[i]);
		if (appshare_debug && !strcmp(suffix[i], "log")) {
			fprintf(stderr, "keeping:  %s\n", tracktmp);
			ff();
		} else {
			if (appshare_debug) {
				fprintf(stderr, "removing: %s\n", tracktmp);
				ff();
			}
			unlink(tracktmp);
		}
		i++;
	}
}

static void launch(Window win) {
	char *cmd, *tmp, *connto, *name;
	int len, timeo = 30, uf = use_forever;
	int w = 0, h = 0, x = 0, y = 0;

	if (win_attr(win)) {
		/* maybe switch to debug only. */
		w = attr.width;
		h = attr.height;
		get_xy(win, &x, &y);
	}

	get_wm_name(win, &name);

	if (strstr(x11vnc_args, "-once")) {
		uf = 0;
	}

	if (control) {
		int i = 0;
		len = 0;
		for (i=0; i < CMAX; i++) {
			if (clients[i] != NULL) {
				len += strlen(clients[i]) + 2;
			}
		}
		connto = (char *) calloc(len, 1);
		for (i=0; i < CMAX; i++) {
			if (clients[i] != NULL) {
				if (connto[0] != '\0') {
					strcat(connto, ",");
				}
				strcat(connto, clients[i]);
			}
		}
	} else {
		connto = strdup(connect_to);
	}
	if (!strcmp(connto, "")) {
		timeo = 0;
	}
	if (uf) {
		timeo = 0;
	}
	
	len = 1000 + strlen(x11vnc) + strlen(connto) + strlen(x11vnc_args)
	    + 3 * (trackdir ? strlen(trackdir) : 100);

	cmd = (char *) calloc(len, 1);
	tmp = (char *) calloc(len, 1);

	sprintf(cmd, "%s %s 0x%lx -bg -quiet %s -nopw -rfbport 0 "
	    "-timeout %d -noxdamage -noxinerama -norc -repeat -speeds dsl "
	    "-env X11VNC_AVOID_WINDOWS=never -env X11VNC_APPSHARE_ACTIVE=1 "
	    "-env X11VNC_NO_CHECK_PM=1 -env %s -novncconnect -shared -nonap "
	    "-remote_prefix X11VNC_APPSHARE_CMD:",
	    x11vnc, id_opt, win, use_forever ? "-forever" : "-once", timeo, unique_tag);

	if (trackdir) {
		FILE *f;
		sprintf(tracktmp, " -noquiet -o %s/0x%lx.log", trackdir, win);
		strcat(cmd, tracktmp);
		sprintf(tracktmp, "%s/0x%lx.connect", trackdir, win);
		f = fopen(tracktmp, "w");
		if (f) {
			fprintf(f, "%s", connto);
			fclose(f);
			sprintf(tmp, " -connect_or_exit '%s'", tracktmp);
			strcat(cmd, tmp);
		} else {
			sprintf(tmp, " -connect_or_exit '%s'", connto);
			strcat(cmd, tmp);
		}
	} else {
		if (!strcmp(connto, "")) {
			sprintf(tmp, " -connect '%s'", connto);
		} else {
			sprintf(tmp, " -connect_or_exit '%s'", connto);
		}
		strcat(cmd, tmp);
	}
	if (uf) {
		char *q = strstr(cmd, "-connect_or_exit");
		if (q) q = strstr(q, "_or_exit");
		if (q) {
			unsigned int i;
			for (i=0; i < strlen("_or_exit"); i++) {
				*q = ' ';
				q++;
			}
		}
	}

	strcat(cmd, " ");
	strcat(cmd, x11vnc_args);

	fprintf(stdout, "launching: x11vnc for window 0x%08lx %dx%d+%d+%d \"%s\"\n",
	    win, w, h, x, y, name);

	if (appshare_debug) {
		fprintf(stderr, "\nrunning:   %s\n\n", cmd);
	}
	ff();

	system(cmd);

	free(cmd);
	free(tmp);
	free(connto);
	free(name);
}

static void stop(Window win) {
	char *cmd;
	int pid = -1;
	int f = find_win(win);
	if (f < 0 || win == None) {
		return;
	}
	if (state[f] == 0) {
		return;
	}
	if (trackdir) {
		pid = trackdir_pid(win);
		if (pid > 0) {
			if (appshare_debug) {fprintf(stderr,
			    "sending SIGTERM to: %d\n", pid); ff();}
			kill((pid_t) pid, SIGTERM);
		}
	}

	cmd = (char *) malloc(1000 + strlen(x11vnc));
	sprintf(cmd, "pkill -TERM -f '%s %s 0x%lx -bg'", x11vnc, id_opt, win);
	if (appshare_debug) {
		fprintf(stdout, "stopping:  0x%08lx - %s\n", win, cmd);
	} else {
		fprintf(stdout, "stopping:  x11vnc for window 0x%08lx  "
		    "(pid: %d)\n", win, pid);
	}
	ff();
	system(cmd);

	sprintf(cmd, "(sleep 0.25 2>/dev/null || sleep 1; pkill -KILL -f '%s "
	    "%s 0x%lx -bg') &", x11vnc, id_opt, win);
	system(cmd);

	if (trackdir) {
		trackdir_cleanup(win);
	}

	free(cmd);
}

static void kill_helper_pid(void) {
	int status;
	if (helper_pid <= 0) {
		return;
	}
	fprintf(stderr, "stopping: helper_pid: %d\n", (int) helper_pid);
	kill(helper_pid, SIGTERM);
	usleep(50 * 1000);
	kill(helper_pid, SIGKILL);
	usleep(25 * 1000);
#if HAVE_SYS_WAIT_H && HAVE_WAITPID 
	waitpid(helper_pid, &status, WNOHANG); 
#endif
}

static void be_helper_pid(char *dpy_str) {
	int cnt = 0;
	int ms = (int) (1000 * helper_delay);
	double last_check = 0.0;

	if (ms < 50) ms = 50;

#if NO_X11
	fprintf(stderr, "be_helper_pid: not compiled with X11.\n");
#else
	dpy = XOpenDisplay(dpy_str);
	ticker_atom = XInternAtom(dpy, ticker_atom_str, False);

	while (1) {
		char tmp[32];
		sprintf(tmp, "HELPER_CNT_%08d", cnt++);
		XChangeProperty(dpy, DefaultRootWindow(dpy), ticker_atom, XA_STRING, 8,
		    PropModeReplace, (unsigned char *) tmp, strlen(tmp));
		XFlush(dpy);
		usleep(ms*1000);
		if (parent_pid > 0) {
			if(dnow() > last_check + 1.0) {
				last_check = dnow();
				if (kill(parent_pid, 0) != 0) {
					fprintf(stderr, "be_helper_pid: parent %d is gone.\n", (int) parent_pid);
					break;
				}
			}
		}
	}
#endif
	exit(0);
}

static void print_logs(void) {
	if (trackdir) {
		DIR *dir = opendir(trackdir);
		if (dir) {
			struct dirent *dp;
			while ( (dp = readdir(dir)) != NULL) {
				FILE *f;
				char *name = dp->d_name;
				if (!strcmp(name, ".") || !strcmp(name, "..")) {
					continue;
				}
				if (strstr(name, "0x") != name) {
					continue;
				}
				if (strstr(name, ".log") == NULL) {
					continue;
				}
				sprintf(tracktmp, "%s/%s", trackdir, name);
				f = fopen(tracktmp, "r");
				if (f) {
					char line[1024];
					fprintf(stderr, "===== x11vnc log %s =====\n", tracktmp);
					while (fgets(line, sizeof(line), f) != NULL) {
						fprintf(stderr, "%s", line);
					}
					fprintf(stderr, "\n");
					ff();
					fclose(f);
				}
			}
			closedir(dir);
		}
	}
}

static void appshare_cleanup(int s) {
	int i;
	if (s) {}

	if (use_forever) {
		/* launch this backup in case they kill -9 us before we terminate everything */
		char cmd[1000];
		sprintf(cmd, "(sleep 3; pkill -TERM -f '%s') &", unique_tag);
		if (appshare_debug) fprintf(stderr, "%s\n", cmd);
		system(cmd);
	}

	for (i=0; i < WMAX; i++) {
		if (watch[i] != None) {
			stop(watch[i]);
		}
	}

	if (trackdir) {
		DIR *dir = opendir(trackdir);
		if (dir) {
			struct dirent *dp;
			while ( (dp = readdir(dir)) != NULL) {
				char *name = dp->d_name;
				if (!strcmp(name, ".") || !strcmp(name, "..")) {
					continue;
				}
				if (strstr(name, "0x") != name) {
					fprintf(stderr, "skipping: %s\n", name);
					continue;
				}
				if (!appshare_debug) {
					fprintf(stderr, "removing: %s\n", name);
					sprintf(tracktmp, "%s/%s", trackdir, name);
					unlink(tracktmp);
				} else {
					if (appshare_debug) fprintf(stderr, "keeping:  %s\n", name);
				}
			}
			closedir(dir);
		}
		if (!appshare_debug) {
			if (strstr(trackdir, trackpre) == trackdir) {
				if (appshare_debug) fprintf(stderr, "removing: %s\n", trackdir);
				rmdir(trackdir);
			}
		}
		ff();
	}

	kill_helper_pid();
			
#if !NO_X11
	XCloseDisplay(dpy);
#endif
	fprintf(stdout, "done.\n");
	ff();
	exit(0);
}

static int trap_xerror(Display *d, XErrorEvent *error) {
	if (d || error) {}
	return 0;
}

#if 0
typedef struct {
    int x, y;                   /* location of window */
    int width, height;          /* width and height of window */
    int border_width;           /* border width of window */
    int depth;                  /* depth of window */
    Visual *visual;             /* the associated visual structure */
    Window root;                /* root of screen containing window */
    int class;                  /* InputOutput, InputOnly*/
    int bit_gravity;            /* one of bit gravity values */
    int win_gravity;            /* one of the window gravity values */
    int backing_store;          /* NotUseful, WhenMapped, Always */
    unsigned long backing_planes;/* planes to be preserved if possible */
    unsigned long backing_pixel;/* value to be used when restoring planes */
    Bool save_under;            /* boolean, should bits under be saved? */
    Colormap colormap;          /* color map to be associated with window */
    Bool map_installed;         /* boolean, is color map currently installed*/
    int map_state;              /* IsUnmapped, IsUnviewable, IsViewable */
    long all_event_masks;       /* set of events all people have interest in*/
    long your_event_mask;       /* my event mask */
    long do_not_propagate_mask; /* set of events that should not propagate */
    Bool override_redirect;     /* boolean value for override-redirect */
    Screen *screen;             /* back pointer to correct screen */
} XWindowAttributes;
#endif

static void get_wm_name(Window win, char **name) {
	int ok;

#if !NO_X11
        XErrorHandler old_handler = XSetErrorHandler(trap_xerror);
	ok = XFetchName(dpy, win, name);
       	XSetErrorHandler(old_handler);
#endif

	if (!ok || *name == NULL) {
		*name = strdup("unknown");
	}
}

static int win_attr(Window win) {
	int ok = 0;
#if !NO_X11
        XErrorHandler old_handler = XSetErrorHandler(trap_xerror);
	ok = XGetWindowAttributes(dpy, win, &attr);
       	XSetErrorHandler(old_handler);
#endif

	if (ok) {
		return 1;
	} else {
		return 0;
	}
}

static void win_select(Window win, int ignore) {
#if !NO_X11
        XErrorHandler old_handler = XSetErrorHandler(trap_xerror);
	if (ignore) {
		XSelectInput(dpy, win, 0);
	} else {
		XSelectInput(dpy, win, SubstructureNotifyMask);
	}
	XSync(dpy, False);
       	XSetErrorHandler(old_handler);
#endif
}

static Window get_parent(Window win) {
	int ok;
	Window r, parent = None, *list = NULL;
	unsigned int nchild;

#if !NO_X11
        XErrorHandler old_handler = XSetErrorHandler(trap_xerror);
	ok = XQueryTree(dpy, win, &r, &parent, &list, &nchild);
       	XSetErrorHandler(old_handler);

	if (!ok) {
		return None;
	}
	if (list) {
		XFree(list);
	}
#endif
	return parent;
}

static int get_xy(Window win, int *x, int *y) {
	Window cr;
	Bool rc = False; 
#if !NO_X11
	XErrorHandler old_handler = XSetErrorHandler(trap_xerror);

	rc = XTranslateCoordinates(dpy, win, root, 0, 0, x, y, &cr);
       	XSetErrorHandler(old_handler);
#endif

	if (!rc) {
		return 0;
	} else {
		return 1;
	}
}

static Window check_inside(Window win) {
	int i, nwin = 0;
	int w, h, x, y;
	int Ws[WMAX], Hs[WMAX], Xs[WMAX], Ys[WMAX];
	Window wins[WMAX];

	if (!win_attr(win)) {
		return None; 
	}

	/* store them first to give the win app more time to settle.  */
	for (i=0; i < WMAX; i++) {
		int X, Y;
		Window wchk = watch[i];
		if (wchk == None) {
			continue;
		}
		if (state[i] == 0) {
			continue;
		}
		if (!win_attr(wchk)) {
			continue;
		}
		if (!get_xy(wchk, &X, &Y)) {
			continue;
		}

		Xs[nwin] = X;
		Ys[nwin] = Y;
		Ws[nwin] = attr.width;
		Hs[nwin] = attr.height;
		wins[nwin] = wchk;
		nwin++;
	}

	if (nwin == 0) {
		return None;
	}

	if (!win_attr(win)) {
		return None; 
	}
	w = attr.width;
	h = attr.height;

	get_xy(win, &x, &y);
	if (!get_xy(win, &x, &y)) {
		return None;
	}

	for (i=0; i < nwin; i++) {
		int X, Y, W, H;
		Window wchk = wins[i];
		X = Xs[i];
		Y = Ys[i];
		W = Ws[i];
		H = Hs[i];

		if (appshare_debug) fprintf(stderr, "check inside: 0x%lx  %dx%d+%d+%d %dx%d+%d+%d\n", wchk, w, h, x, y, W, H, X, Y);

		if (X <= x && Y <= y) {
			if (x + w  <= X + W && y + h < Y + H) {
				return wchk;
			}
		}
	}

	return None;
}

static void add_win(Window win) {
	int idx  = find_win(win);
	int free = find_win(None);
	if (idx >= 0) {
		if (appshare_debug) {fprintf(stderr, "already watching window: 0x%lx\n", win); ff();}
		return;
	}
	if (free < 0) {
		fprintf(stderr, "ran out of slots for window: 0x%lx\n", win); ff();
		return;
	}

	if (appshare_debug) {fprintf(stderr, "watching: 0x%lx at %d\n", win, free); ff();}

	watch[free] = win;
	state[free] = 0;

	win_select(win, 0);
}

static void delete_win(Window win) {
	int i;
	for (i=0; i < WMAX; i++) {
		if (watch[i] == win) {
			watch[i] = None;
			state[i] = 0;
			if (appshare_debug) {fprintf(stderr, "deleting: 0x%lx at %d\n", win, i); ff();}
		}
	}
}

static void recurse_search(int level, int level_max, Window top, Window app, int *nw) {
	Window w, r, parent, *list = NULL;
	unsigned int nchild;
	int ok = 0;

	if (appshare_debug > 1) {
		fprintf(stderr, "level: %d level_max: %d  top: 0x%lx  app: 0x%lx\n", level, level_max, top, app);
	}
	if (level >= level_max) {
		return;
	}
	
#if !NO_X11
	ok = XQueryTree(dpy, top, &r, &parent, &list, &nchild);
	if (ok) {
		int i;
		for (i=0; i < (int) nchild; i++) {
			w = list[i];
			if (w == None || find_win(w) >= 0) {
				continue;
			}
			if (ours(w) && w != app) {
				if (appshare_debug) fprintf(stderr, "add level %d 0x%lx %d/%d\n",
				    level, w, i, nchild);
				add_win(w);
				(*nw)++;
			}
		}
		for (i=0; i < (int) nchild; i++) {
			w = list[i];
			if (w == None || ours(w)) {
				continue;
			} 
			recurse_search(level+1, level_max, w, app, nw);
		}
	}
	if (list) {
		XFree(list);
	}
#endif
}
		
static void add_app(Window app) {
	int i, nw = 0, free = -1;
        XErrorHandler old_handler;

#if !NO_X11
	i = find_app(app);
	if (i >= 0) {
		fprintf(stderr, "already tracking app: 0x%lx\n", app);
		return;
	}
	for (i=0; i < AMAX; i++) {
		if (same_app(apps[i], app)) {
			fprintf(stderr, "already tracking app: 0x%lx via 0x%lx\n", app, apps[i]);
			return;
		}
	}
	free = find_app(None);
	if (free < 0) {
		fprintf(stderr, "ran out of app slots.\n");
		return;
	}
	apps[free] = app;

	add_win(app);

        old_handler = XSetErrorHandler(trap_xerror);
	recurse_search(0, tree_depth, root, app, &nw);
       	XSetErrorHandler(old_handler);
#endif
	fprintf(stderr, "tracking %d windows related to app window 0x%lx\n", nw, app);
}

static void del_app(Window app) {
	int i;
	for (i=0; i < WMAX; i++) {
		Window win = watch[i];
		if (win != None) {
			if (same_app(app, win)) {
				destroy_win(win);
			}
		}
	}
	for (i=0; i < AMAX; i++) {
		Window app2 = apps[i];
		if (app2 != None) {
			if (same_app(app, app2)) {
				apps[i] = None;
			}
		}
	}
}

static void wait_until_empty(char *file) {
	double t = 0.0, dt = 0.05;
	while (t < 1.0) {
		struct stat sb;
		if (stat(file, &sb) != 0) {
			return;
		}
		if (sb.st_size == 0) {
			return;
		}
		t += dt;
		usleep( (int) (dt * 1000 * 1000) );
	}
}

static void client(char *client, int add) {
	DIR *dir;
	struct dirent *dp;

	if (!client) {
		return;
	}
	if (!trackdir) {
		fprintf(stderr, "no trackdir, cannot %s client: %s\n",
		    add ? "add" : "disconnect", client);
		ff();
		return;
	}
	fprintf(stdout, "%s client: %s\n", add ? "adding  " : "deleting", client);

	dir = opendir(trackdir);
	if (!dir) {
		fprintf(stderr, "could not opendir trackdir: %s\n", trackdir);
		return;
	}
	while ( (dp = readdir(dir)) != NULL) {
		char *name = dp->d_name;
		if (!strcmp(name, ".") || !strcmp(name, "..")) {
			continue;
		}
		if (strstr(name, "0x") != name) {
			continue;
		}
		if (strstr(name, ".connect")) {
			FILE *f;
			char *tmp;
			Window twin;

			if (scan_hexdec(name, &twin)) {
				int f = find_win(twin);
				if (appshare_debug) {
					fprintf(stderr, "twin: 0x%lx name=%s f=%d\n", twin, name, f);
					ff();
				}
				if (f < 0) {
					continue;
				}
			}

			tmp = (char *) calloc(100 + strlen(client), 1);
			sprintf(tracktmp, "%s/%s", trackdir, name);
			if (add) {
				sprintf(tmp, "%s\n", client);
			} else {
				sprintf(tmp, "cmd=close:%s\n", client);
			}
			wait_until_empty(tracktmp);
			f = fopen(tracktmp, "w");
			if (f) {
				if (appshare_debug) {
					fprintf(stderr, "%s client: %s + %s",
					add ? "add" : "disconnect", tracktmp, tmp);
					ff();
				}
				fprintf(f, "%s", tmp);
				fclose(f);
			}
			free(tmp);
		}
	}
	closedir(dir);
}

static void mapped(Window win) {
	int f;
	if (win == None) {
		return;
	}
	f = find_win(win);
	if (f < 0) {
		if (win_attr(win)) {
			if (get_parent(win) == root) {
				/* XXX more cases? */
				add_win(win);
			}
		}
	}
}

static void unmapped(Window win) {
	int f = find_win(win);
	if (f < 0 || win == None) {
		return;
	}
	stop(win);	
	state[f] = 0;
}

static void destroy_win(Window win) {
	stop(win);
	delete_win(win);
}

static Window parse_win(char *str) {
	Window win = None;
	if (!str) {
		return None;
	}
	if (!strcmp(str, "pick") || !strcmp(str, "p")) {
		static double last_pick = 0.0;
		if (dnow() < start_time + 15) {
			;
		} else if (dnow() < last_pick + 2) {
			return None;
		} else {
			last_pick = dnow();
		}
		if (!pick_windowid(&win)) {
			fprintf(stderr, "parse_win: bad window pick.\n");
			win = None;
		}
		if (win == root) {
			fprintf(stderr, "parse_win: ignoring pick of rootwin 0x%lx.\n", win);
			win = None;
		}
		ff();
	} else if (!scan_hexdec(str, &win)) {
		win = None;
	}
	return win;
}

static void add_or_del_app(char *str, int add) {
	Window win = parse_win(str);

	if (win != None) {
		if (add) {
			add_app(win);
		} else {
			del_app(win);
		}
	} else if (!strcmp(str, "all")) {
		if (!add) {
			int i;
			for (i=0; i < AMAX; i++) {
				if (apps[i] != None) {
					del_app(apps[i]);
				}
			}
		}
	}
}

static void add_or_del_win(char *str, int add) {
	Window win = parse_win(str);

	if (win != None) {
		int f = find_win(win);
		if (add) {
			if (f < 0 && win_attr(win)) {
				add_win(win);
			}
		} else {
			if (f >= 0) {
				destroy_win(win);
			}
		}
	} else if (!strcmp(str, "all")) {
		if (!add) {
			int i;
			for (i=0; i < WMAX; i++) {
				if (watch[i] != None) {
					destroy_win(watch[i]);
				}
			}
		}
	} 
}

static void add_or_del_client(char *str, int add) {
	int i;

	if (!str) {
		return;
	}
	if (strcmp(control, "internal")) {
		return;
	}
	if (add) {
		int idx  = find_client(str);
		int free = find_client(NULL);

		if (idx >=0) {
			fprintf(stderr, "already tracking client: %s in slot %d\n", str, idx);
			ff();
			return;
		}
		if (free < 0) {
			static int cnt = 0;
			if (cnt++ < 10) {
				fprintf(stderr, "ran out of client slots.\n");
				ff();
			}
			return;
		}
		clients[free] = strdup(str);
		client(str, 1);
	} else {
		if (str[0] == '#' || str[0] == '%') {
			if (sscanf(str+1, "%d", &i) == 1) {
				i--;
				if (0 <= i && i < CMAX) {
					if (clients[i] != NULL) {
						client(clients[i], 0);
						free(clients[i]);
						clients[i] = NULL;
						return;
					}
				}
			}
		} else if (!strcmp(str, "all")) {
			for (i=0; i < CMAX; i++) {
				if (clients[i] == NULL) {
					continue;
				}
				client(clients[i], 0);
				free(clients[i]);
				clients[i] = NULL;
			}
			return;
		}

		i = find_client(str);
		if (i >= 0) {
			free(clients[i]);
			clients[i] = NULL;
			client(str, 0);
		}
	}
}

static void restart_x11vnc(void) {
	int i, n = 0;
	Window win, active[WMAX];
	for (i=0; i < WMAX; i++) {
		win = watch[i];
		if (win == None) {
			continue;
		}
		if (state[i]) {
			active[n++] = win;
			stop(win);
		}
	}
	if (n) {
		usleep(1500 * 1000);
	}
	for (i=0; i < n; i++) {
		win = active[i];
		launch(win);
	}
}

static unsigned long cmask = 0x3fc00000; /* 00111111110000000000000000000000 */

static void init_cmask(void) {
	/* dependent on the X server implementation; XmuClientWindow better? */
	/* xc/programs/Xserver/include/resource.h */
	int didit = 0, res_cnt = 29, client_bits = 8;

	if (getenv("X11VNC_APPSHARE_CLIENT_MASK")) {
		unsigned long cr;
		if (sscanf(getenv("X11VNC_APPSHARE_CLIENT_MASK"), "0x%lx", &cr) == 1) {
			cmask = cr;
			didit = 1;
		}
	} else if (getenv("X11VNC_APPSHARE_CLIENT_BITS")) {
		int cr = atoi(getenv("X11VNC_APPSHARE_CLIENT_BITS"));
		if (cr > 0) {
			client_bits = cr;
		}
	}
	if (!didit) {
		cmask = (((1 << client_bits) - 1) << (res_cnt-client_bits));
	}
	fprintf(stderr, "client_mask: 0x%08lx\n", cmask);
}

static int same_app(Window win, Window app) {
	if ( (win & cmask) == (app & cmask) ) {
		return 1;
	} else {
		return 0;
	}
}

static int ours(Window win) {
	int i;
	for (i=0; i < AMAX; i++) {
		if (apps[i] != None) {
			if (same_app(win, apps[i])) {
				return 1;
			}
		}
	}
	return 0;
}

static void list_clients(void) {
	int i, n = 0;
	for (i=0; i < CMAX; i++) {
		if (clients[i] == NULL) {
			continue;
		}
		fprintf(stdout, "client[%02d] %s\n", ++n, clients[i]);
	}
	fprintf(stdout, "total clients: %d\n", n);
	ff();
}

static void list_windows(void) {
	int i, n = 0;
	for (i=0; i < WMAX; i++) {
		char *name;
		Window win = watch[i];
		if (win == None) {
			continue;
		}
		get_wm_name(win, &name);
		fprintf(stdout, "window[%02d] 0x%08lx state: %d slot: %03d \"%s\"\n",
		    ++n, win, state[i], i, name);
		free(name);
	}
	fprintf(stdout, "total windows: %d\n", n);
	ff();
}

static void list_apps(void) {
	int i, n = 0;
	for (i=0; i < AMAX; i++) {
		char *name;
		Window win = apps[i];
		if (win == None) {
			continue;
		}
		get_wm_name(win, &name);
		fprintf(stdout, "app[%02d] 0x%08lx state: %d slot: %03d \"%s\"\n",
		    ++n, win, state[i], i, name);
		free(name);
	}
	fprintf(stdout, "total apps: %d\n", n);
	ff();
}

static int process_control(char *file, int check_clients) {
	int i, nnew = 0, seen[CMAX];
	char line[1024], *newctl[CMAX];
	FILE *f;

	f = fopen(file, "r");
	if (!f) {
		return 1;
	}
	if (check_clients) {
		for (i=0; i < CMAX; i++) {
			seen[i] = 0;
		}
	}
	while (fgets(line, sizeof(line), f) != NULL) {
		char *q = strchr(line, '\n');
		if (q) *q = '\0';

		if (appshare_debug) {
			fprintf(stderr, "check_control: %s\n", line);
			ff();
		}

		q = lblanks(line);
		if (q[0] == '#') {
			continue;
		}
		if (!strcmp(q, "")) {
			continue;
		}
		if (strstr(q, "cmd=") == q) {
			char *cmd = q + strlen("cmd=");
			if (!strcmp(cmd, "quit")) {
				if (strcmp(control, file) && strstr(file, ".cmd")) {
					FILE *f2 = fopen(file, "w");
					if (f2) fclose(f2);
				}
				appshare_cleanup(0);
			} else if (!strcmp(cmd, "wait")) {
				return 0;
			} else if (strstr(cmd, "bcast:") == cmd) {
				;
			} else if (strstr(cmd, "del_window:") == cmd) {
				add_or_del_win(cmd + strlen("del_window:"), 0);
			} else if (strstr(cmd, "add_window:") == cmd) {
				add_or_del_win(cmd + strlen("add_window:"), 1);
			} else if (strstr(cmd, "del:") == cmd) {
				add_or_del_win(cmd + strlen("del:"), 0);
			} else if (strstr(cmd, "add:") == cmd) {
				add_or_del_win(cmd + strlen("add:"), 1);
			} else if (strstr(cmd, "del_client:") == cmd) {
				add_or_del_client(cmd + strlen("del_client:"), 0);
			} else if (strstr(cmd, "add_client:") == cmd) {
				add_or_del_client(cmd + strlen("add_client:"), 1);
			} else if (strstr(cmd, "-") == cmd) {
				add_or_del_client(cmd + strlen("-"), 0);
			} else if (strstr(cmd, "+") == cmd) {
				add_or_del_client(cmd + strlen("+"), 1);
			} else if (strstr(cmd, "del_app:") == cmd) {
				add_or_del_app(cmd + strlen("del_app:"), 0);
			} else if (strstr(cmd, "add_app:") == cmd) {
				add_or_del_app(cmd + strlen("add_app:"), 1);
			} else if (strstr(cmd, "debug:") == cmd) {
				appshare_debug = atoi(cmd + strlen("debug:"));
			} else if (strstr(cmd, "showmenus:") == cmd) {
				skip_menus = atoi(cmd + strlen("showmenus:"));
				skip_menus = !(skip_menus);
			} else if (strstr(cmd, "noexit:") == cmd) {
				exit_no_app_win = atoi(cmd + strlen("noexit:"));
				exit_no_app_win = !(exit_no_app_win);
			} else if (strstr(cmd, "use_forever:") == cmd) {
				use_forever = atoi(cmd + strlen("use_forever:"));
			} else if (strstr(cmd, "tree_depth:") == cmd) {
				tree_depth = atoi(cmd + strlen("tree_depth:"));
			} else if (strstr(cmd, "x11vnc_args:") == cmd) {
				x11vnc_args = strdup(cmd + strlen("x11vnc_args:"));
			} else if (strstr(cmd, "env:") == cmd) {
				putenv(cmd + strlen("env:"));
			} else if (strstr(cmd, "noop") == cmd) {
				;
			} else if (!strcmp(cmd, "restart")) {
				restart_x11vnc();
			} else if (!strcmp(cmd, "list_clients") || !strcmp(cmd, "lc")) {
				list_clients();
			} else if (!strcmp(cmd, "list_windows") || !strcmp(cmd, "lw")) {
				list_windows();
			} else if (!strcmp(cmd, "list_apps") || !strcmp(cmd, "la")) {
				list_apps();
			} else if (!strcmp(cmd, "list_all") || !strcmp(cmd, "ls")) {
				list_windows();
				fprintf(stderr, "\n");
				list_apps();
				fprintf(stderr, "\n");
				list_clients();
			} else if (!strcmp(cmd, "print_logs") || !strcmp(cmd, "pl")) {
				print_logs();
			} else if (!strcmp(cmd, "?") || !strcmp(cmd, "h") || !strcmp(cmd, "help")) {
				fprintf(stderr, "available commands:\n");
				fprintf(stderr, "\n");
				fprintf(stderr, "   quit restart noop x11vnc help ? ! !!\n");
				fprintf(stderr, "\n");
				fprintf(stderr, "   add_window:win  (add:win, add:pick)\n");
				fprintf(stderr, "   del_window:win  (del:win, del:pick, del:all)\n");
				fprintf(stderr, "   add_app:win     (add_app:pick)\n");
				fprintf(stderr, "   del_app:win     (del_app:pick, del_app:all)\n");
				fprintf(stderr, "   add_client:host (+host)\n");
				fprintf(stderr, "   del_client:host (-host, -all)\n");
				fprintf(stderr, "\n");
				fprintf(stderr, "   list_windows    (lw)\n");
				fprintf(stderr, "   list_apps       (la)\n");
				fprintf(stderr, "   list_clients    (lc)\n");
				fprintf(stderr, "   list_all        (ls)\n");
				fprintf(stderr, "   print_logs      (pl)\n");
				fprintf(stderr, "\n");
				fprintf(stderr, "   debug:n   showmenus:n   noexit:n\n");
			} else {
				fprintf(stderr, "unrecognized %s\n", q);
			}
			continue;
		}
		if (check_clients) {
			int idx = find_client(q);
			if (idx >= 0) {
				seen[idx] = 1;
			} else {
				newctl[nnew++] = strdup(q);
			}
		}
	}
	fclose(f);

	if (check_clients) {
		for (i=0; i < CMAX; i++) {
			if (clients[i] == NULL) {
				continue;
			}
			if (!seen[i]) {
				client(clients[i], 0);
				free(clients[i]);
				clients[i] = NULL;
			}
		}
		for (i=0; i < nnew; i++) {
			int free = find_client(NULL);
			if (free < 0) {
				static int cnt = 0;
				if (cnt++ < 10) {
					fprintf(stderr, "ran out of client slots.\n");
					ff();
					break;
				}
				continue;
			}
			clients[free] = newctl[i];
			client(newctl[i], 1);
		}
	}
	return 1;
}

static int check_control(void) {
	static int last_size = -1;
	static time_t last_mtime = 0;
	struct stat sb;
	char *control_cmd;

	if (!control) {
		return 1;
	}

	if (!strcmp(control, "internal")) {
		return 1;
	}
		
	control_cmd = (char *)malloc(strlen(control) + strlen(".cmd") + 1);
	sprintf(control_cmd, "%s.cmd", control);
	if (stat(control_cmd, &sb) == 0) {
		FILE *f;
		if (sb.st_size > 0) {
			process_control(control_cmd, 0);
		}
		f = fopen(control_cmd, "w");
		if (f) {
			fclose(f);
		}
	}
	free(control_cmd);

	if (stat(control, &sb) != 0) {
		return 1;
	}
	if (last_size == (int) sb.st_size && last_mtime == sb.st_mtime) {
		return 1;
	}
	last_size = (int) sb.st_size;
	last_mtime = sb.st_mtime;

	return process_control(control, 1);
}

static void update(void) {
	int i, app_ok = 0;
	if (last_event_type != PropertyNotify) {
		if (appshare_debug) fprintf(stderr, "\nupdate ...\n");
	} else if (appshare_debug > 1) {
		fprintf(stderr, "update ... propertynotify\n");
	}
	if (!check_control()) {
		return;
	}
	for (i=0; i < WMAX; i++) {
		Window win = watch[i];
		if (win == None) {
			continue;
		}
		if (!win_attr(win)) {
			destroy_win(win);
			continue;
		}
		if (find_app(win) >= 0) {
			app_ok++;
		}
		if (state[i] == 0) {
			if (attr.map_state == IsViewable) {
				if (skip_menus) {
					Window inside = check_inside(win);
					if (inside != None) {
						if (appshare_debug) {fprintf(stderr, "skip_menus: window 0x%lx is inside of 0x%lx, not tracking it.\n", win, inside); ff();}
						delete_win(win);
						continue;
					}
				}
				launch(win);
				state[i] = 1;
			}
		} else if (state[i] == 1) {
			if (attr.map_state != IsViewable) {
				stop(win);
				state[i] = 0;
			}
		}
	}
	if (exit_no_app_win && !app_ok) {
		for (i=0; i < AMAX; i++) {
			if (apps[i] != None) {
				fprintf(stdout, "main application window is gone: 0x%lx\n", apps[i]);
			}
		}
		ff();
		appshare_cleanup(0);
	}
	if (last_event_type != PropertyNotify) {
		if (appshare_debug) {fprintf(stderr, "update done.\n"); ff();}
	}
}

static void exiter(char *msg, int rc) {
	fprintf(stderr, "%s", msg);
	ff();
	kill_helper_pid();
	exit(rc);
}

static void set_trackdir(void) {
	char tmp[256];
	struct stat sb;
	if (!strcmp(trackdir, "none")) {
		trackdir = NULL;
		return;
	}
	if (!strcmp(trackdir, "unset")) {
		int fd;
		sprintf(tmp, "%s.XXXXXX", trackpre);
		fd = mkstemp(tmp);
		if (fd < 0) {
			strcat(tmp, ": failed to create file.\n");
			exiter(tmp, 1);
		}
		/* XXX race */
		close(fd);
		unlink(tmp);
		if (mkdir(tmp, 0700) != 0) {
			strcat(tmp, ": failed to create dir.\n");
			exiter(tmp, 1);
		}
		trackdir = strdup(tmp);
	}
	if (stat(trackdir, &sb) != 0) {
		if (mkdir(trackdir, 0700) != 0) {
			exiter("could not make trackdir.\n", 1);
		}
	} else if (! S_ISDIR(sb.st_mode)) {
		exiter("trackdir not a directory.\n", 1);
	}
	tracktmp = (char *) calloc(1000 + strlen(trackdir), 1);
}

static void process_string(char *str) {
	FILE *f;
	char *file;
	if (trackdir) {
		sprintf(tracktmp, "%s/0xprop.cmd", trackdir);
		file = strdup(tracktmp);
	} else {
		char tmp[] = "/tmp/x11vnc-appshare.cmd.XXXXXX";
		int fd = mkstemp(tmp);
		if (fd < 0) {
			return;
		}
		file = strdup(tmp);
		close(fd);
	}
	f = fopen(file, "w");
	if (f) {
		fprintf(f, "%s", str);
		fclose(f);
		process_control(file, 0);
	}
	unlink(file);
	free(file);
}

static void handle_shell(void) {
	struct timeval tv;
	static char lastline[1000];
	static int first = 1;
	fd_set rfds;
	int fd0 = fileno(stdin);

	if (first) {
		memset(lastline, 0, sizeof(lastline));
		first = 0;
	}

	FD_ZERO(&rfds);
	FD_SET(fd0, &rfds);
	tv.tv_sec = 0; 
	tv.tv_usec = 0; 
	select(fd0+1, &rfds, NULL, NULL, &tv);
	if (FD_ISSET(fd0, &rfds)) {
		char line[1000], line2[1010];
		if (fgets(line, sizeof(line), stdin) != NULL) {
			char *str = lblanks(line);
			char *q = strrchr(str, '\n');
			if (q) *q = '\0';
			if (strcmp(str, "")) {
				if (!strcmp(str, "!!")) {
					sprintf(line, "%s", lastline);
					fprintf(stderr, "%s\n", line);
					str = line;
				}
				if (strstr(str, "!") == str) {
					system(str+1);
				} else if (!strcmp(str, "x11vnc") || !strcmp(str, "ps")) {
					char *cmd = "ps -elf | egrep 'PID|x11vnc' | grep -v egrep";
					fprintf(stderr, "%s\n", cmd);
					system(cmd);
				} else {
					sprintf(line2, "cmd=%s", str);
					process_string(line2);
				}
				sprintf(lastline, "%s", str);
			}
		}
		fprintf(stderr, "\n%s", prompt); ff();
	}
}

static void handle_prop_cmd(void) {
	char *value, *str, *done = "DONE";

	if (cmd_atom == None) {
		return;
	}

	value = get_xprop(cmd_atom_str, root);
	if (value == NULL) {
		return;
	}

	str = lblanks(value);
	if (!strcmp(str, done)) {
		free(value);
		return;
	}
	if (strstr(str, "cmd=quit") == str || strstr(str, "\ncmd=quit")) {
		set_xprop(cmd_atom_str, root, done);
		appshare_cleanup(0);
	}

	process_string(str);

	free(value);
	set_xprop(cmd_atom_str, root, done);
}

#define PREFIX if(appshare_debug) fprintf(stderr, "  %8.2f  0x%08lx : ", dnow() - start, ev.xany.window);

static void monitor(void) {
#if !NO_X11
	XEvent ev;
	double start = dnow();
	int got_prop_cmd = 0;

	if (shell) {
		update();
		fprintf(stderr, "\n\n");
		process_string("cmd=help");
		fprintf(stderr, "\n%s", prompt); ff();
	}

	while (1) {
		int t;

		if (XEventsQueued(dpy, QueuedAlready) == 0) {
			update();
			if (got_prop_cmd) {
				handle_prop_cmd();
			}
			got_prop_cmd = 0;
			if (shell) {
				handle_shell();
			}
		}

		XNextEvent(dpy, &ev);

		last_event_type = ev.type;

		switch (ev.type) {
		case Expose:
			PREFIX
			if(appshare_debug) fprintf(stderr, "Expose %04dx%04d+%04d+%04d\n", ev.xexpose.width, ev.xexpose.height, ev.xexpose.x, ev.xexpose.y);
			break;
		case ConfigureNotify:
#if 0
			PREFIX
			if(appshare_debug) fprintf(stderr, "ConfigureNotify %04dx%04d+%04d+%04d  above: 0x%lx\n", ev.xconfigure.width, ev.xconfigure.height, ev.xconfigure.x, ev.xconfigure.y, ev.xconfigure.above);
#endif
			break;
		case VisibilityNotify:
			PREFIX
			if (appshare_debug) {
			fprintf(stderr, "VisibilityNotify: ");
			t = ev.xvisibility.state;
			if (t == VisibilityFullyObscured)     fprintf(stderr, "VisibilityFullyObscured\n");
			if (t == VisibilityPartiallyObscured) fprintf(stderr, "VisibilityPartiallyObscured\n");
			if (t == VisibilityUnobscured)        fprintf(stderr, "VisibilityUnobscured\n");
			}
			break;
		case MapNotify:
			PREFIX
			if(appshare_debug) fprintf(stderr, "MapNotify      win: 0x%lx\n", ev.xmap.window);
			if (ours(ev.xmap.window)) {
				mapped(ev.xmap.window);
			}
			break;
		case UnmapNotify:
			PREFIX
			if(appshare_debug) fprintf(stderr, "UnmapNotify    win: 0x%lx\n", ev.xmap.window);
			if (ours(ev.xmap.window)) {
				unmapped(ev.xmap.window);
			}
			break;
		case MapRequest:
			PREFIX
			if(appshare_debug) fprintf(stderr, "MapRequest\n");
			break;
		case CreateNotify:
			PREFIX
			if(appshare_debug) fprintf(stderr, "CreateNotify parent: 0x%lx  win: 0x%lx\n", ev.xcreatewindow.parent, ev.xcreatewindow.window);
			if (ev.xcreatewindow.parent == root && ours(ev.xcreatewindow.window)) {
				if (find_win(ev.xcreatewindow.window) >= 0) {
					destroy_win(ev.xcreatewindow.window);
				}
				add_win(ev.xcreatewindow.window);
			}
			break;
		case DestroyNotify:
			PREFIX
			if(appshare_debug) fprintf(stderr, "DestroyNotify  win: 0x%lx\n", ev.xdestroywindow.window);
			if (ours(ev.xdestroywindow.window)) {
				destroy_win(ev.xdestroywindow.window);
			}
			break;
		case ConfigureRequest:
			PREFIX
			if(appshare_debug) fprintf(stderr, "ConfigureRequest\n");
			break;
		case CirculateRequest:
#if 0
			PREFIX
			if(appshare_debug) fprintf(stderr, "CirculateRequest parent: 0x%lx  win: 0x%lx\n", ev.xcirculaterequest.parent, ev.xcirculaterequest.window);
#endif
			break;
		case CirculateNotify:
#if 0
			PREFIX
			if(appshare_debug) fprintf(stderr, "CirculateNotify\n");
#endif
			break;
		case PropertyNotify:
#if 0
			PREFIX
			if(appshare_debug) fprintf(stderr, "PropertyNotify\n");
#endif
			if (cmd_atom != None && ev.xproperty.atom == cmd_atom) {
				got_prop_cmd++;
			}
			break;
		case ReparentNotify:
			PREFIX
			if(appshare_debug) fprintf(stderr, "ReparentNotify parent: 0x%lx  win: 0x%lx\n", ev.xreparent.parent, ev.xreparent.window);
			if (ours(ev.xreparent.window)) {
				if (ours(ev.xreparent.parent)) {
					destroy_win(ev.xreparent.window);
				} else if (ev.xreparent.parent == root) {
					/* ??? */
				}
			}
			break;
		default:
			PREFIX
			if(appshare_debug) fprintf(stderr, "Unknown: %d\n", ev.type);
			break;
		}
	}
#endif
}

int appshare_main(int argc, char *argv[]) {
	int i;
	char *app_str = NULL;
	char *dpy_str = NULL;
	long xselectinput = 0;
#if NO_X11
	exiter("not compiled with X11\n", 1);
#else
	for (i=0; i < WMAX; i++) {
		watch[i] = None;
		state[i] = 0;
	}
	for (i=0; i < AMAX; i++) {
		apps[i]  = None;
	}
	for (i=0; i < CMAX; i++) {
		clients[i] = NULL;
	}

	x11vnc = strdup(argv[0]);

	for (i=1; i < argc; i++) {
		int end = (i == argc-1) ? 1 : 0;
		char *s = argv[i];
		if (strstr(s, "--") == s) {
			s++;
		}

		if (!strcmp(s, "-h") || !strcmp(s, "-help")) {
			fprintf(stdout, "%s", usage);
			exit(0);
		} else if (!strcmp(s, "-id")) {
			id_opt = "-id";
			if (end) exiter("no -id value supplied\n", 1);
			app_str = strdup(argv[++i]);
		} else if (!strcmp(s, "-sid")) {
			id_opt = "-sid";
			if (end) exiter("no -sid value supplied\n", 1);
			app_str = strdup(argv[++i]);
		} else if (!strcmp(s, "-connect") || !strcmp(s, "-connect_or_exit") || !strcmp(s, "-coe")) {
			if (end) exiter("no -connect value supplied\n", 1);
			connect_to = strdup(argv[++i]);
		} else if (!strcmp(s, "-control")) {
			if (end) exiter("no -control value supplied\n", 1);
			control = strdup(argv[++i]);
			if (!strcmp(control, "shell")) {
				free(control);
				control = strdup("internal");
				shell = 1;
			}
		} else if (!strcmp(s, "-trackdir")) {
			if (end) exiter("no -trackdir value supplied\n", 1);
			trackdir = strdup(argv[++i]);
		} else if (!strcmp(s, "-display")) {
			if (end) exiter("no -display value supplied\n", 1);
			dpy_str = strdup(argv[++i]);
			set_env("DISPLAY", dpy_str);
		} else if (!strcmp(s, "-delay")) {
			if (end) exiter("no -delay value supplied\n", 1);
			helper_delay = atof(argv[++i]);
		} else if (!strcmp(s, "-args")) {
			if (end) exiter("no -args value supplied\n", 1);
			x11vnc_args = strdup(argv[++i]);
		} else if (!strcmp(s, "-env")) {
			if (end) exiter("no -env value supplied\n", 1);
			putenv(argv[++i]);
		} else if (!strcmp(s, "-debug")) {
			appshare_debug++;
		} else if (!strcmp(s, "-showmenus")) {
			skip_menus = 0;
		} else if (!strcmp(s, "-noexit")) {
			exit_no_app_win = 0;
		} else if (!strcmp(s, "-shell")) {
			shell = 1;
		} else if (!strcmp(s, "-nocmds") || !strcmp(s, "-safer")) {
			fprintf(stderr, "ignoring %s in -appshare mode.\n", s);
		} else if (!strcmp(s, "-appshare")) {
			;
		} else {
			fprintf(stderr, "unrecognized 'x11vnc -appshare' option: %s\n", s);
			exiter("", 1);
		}
	}

	if (getenv("X11VNC_APPSHARE_DEBUG")) {
		appshare_debug = atoi(getenv("X11VNC_APPSHARE_DEBUG"));
	}

	/* let user override name for multiple instances: */
	if (getenv("X11VNC_APPSHARE_COMMAND_PROPNAME")) {
		cmd_atom_str = strdup(getenv("X11VNC_APPSHARE_COMMAND_PROPNAME"));
	}
	if (getenv("X11VNC_APPSHARE_TICKER_PROPNAME")) {
		ticker_atom_str = strdup(getenv("X11VNC_APPSHARE_TICKER_PROPNAME"));
	}

	if (shell) {
		if (!control || strcmp(control, "internal")) {
			exiter("mode -shell requires '-control internal'\n", 1);
		}
	}

	if (connect_to == NULL && control != NULL) {
		struct stat sb;
		if (stat(control, &sb) == 0) {
			int len = 100 + sb.st_size;
			FILE *f = fopen(control, "r");

			if (f) {
				char *line = (char *) malloc(len);
				connect_to = (char *) calloc(2 * len, 1);
				while (fgets(line, len, f) != NULL) {
					char *q = strchr(line, '\n');
					if (q) *q = '\0';
					q = lblanks(line);
					if (q[0] == '#') {
						continue;
					}
					if (connect_to[0] != '\0') {
						strcat(connect_to, ",");
					}
					strcat(connect_to, q);
				}
				fclose(f);
			}
			fprintf(stderr, "set -connect to: %s\n", connect_to);
		}
	}
	if (0 && connect_to == NULL && control == NULL) {
		exiter("no -connect host or -control file specified.\n", 1);
	}

	if (control) {
		pid_t pid;
		parent_pid = getpid();
		pid = fork();
		if (pid == (pid_t) -1) {
			;
		} else if (pid == 0) {
			be_helper_pid(dpy_str);
			exit(0);
		} else {
			helper_pid = pid;
		}
	}

	dpy = XOpenDisplay(dpy_str);
	if (!dpy) {
		exiter("cannot open display\n", 1);
	}

	root = DefaultRootWindow(dpy);

	xselectinput = SubstructureNotifyMask;
	if (helper_pid > 0) {
		ticker_atom = XInternAtom(dpy, ticker_atom_str, False);
		xselectinput |= PropertyChangeMask;
	}
	XSelectInput(dpy, root, xselectinput);

	cmd_atom = XInternAtom(dpy, cmd_atom_str, False);

	init_cmask();

	sprintf(unique_tag, "X11VNC_APPSHARE_TAG=%d-tag", getpid());

	start_time = dnow();

	if (app_str == NULL) {
		exiter("no -id/-sid window specified.\n", 1);
	} else {
		char *p, *str = strdup(app_str);
		char *alist[AMAX];
		int i, n = 0;

		p = strtok(str, ",");
		while (p) {
			if (n >= AMAX) {
				fprintf(stderr, "ran out of app slots: %s\n", app_str);
				exiter("", 1);
			}
			alist[n++] = strdup(p);
			p = strtok(NULL, ",");
		}
		free(str);

		for (i=0; i < n; i++) {
			Window app = None;
			p = alist[i];
			app = parse_win(p);
			free(p);

			if (app != None) {
				if (!ours(app)) {
					add_app(app);
				}
			}
		}
	}

	set_trackdir();

	signal(SIGINT,  appshare_cleanup);
	signal(SIGTERM, appshare_cleanup);

	rfbLogEnable(0);

	if (connect_to) {
		char *p, *str = strdup(connect_to);
		int n = 0;
		p = strtok(str, ",");
		while (p) {
			clients[n++] = strdup(p);
			p = strtok(NULL, ",");
		}
		free(str);
	} else {
		connect_to = strdup("");
	}

	for (i=0; i < AMAX; i++) {
		if (apps[i] == None) {
			continue;
		}
		fprintf(stdout, "Using app win: 0x%08lx  root: 0x%08lx\n", apps[i], root);
	}
	fprintf(stdout, "\n");

	monitor();

	appshare_cleanup(0);

#endif
	return 0;
}

