1/*
2   Copyright (C) 2002-2010 Karl J. Runge <runge@karlrunge.com>
3   All rights reserved.
4
5This file is part of x11vnc.
6
7x11vnc is free software; you can redistribute it and/or modify
8it under the terms of the GNU General Public License as published by
9the Free Software Foundation; either version 2 of the License, or (at
10your option) any later version.
11
12x11vnc is distributed in the hope that it will be useful,
13but WITHOUT ANY WARRANTY; without even the implied warranty of
14MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15GNU General Public License for more details.
16
17You should have received a copy of the GNU General Public License
18along with x11vnc; if not, write to the Free Software
19Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA
20or see <http://www.gnu.org/licenses/>.
21
22In addition, as a special exception, Karl J. Runge
23gives permission to link the code of its release of x11vnc with the
24OpenSSL project's "OpenSSL" library (or with modified versions of it
25that use the same license as the "OpenSSL" library), and distribute
26the linked executables.  You must obey the GNU General Public License
27in all respects for all of the code used other than "OpenSSL".  If you
28modify this file, you may extend this exception to your version of the
29file, but you are not obligated to do so.  If you do not wish to do
30so, delete this exception statement from your version.
31*/
32
33/* -- appshare.c -- */
34
35#include "x11vnc.h"
36
37extern int pick_windowid(unsigned long *num);
38extern char *get_xprop(char *prop, Window win);
39extern int set_xprop(char *prop, Window win, char *value);
40extern void set_env(char *name, char *value);
41extern double dnow(void);
42
43static char *usage =
44"\n"
45"  x11vnc -appshare: an experiment in application sharing via x11vnc.\n"
46"\n"
47#if !SMALL_FOOTPRINT
48"  Usage:   x11vnc -appshare -id windowid -connect viewer_host:0\n"
49"           x11vnc -appshare -id pick     -connect viewer_host:0\n"
50"\n"
51"  Both the -connect option and the -id (or -sid) option are required.\n"
52"  (However see the -control option below that can replace -connect.)\n"
53"\n"
54"  The VNC viewer at viewer_host MUST be in 'listen' mode.  This is because\n"
55"  a new VNC connection (and viewer window) is established for each new\n"
56"  toplevel window that the application creates.  For example:\n"
57"\n"
58"       vncviewer -listen 0\n"
59"\n"
60"  The '-connect viewer_host:0' indicates the listening viewer to connect to.\n"
61"\n"
62"  No password should be used, otherwise it will need to be typed for each\n"
63"  new window (or one could use vncviewer -passwd file if the viewer supports\n"
64"  that.)  For security an SSH tunnel can be used:\n"
65"\n"
66"       ssh -R 5500:localhost:5500 user@server_host\n"
67"\n"
68"  (then use -connect localhost:0)\n"
69"\n"
70"  The -id/-sid option is as in x11vnc(1).  It is either a numerical window\n"
71"  id or the string 'pick' which will ask the user to click on an app window.\n"
72"  To track more than one application at the same time, list their window ids\n"
73"  separated by commas (see also the 'add_app' command below.)\n"
74"\n"
75"  Additional options:\n"
76"\n"
77"      -h, -help      Print this help.\n"
78"      -debug         Print debugging output (same as X11VNC_APPSHARE_DEBUG=1)\n"
79"      -showmenus     Create a new viewer window even if a new window is\n"
80"                     completely inside of an existing one.  Default is to\n"
81"                     try to not show them in a new viewer window.\n"
82"      -noexit        Do not exit if the main app (windowid/pick) window\n"
83"                     goes away.  Default is to exit.\n"
84"      -display dpy   X DISPLAY to use.\n"
85"      -trackdir dir  Set tracking directory to 'dir'. x11vnc -appshare does\n"
86"                     better if it can communicate with the x11vnc's via a\n"
87"                     file channel. By default a dir in /tmp is used, -trackdir\n"
88"                     specifies another directory, or use 'none' to disable.\n"
89"      -args 'string' Pass options 'string' to x11vnc (e.g. -scale 3/4,\n"
90"                     -viewonly, -wait, -once, etc.)\n"
91"      -env VAR=VAL   Set environment variables on cmdline as in x11vnc.\n"
92"\n"
93"      -control file  This is a file that one edits to manage the appshare\n"
94"                     mode.  It replaces -connect.  Lines beginning with '#'\n"
95"                     are ignored.  Initially start off with all of the\n"
96"                     desired clients in the file, one per line.  If you add\n"
97"                     a new client-line, that client is connected to. If you\n"
98"                     delete (or comment out) a client-line, that client is\n"
99"                     disconnected (for this to work, do not disable trackdir.)\n"
100"\n"
101"                     You can also put cmd= lines in the control file to perform\n"
102"                     different actions.  These are supported:\n"
103"\n"
104"                         cmd=quit            Disconnect all clients and exit.\n"
105"                         cmd=restart         Restart all of the x11vnc's.\n"
106"                         cmd=noop            Do nothing (e.g. ping)\n"
107"                         cmd=x11vnc          Run ps(1) looking for x11vnc's\n"
108"                         cmd=help            Print out help text.\n"
109"                         cmd=add_window:win  Add a window to be watched.\n"
110"                         cmd=del_window:win  Delete a window.\n"
111"                         cmd=add_app:win     Add an application to be watched.\n"
112"                         cmd=del_app:win     Delete an application.\n"
113"                         cmd=add_client:host Add client ('internal' mode only)\n"
114"                         cmd=del_client:host Del client ('internal' mode only)\n"
115"                         cmd=list_windows    List all tracked windows.\n"
116"                         cmd=list_apps       List all tracked applications.\n"
117"                         cmd=list_clients    List all connected clients.\n"
118"                         cmd=list_all        List all three.\n"
119"                         cmd=print_logs      Print out the x11vnc logfiles.\n"
120"                         cmd=debug:n         Set -debug to n (0 or 1).\n"
121"                         cmd=showmenus:n     Set -showmenus to n (0 or 1).\n"
122"                         cmd=noexit:n        Set -noexit to n (0 or 1).\n"
123"\n"
124"                     See the '-command internal' mode described below for a way\n"
125"                     that tracks connected clients internally (not in a file.)\n"
126"\n"
127"                     In '-shell' mode (see below) you can type in the above\n"
128"                     without the leading 'cmd='.\n"
129"\n"
130"                     For 'add_window' and 'del_window' the 'win' can be a\n"
131"                     numerical window id or 'pick'.  Same for 'add_app'.  Be\n"
132"                     sure to remove or comment out the add/del line quickly\n"
133"                     (e.g. before picking) or it will be re-run the next time\n"
134"                     the file is processed.\n"
135"\n"
136"                     If a file with the same name as the control file but\n"
137"                     ending with suffix '.cmd' is found, then commands in it\n"
138"                     (cmd=...) are processed and then the file is truncated.\n"
139"                     This allows 'one time' command actions to be run.  Any\n"
140"                     client hostnames in the '.cmd' file are ignored.  Also\n"
141"                     see below for the X11VNC_APPSHARE_COMMAND X property\n"
142"                     which is similar to '.cmd'\n"
143"\n"
144"      -control internal   Manage connected clients internally, see below.\n"
145"      -control shell      Same as: -shell -control internal\n"
146"\n"
147"      -delay secs    Maximum timeout delay before re-checking the control file.\n"
148"                     It can be a fraction, e.g. -delay 0.25  Default 0.5\n"
149"\n"
150"      -shell         Simple command line for '-control internal' mode (see the\n"
151"                     details of this mode below.)  Enter '?' for command list.\n"
152"\n"
153"  To stop x11vnc -appshare press Ctrl-C, or (if -noexit not supplied) delete\n"
154"  the initial app window or exit the application. Or cmd=quit in -control mode.\n"
155"\n"
156#if 0
157"  If you want your setup to survive periods of time where there are no clients\n"
158"  connected you will need to supply -args '-forever' otherwise the x11vnc's\n"
159"  will exit when the last client disconnects.  Howerver, _starting_ with no\n"
160"  clients (e.g. empty control file) will work without -args '-forever'.\n"
161"\n"
162#endif
163"  In addition to the '.cmd' file channel, for faster response you can set\n"
164"  X11VNC_APPSHARE_COMMAND X property on the root window to the string that\n"
165"  would go into the '.cmd' file.  For example:\n"
166"\n"
167" xprop -root -f X11VNC_APPSHARE_COMMAND 8s -set X11VNC_APPSHARE_COMMAND cmd=quit\n"
168"\n"
169"  The property value will be set to 'DONE' after the command(s) is processed.\n"
170"\n"
171"  If -control file is specified as 'internal' then no control file is used\n"
172"  and client tracking is done internally.  You must add and delete clients\n"
173"  with the cmd=add_client:<client> and cmd=del_client:<client> commands.\n"
174"  Note that '-control internal' is required for '-shell' mode.  Using\n"
175"  '-control shell' implies internal mode and -shell.\n"
176"\n"
177"  Limitations:\n"
178"\n"
179"     This is a quick lash-up, many things will not work properly.\n"
180"\n"
181"     The main idea is to provide simple application sharing for two or more\n"
182"     parties to collaborate without needing to share the entire desktop.  It\n"
183"     provides an improvement over -id/-sid that only shows a single window.\n"
184"\n"
185"     Only reverse connections can be done.  (Note: one can specify multiple\n"
186"     viewing hosts via: -connect host1,host2,host3 or add/remove them\n"
187"     dynamically as described above.)\n"
188"\n"
189"     If a new window obscures an old one, you will see some or all of the\n"
190"     new window in the old one.  The hope is this is a popup dialog or menu\n"
191"     that will go away soon.  Otherwise a user at the physical display will\n"
192"     need to move it. (See also the SSVNC viewer features described below.) \n"
193"\n"
194"     The viewer side cannot resize or make windows move on the physical\n"
195"     display.  Again, a user at the physical display may need to help, or\n"
196"     use the SSVNC viewer (see Tip below.)\n"
197"\n"
198"     Tip: If the application has its own 'resize corner', then dragging\n"
199"          it may successfully resize the application window.\n"
200"     Tip: Some desktop environments enable moving a window via, say,\n"
201"          Alt+Left-Button-Drag.  One may be able to move a window this way.\n"
202"          Also, e.g., Alt+Right-Button-Drag may resize a window.\n"
203"     Tip: Clicking on part of an obscured window may raise it to the top.\n"
204"          Also, e.g., Alt+Middle-Button may toggle Raise/Lower.\n"
205"\n"
206"     Tip: The SSVNC 1.0.25 unix and macosx vncviewer has 'EscapeKeys' hot\n"
207"          keys that will move, resize, raise, and lower the window via the\n"
208"          x11vnc -remote_prefix X11VNC_APPSHARE_CMD: feature.  So in the\n"
209"          viewer while holding down Shift_L+Super_L+Alt_L the arrow keys\n"
210"          move the window, PageUp/PageDn/Home/End resize it, and - and +\n"
211"          raise and lower it.  Key 'M' or Button1 moves the remote window\n"
212"          to the +X+Y of the viewer window.  Key 'D' or Button3 deletes\n"
213"          the remote window.\n"
214"\n"
215"          You can run the SSVNC vncviewer with options '-escape default',\n"
216"          '-multilisten' and '-env VNCVIEWER_MIN_TITLE=1'; or just run\n"
217"          with option '-appshare' to enable these and automatic placement.\n"
218"\n"
219"     If any part of a window goes off of the display screen, then x11vnc\n"
220"     may be unable to poll it (without crashing), and so the window will\n"
221"     stop updating until the window is completely on-screen again.\n"
222"\n"
223"     The (stock) vnc viewer does not know where to best position each new\n"
224"     viewer window; it likely centers each one (including when resized.)\n"
225"     Note: The SSVNC viewer in '-appshare' mode places them correctly.\n"
226"\n"
227"     Deleting a viewer window does not delete the real window.\n"
228"     Note: The SSVNC viewer Shift+EscapeKeys+Button3 deletes it.\n"
229"\n"
230"     Sometimes new window detection fails.\n"
231"\n"
232"     Sometimes menu/popup detection fails.\n"
233"\n"
234"     Sometimes the contents of a menu/popup window have blacked-out regions.\n"
235"     Try -sid or -showmenus as a workaround.\n"
236"\n"
237"     If the application starts up a new application (a different process)\n"
238"     that new application will not be tracked (but, unfortunately, it may\n"
239"     cover up existing windows that are being tracked.) See cmd=add_window\n"
240"     and cmd=add_app described above.\n"
241"\n"
242#endif
243;
244
245#include <stdio.h>
246#include <stdlib.h>
247#include <string.h>
248
249#define WMAX 192
250#define CMAX 128
251#define AMAX 32
252
253static Window root = None;
254static Window watch[WMAX];
255static Window apps[WMAX];
256static int state[WMAX];
257static char *clients[CMAX];
258static XWindowAttributes attr;
259static char *ticker_atom_str = "X11VNC_APPSHARE_TICKER";
260static Atom ticker_atom = None;
261static char *cmd_atom_str = "X11VNC_APPSHARE_COMMAND";
262static Atom cmd_atom = None;
263static char *connect_to = NULL;
264static char *x11vnc_args = "";
265static char *id_opt = "-id";
266static int skip_menus = 1;
267static int exit_no_app_win = 1;
268static int shell = 0;
269static int tree_depth = 3;
270static char *prompt = "appshare> ";
271static char *x11vnc = "x11vnc";
272static char *control = NULL;
273static char *trackdir = "unset";
274static char *trackpre = "/tmp/x11vnc-appshare-trackdir-tmp";
275static char *tracktmp = NULL;
276static char unique_tag[100];
277static int use_forever = 1;
278static int last_event_type = 0;
279static pid_t helper_pid = 0;
280static pid_t parent_pid = 0;
281static double helper_delay = 0.5;
282static int appshare_debug = 0;
283static double start_time = 0.0;
284
285static void get_wm_name(Window win, char **name);
286static int win_attr(Window win);
287static int get_xy(Window win, int *x, int *y);
288static Window check_inside(Window win);
289static int ours(Window win);
290static void destroy_win(Window win);
291static int same_app(Window win, Window app);
292
293static void ff(void) {
294	fflush(stdout);
295	fflush(stderr);
296}
297
298static int find_win(Window win) {
299	int i;
300	for (i=0; i < WMAX; i++) {
301		if (watch[i] == win) {
302			return i;
303		}
304	}
305	return -1;
306}
307
308static int find_app(Window app) {
309	int i;
310	for (i=0; i < AMAX; i++) {
311		if (apps[i] == app) {
312			return i;
313		}
314	}
315	return -1;
316}
317
318static int find_client(char *cl) {
319	int i;
320	for (i=0; i < CMAX; i++) {
321		if (cl == NULL) {
322			if (clients[i] == NULL) {
323				return i;
324			}
325			continue;
326		}
327		if (clients[i] == NULL) {
328			continue;
329		}
330		if (!strcmp(clients[i], cl)) {
331			return i;
332		}
333	}
334	return -1;
335}
336
337static int trackdir_pid(Window win) {
338	FILE *f;
339	int ln = 0, pid = 0;
340	char line[1024];
341
342	if (!trackdir) {
343		return 0;
344	}
345	sprintf(tracktmp, "%s/0x%lx.log", trackdir, win);
346	f = fopen(tracktmp, "r");
347	if (!f) {
348		return 0;
349	}
350	while (fgets(line, sizeof(line), f) != NULL) {
351		if (ln++ > 30) {
352			break;
353		}
354		if (strstr(line, "x11vnc version:")) {
355			char *q = strstr(line, "pid:");
356			if (q) {
357				int p;
358				if (sscanf(q, "pid: %d", &p) == 1) {
359					if (p > 0) {
360						pid = p;
361						break;
362					}
363				}
364			}
365		}
366	}
367	fclose(f);
368	return pid;
369}
370
371static void trackdir_cleanup(Window win) {
372	char *suffix[] = {"log", "connect", NULL};
373	int i=0;
374	if (!trackdir) {
375		return;
376	}
377	while (suffix[i] != NULL) {
378		sprintf(tracktmp, "%s/0x%lx.%s", trackdir, win, suffix[i]);
379		if (appshare_debug && !strcmp(suffix[i], "log")) {
380			fprintf(stderr, "keeping:  %s\n", tracktmp);
381			ff();
382		} else {
383			if (appshare_debug) {
384				fprintf(stderr, "removing: %s\n", tracktmp);
385				ff();
386			}
387			unlink(tracktmp);
388		}
389		i++;
390	}
391}
392
393static void launch(Window win) {
394	char *cmd, *tmp, *connto, *name;
395	int len, timeo = 30, uf = use_forever;
396	int w = 0, h = 0, x = 0, y = 0;
397
398	if (win_attr(win)) {
399		/* maybe switch to debug only. */
400		w = attr.width;
401		h = attr.height;
402		get_xy(win, &x, &y);
403	}
404
405	get_wm_name(win, &name);
406
407	if (strstr(x11vnc_args, "-once")) {
408		uf = 0;
409	}
410
411	if (control) {
412		int i = 0;
413		len = 0;
414		for (i=0; i < CMAX; i++) {
415			if (clients[i] != NULL) {
416				len += strlen(clients[i]) + 2;
417			}
418		}
419		connto = (char *) calloc(len, 1);
420		for (i=0; i < CMAX; i++) {
421			if (clients[i] != NULL) {
422				if (connto[0] != '\0') {
423					strcat(connto, ",");
424				}
425				strcat(connto, clients[i]);
426			}
427		}
428	} else {
429		connto = strdup(connect_to);
430	}
431	if (!strcmp(connto, "")) {
432		timeo = 0;
433	}
434	if (uf) {
435		timeo = 0;
436	}
437
438	len = 1000 + strlen(x11vnc) + strlen(connto) + strlen(x11vnc_args)
439	    + 3 * (trackdir ? strlen(trackdir) : 100);
440
441	cmd = (char *) calloc(len, 1);
442	tmp = (char *) calloc(len, 1);
443
444	sprintf(cmd, "%s %s 0x%lx -bg -quiet %s -nopw -rfbport 0 "
445	    "-timeout %d -noxdamage -noxinerama -norc -repeat -speeds dsl "
446	    "-env X11VNC_AVOID_WINDOWS=never -env X11VNC_APPSHARE_ACTIVE=1 "
447	    "-env X11VNC_NO_CHECK_PM=1 -env %s -novncconnect -shared -nonap "
448	    "-remote_prefix X11VNC_APPSHARE_CMD:",
449	    x11vnc, id_opt, win, use_forever ? "-forever" : "-once", timeo, unique_tag);
450
451	if (trackdir) {
452		FILE *f;
453		sprintf(tracktmp, " -noquiet -o %s/0x%lx.log", trackdir, win);
454		strcat(cmd, tracktmp);
455		sprintf(tracktmp, "%s/0x%lx.connect", trackdir, win);
456		f = fopen(tracktmp, "w");
457		if (f) {
458			fprintf(f, "%s", connto);
459			fclose(f);
460			sprintf(tmp, " -connect_or_exit '%s'", tracktmp);
461			strcat(cmd, tmp);
462		} else {
463			sprintf(tmp, " -connect_or_exit '%s'", connto);
464			strcat(cmd, tmp);
465		}
466	} else {
467		if (!strcmp(connto, "")) {
468			sprintf(tmp, " -connect '%s'", connto);
469		} else {
470			sprintf(tmp, " -connect_or_exit '%s'", connto);
471		}
472		strcat(cmd, tmp);
473	}
474	if (uf) {
475		char *q = strstr(cmd, "-connect_or_exit");
476		if (q) q = strstr(q, "_or_exit");
477		if (q) {
478			unsigned int i;
479			for (i=0; i < strlen("_or_exit"); i++) {
480				*q = ' ';
481				q++;
482			}
483		}
484	}
485
486	strcat(cmd, " ");
487	strcat(cmd, x11vnc_args);
488
489	fprintf(stdout, "launching: x11vnc for window 0x%08lx %dx%d+%d+%d \"%s\"\n",
490	    win, w, h, x, y, name);
491
492	if (appshare_debug) {
493		fprintf(stderr, "\nrunning:   %s\n\n", cmd);
494	}
495	ff();
496
497	system(cmd);
498
499	free(cmd);
500	free(tmp);
501	free(connto);
502	free(name);
503}
504
505static void stop(Window win) {
506	char *cmd;
507	int pid = -1;
508	int f = find_win(win);
509	if (f < 0 || win == None) {
510		return;
511	}
512	if (state[f] == 0) {
513		return;
514	}
515	if (trackdir) {
516		pid = trackdir_pid(win);
517		if (pid > 0) {
518			if (appshare_debug) {fprintf(stderr,
519			    "sending SIGTERM to: %d\n", pid); ff();}
520			kill((pid_t) pid, SIGTERM);
521		}
522	}
523
524	cmd = (char *) malloc(1000 + strlen(x11vnc));
525	sprintf(cmd, "pkill -TERM -f '%s %s 0x%lx -bg'", x11vnc, id_opt, win);
526	if (appshare_debug) {
527		fprintf(stdout, "stopping:  0x%08lx - %s\n", win, cmd);
528	} else {
529		fprintf(stdout, "stopping:  x11vnc for window 0x%08lx  "
530		    "(pid: %d)\n", win, pid);
531	}
532	ff();
533	system(cmd);
534
535	sprintf(cmd, "(sleep 0.25 2>/dev/null || sleep 1; pkill -KILL -f '%s "
536	    "%s 0x%lx -bg') &", x11vnc, id_opt, win);
537	system(cmd);
538
539	if (trackdir) {
540		trackdir_cleanup(win);
541	}
542
543	free(cmd);
544}
545
546static void kill_helper_pid(void) {
547	int status;
548	if (helper_pid <= 0) {
549		return;
550	}
551	fprintf(stderr, "stopping: helper_pid: %d\n", (int) helper_pid);
552	kill(helper_pid, SIGTERM);
553	usleep(50 * 1000);
554	kill(helper_pid, SIGKILL);
555	usleep(25 * 1000);
556#if LIBVNCSERVER_HAVE_SYS_WAIT_H && LIBVNCSERVER_HAVE_WAITPID
557	waitpid(helper_pid, &status, WNOHANG);
558#endif
559}
560
561static void be_helper_pid(char *dpy_str) {
562	int cnt = 0;
563	int ms = (int) (1000 * helper_delay);
564	double last_check = 0.0;
565
566	if (ms < 50) ms = 50;
567
568#if NO_X11
569	fprintf(stderr, "be_helper_pid: not compiled with X11.\n");
570#else
571	dpy = XOpenDisplay(dpy_str);
572	ticker_atom = XInternAtom(dpy, ticker_atom_str, False);
573
574	while (1) {
575		char tmp[32];
576		sprintf(tmp, "HELPER_CNT_%08d", cnt++);
577		XChangeProperty(dpy, DefaultRootWindow(dpy), ticker_atom, XA_STRING, 8,
578		    PropModeReplace, (unsigned char *) tmp, strlen(tmp));
579		XFlush(dpy);
580		usleep(ms*1000);
581		if (parent_pid > 0) {
582			if(dnow() > last_check + 1.0) {
583				last_check = dnow();
584				if (kill(parent_pid, 0) != 0) {
585					fprintf(stderr, "be_helper_pid: parent %d is gone.\n", (int) parent_pid);
586					break;
587				}
588			}
589		}
590	}
591#endif
592	exit(0);
593}
594
595static void print_logs(void) {
596	if (trackdir) {
597		DIR *dir = opendir(trackdir);
598		if (dir) {
599			struct dirent *dp;
600			while ( (dp = readdir(dir)) != NULL) {
601				FILE *f;
602				char *name = dp->d_name;
603				if (!strcmp(name, ".") || !strcmp(name, "..")) {
604					continue;
605				}
606				if (strstr(name, "0x") != name) {
607					continue;
608				}
609				if (strstr(name, ".log") == NULL) {
610					continue;
611				}
612				sprintf(tracktmp, "%s/%s", trackdir, name);
613				f = fopen(tracktmp, "r");
614				if (f) {
615					char line[1024];
616					fprintf(stderr, "===== x11vnc log %s =====\n", tracktmp);
617					while (fgets(line, sizeof(line), f) != NULL) {
618						fprintf(stderr, "%s", line);
619					}
620					fprintf(stderr, "\n");
621					ff();
622					fclose(f);
623				}
624			}
625			closedir(dir);
626		}
627	}
628}
629
630static void appshare_cleanup(int s) {
631	int i;
632	if (s) {}
633
634	if (use_forever) {
635		/* launch this backup in case they kill -9 us before we terminate everything */
636		char cmd[1000];
637		sprintf(cmd, "(sleep 3; pkill -TERM -f '%s') &", unique_tag);
638		if (appshare_debug) fprintf(stderr, "%s\n", cmd);
639		system(cmd);
640	}
641
642	for (i=0; i < WMAX; i++) {
643		if (watch[i] != None) {
644			stop(watch[i]);
645		}
646	}
647
648	if (trackdir) {
649		DIR *dir = opendir(trackdir);
650		if (dir) {
651			struct dirent *dp;
652			while ( (dp = readdir(dir)) != NULL) {
653				char *name = dp->d_name;
654				if (!strcmp(name, ".") || !strcmp(name, "..")) {
655					continue;
656				}
657				if (strstr(name, "0x") != name) {
658					fprintf(stderr, "skipping: %s\n", name);
659					continue;
660				}
661				if (!appshare_debug) {
662					fprintf(stderr, "removing: %s\n", name);
663					sprintf(tracktmp, "%s/%s", trackdir, name);
664					unlink(tracktmp);
665				} else {
666					if (appshare_debug) fprintf(stderr, "keeping:  %s\n", name);
667				}
668			}
669			closedir(dir);
670		}
671		if (!appshare_debug) {
672			if (strstr(trackdir, trackpre) == trackdir) {
673				if (appshare_debug) fprintf(stderr, "removing: %s\n", trackdir);
674				rmdir(trackdir);
675			}
676		}
677		ff();
678	}
679
680	kill_helper_pid();
681
682#if !NO_X11
683	XCloseDisplay(dpy);
684#endif
685	fprintf(stdout, "done.\n");
686	ff();
687	exit(0);
688}
689
690static int trap_xerror(Display *d, XErrorEvent *error) {
691	if (d || error) {}
692	return 0;
693}
694
695#if 0
696typedef struct {
697    int x, y;                   /* location of window */
698    int width, height;          /* width and height of window */
699    int border_width;           /* border width of window */
700    int depth;                  /* depth of window */
701    Visual *visual;             /* the associated visual structure */
702    Window root;                /* root of screen containing window */
703    int class;                  /* InputOutput, InputOnly*/
704    int bit_gravity;            /* one of bit gravity values */
705    int win_gravity;            /* one of the window gravity values */
706    int backing_store;          /* NotUseful, WhenMapped, Always */
707    unsigned long backing_planes;/* planes to be preserved if possible */
708    unsigned long backing_pixel;/* value to be used when restoring planes */
709    Bool save_under;            /* boolean, should bits under be saved? */
710    Colormap colormap;          /* color map to be associated with window */
711    Bool map_installed;         /* boolean, is color map currently installed*/
712    int map_state;              /* IsUnmapped, IsUnviewable, IsViewable */
713    long all_event_masks;       /* set of events all people have interest in*/
714    long your_event_mask;       /* my event mask */
715    long do_not_propagate_mask; /* set of events that should not propagate */
716    Bool override_redirect;     /* boolean value for override-redirect */
717    Screen *screen;             /* back pointer to correct screen */
718} XWindowAttributes;
719#endif
720
721static void get_wm_name(Window win, char **name) {
722	int ok;
723
724#if !NO_X11
725        XErrorHandler old_handler = XSetErrorHandler(trap_xerror);
726	ok = XFetchName(dpy, win, name);
727       	XSetErrorHandler(old_handler);
728#endif
729
730	if (!ok || *name == NULL) {
731		*name = strdup("unknown");
732	}
733}
734
735static int win_attr(Window win) {
736	int ok = 0;
737#if !NO_X11
738        XErrorHandler old_handler = XSetErrorHandler(trap_xerror);
739	ok = XGetWindowAttributes(dpy, win, &attr);
740       	XSetErrorHandler(old_handler);
741#endif
742
743	if (ok) {
744		return 1;
745	} else {
746		return 0;
747	}
748}
749
750static void win_select(Window win, int ignore) {
751#if !NO_X11
752        XErrorHandler old_handler = XSetErrorHandler(trap_xerror);
753	if (ignore) {
754		XSelectInput(dpy, win, 0);
755	} else {
756		XSelectInput(dpy, win, SubstructureNotifyMask);
757	}
758	XSync(dpy, False);
759       	XSetErrorHandler(old_handler);
760#endif
761}
762
763static Window get_parent(Window win) {
764	int ok;
765	Window r, parent = None, *list = NULL;
766	unsigned int nchild;
767
768#if !NO_X11
769        XErrorHandler old_handler = XSetErrorHandler(trap_xerror);
770	ok = XQueryTree(dpy, win, &r, &parent, &list, &nchild);
771       	XSetErrorHandler(old_handler);
772
773	if (!ok) {
774		return None;
775	}
776	if (list) {
777		XFree(list);
778	}
779#endif
780	return parent;
781}
782
783static int get_xy(Window win, int *x, int *y) {
784	Window cr;
785	Bool rc = False;
786#if !NO_X11
787	XErrorHandler old_handler = XSetErrorHandler(trap_xerror);
788
789	rc = XTranslateCoordinates(dpy, win, root, 0, 0, x, y, &cr);
790       	XSetErrorHandler(old_handler);
791#endif
792
793	if (!rc) {
794		return 0;
795	} else {
796		return 1;
797	}
798}
799
800static Window check_inside(Window win) {
801	int i, nwin = 0;
802	int w, h, x, y;
803	int Ws[WMAX], Hs[WMAX], Xs[WMAX], Ys[WMAX];
804	Window wins[WMAX];
805
806	if (!win_attr(win)) {
807		return None;
808	}
809
810	/* store them first to give the win app more time to settle.  */
811	for (i=0; i < WMAX; i++) {
812		int X, Y;
813		Window wchk = watch[i];
814		if (wchk == None) {
815			continue;
816		}
817		if (state[i] == 0) {
818			continue;
819		}
820		if (!win_attr(wchk)) {
821			continue;
822		}
823		if (!get_xy(wchk, &X, &Y)) {
824			continue;
825		}
826
827		Xs[nwin] = X;
828		Ys[nwin] = Y;
829		Ws[nwin] = attr.width;
830		Hs[nwin] = attr.height;
831		wins[nwin] = wchk;
832		nwin++;
833	}
834
835	if (nwin == 0) {
836		return None;
837	}
838
839	if (!win_attr(win)) {
840		return None;
841	}
842	w = attr.width;
843	h = attr.height;
844
845	get_xy(win, &x, &y);
846	if (!get_xy(win, &x, &y)) {
847		return None;
848	}
849
850	for (i=0; i < nwin; i++) {
851		int X, Y, W, H;
852		Window wchk = wins[i];
853		X = Xs[i];
854		Y = Ys[i];
855		W = Ws[i];
856		H = Hs[i];
857
858		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);
859
860		if (X <= x && Y <= y) {
861			if (x + w  <= X + W && y + h < Y + H) {
862				return wchk;
863			}
864		}
865	}
866
867	return None;
868}
869
870static void add_win(Window win) {
871	int idx  = find_win(win);
872	int free = find_win(None);
873	if (idx >= 0) {
874		if (appshare_debug) {fprintf(stderr, "already watching window: 0x%lx\n", win); ff();}
875		return;
876	}
877	if (free < 0) {
878		fprintf(stderr, "ran out of slots for window: 0x%lx\n", win); ff();
879		return;
880	}
881
882	if (appshare_debug) {fprintf(stderr, "watching: 0x%lx at %d\n", win, free); ff();}
883
884	watch[free] = win;
885	state[free] = 0;
886
887	win_select(win, 0);
888}
889
890static void delete_win(Window win) {
891	int i;
892	for (i=0; i < WMAX; i++) {
893		if (watch[i] == win) {
894			watch[i] = None;
895			state[i] = 0;
896			if (appshare_debug) {fprintf(stderr, "deleting: 0x%lx at %d\n", win, i); ff();}
897		}
898	}
899}
900
901static void recurse_search(int level, int level_max, Window top, Window app, int *nw) {
902	Window w, r, parent, *list = NULL;
903	unsigned int nchild;
904	int ok = 0;
905
906	if (appshare_debug > 1) {
907		fprintf(stderr, "level: %d level_max: %d  top: 0x%lx  app: 0x%lx\n", level, level_max, top, app);
908	}
909	if (level >= level_max) {
910		return;
911	}
912
913#if !NO_X11
914	ok = XQueryTree(dpy, top, &r, &parent, &list, &nchild);
915	if (ok) {
916		int i;
917		for (i=0; i < (int) nchild; i++) {
918			w = list[i];
919			if (w == None || find_win(w) >= 0) {
920				continue;
921			}
922			if (ours(w) && w != app) {
923				if (appshare_debug) fprintf(stderr, "add level %d 0x%lx %d/%d\n",
924				    level, w, i, nchild);
925				add_win(w);
926				(*nw)++;
927			}
928		}
929		for (i=0; i < (int) nchild; i++) {
930			w = list[i];
931			if (w == None || ours(w)) {
932				continue;
933			}
934			recurse_search(level+1, level_max, w, app, nw);
935		}
936	}
937	if (list) {
938		XFree(list);
939	}
940#endif
941}
942
943static void add_app(Window app) {
944	int i, nw = 0, free = -1;
945        XErrorHandler old_handler;
946
947#if !NO_X11
948	i = find_app(app);
949	if (i >= 0) {
950		fprintf(stderr, "already tracking app: 0x%lx\n", app);
951		return;
952	}
953	for (i=0; i < AMAX; i++) {
954		if (same_app(apps[i], app)) {
955			fprintf(stderr, "already tracking app: 0x%lx via 0x%lx\n", app, apps[i]);
956			return;
957		}
958	}
959	free = find_app(None);
960	if (free < 0) {
961		fprintf(stderr, "ran out of app slots.\n");
962		return;
963	}
964	apps[free] = app;
965
966	add_win(app);
967
968        old_handler = XSetErrorHandler(trap_xerror);
969	recurse_search(0, tree_depth, root, app, &nw);
970       	XSetErrorHandler(old_handler);
971#endif
972	fprintf(stderr, "tracking %d windows related to app window 0x%lx\n", nw, app);
973}
974
975static void del_app(Window app) {
976	int i;
977	for (i=0; i < WMAX; i++) {
978		Window win = watch[i];
979		if (win != None) {
980			if (same_app(app, win)) {
981				destroy_win(win);
982			}
983		}
984	}
985	for (i=0; i < AMAX; i++) {
986		Window app2 = apps[i];
987		if (app2 != None) {
988			if (same_app(app, app2)) {
989				apps[i] = None;
990			}
991		}
992	}
993}
994
995static void wait_until_empty(char *file) {
996	double t = 0.0, dt = 0.05;
997	while (t < 1.0) {
998		struct stat sb;
999		if (stat(file, &sb) != 0) {
1000			return;
1001		}
1002		if (sb.st_size == 0) {
1003			return;
1004		}
1005		t += dt;
1006		usleep( (int) (dt * 1000 * 1000) );
1007	}
1008}
1009
1010static void client(char *client, int add) {
1011	DIR *dir;
1012	struct dirent *dp;
1013
1014	if (!client) {
1015		return;
1016	}
1017	if (!trackdir) {
1018		fprintf(stderr, "no trackdir, cannot %s client: %s\n",
1019		    add ? "add" : "disconnect", client);
1020		ff();
1021		return;
1022	}
1023	fprintf(stdout, "%s client: %s\n", add ? "adding  " : "deleting", client);
1024
1025	dir = opendir(trackdir);
1026	if (!dir) {
1027		fprintf(stderr, "could not opendir trackdir: %s\n", trackdir);
1028		return;
1029	}
1030	while ( (dp = readdir(dir)) != NULL) {
1031		char *name = dp->d_name;
1032		if (!strcmp(name, ".") || !strcmp(name, "..")) {
1033			continue;
1034		}
1035		if (strstr(name, "0x") != name) {
1036			continue;
1037		}
1038		if (strstr(name, ".connect")) {
1039			FILE *f;
1040			char *tmp;
1041			Window twin;
1042
1043			if (scan_hexdec(name, &twin)) {
1044				int f = find_win(twin);
1045				if (appshare_debug) {
1046					fprintf(stderr, "twin: 0x%lx name=%s f=%d\n", twin, name, f);
1047					ff();
1048				}
1049				if (f < 0) {
1050					continue;
1051				}
1052			}
1053
1054			tmp = (char *) calloc(100 + strlen(client), 1);
1055			sprintf(tracktmp, "%s/%s", trackdir, name);
1056			if (add) {
1057				sprintf(tmp, "%s\n", client);
1058			} else {
1059				sprintf(tmp, "cmd=close:%s\n", client);
1060			}
1061			wait_until_empty(tracktmp);
1062			f = fopen(tracktmp, "w");
1063			if (f) {
1064				if (appshare_debug) {
1065					fprintf(stderr, "%s client: %s + %s",
1066					add ? "add" : "disconnect", tracktmp, tmp);
1067					ff();
1068				}
1069				fprintf(f, "%s", tmp);
1070				fclose(f);
1071			}
1072			free(tmp);
1073		}
1074	}
1075	closedir(dir);
1076}
1077
1078static void mapped(Window win) {
1079	int f;
1080	if (win == None) {
1081		return;
1082	}
1083	f = find_win(win);
1084	if (f < 0) {
1085		if (win_attr(win)) {
1086			if (get_parent(win) == root) {
1087				/* XXX more cases? */
1088				add_win(win);
1089			}
1090		}
1091	}
1092}
1093
1094static void unmapped(Window win) {
1095	int f = find_win(win);
1096	if (f < 0 || win == None) {
1097		return;
1098	}
1099	stop(win);
1100	state[f] = 0;
1101}
1102
1103static void destroy_win(Window win) {
1104	stop(win);
1105	delete_win(win);
1106}
1107
1108static Window parse_win(char *str) {
1109	Window win = None;
1110	if (!str) {
1111		return None;
1112	}
1113	if (!strcmp(str, "pick") || !strcmp(str, "p")) {
1114		static double last_pick = 0.0;
1115		if (dnow() < start_time + 15) {
1116			;
1117		} else if (dnow() < last_pick + 2) {
1118			return None;
1119		} else {
1120			last_pick = dnow();
1121		}
1122		if (!pick_windowid(&win)) {
1123			fprintf(stderr, "parse_win: bad window pick.\n");
1124			win = None;
1125		}
1126		if (win == root) {
1127			fprintf(stderr, "parse_win: ignoring pick of rootwin 0x%lx.\n", win);
1128			win = None;
1129		}
1130		ff();
1131	} else if (!scan_hexdec(str, &win)) {
1132		win = None;
1133	}
1134	return win;
1135}
1136
1137static void add_or_del_app(char *str, int add) {
1138	Window win = parse_win(str);
1139
1140	if (win != None) {
1141		if (add) {
1142			add_app(win);
1143		} else {
1144			del_app(win);
1145		}
1146	} else if (!strcmp(str, "all")) {
1147		if (!add) {
1148			int i;
1149			for (i=0; i < AMAX; i++) {
1150				if (apps[i] != None) {
1151					del_app(apps[i]);
1152				}
1153			}
1154		}
1155	}
1156}
1157
1158static void add_or_del_win(char *str, int add) {
1159	Window win = parse_win(str);
1160
1161	if (win != None) {
1162		int f = find_win(win);
1163		if (add) {
1164			if (f < 0 && win_attr(win)) {
1165				add_win(win);
1166			}
1167		} else {
1168			if (f >= 0) {
1169				destroy_win(win);
1170			}
1171		}
1172	} else if (!strcmp(str, "all")) {
1173		if (!add) {
1174			int i;
1175			for (i=0; i < WMAX; i++) {
1176				if (watch[i] != None) {
1177					destroy_win(watch[i]);
1178				}
1179			}
1180		}
1181	}
1182}
1183
1184static void add_or_del_client(char *str, int add) {
1185	int i;
1186
1187	if (!str) {
1188		return;
1189	}
1190	if (strcmp(control, "internal")) {
1191		return;
1192	}
1193	if (add) {
1194		int idx  = find_client(str);
1195		int free = find_client(NULL);
1196
1197		if (idx >=0) {
1198			fprintf(stderr, "already tracking client: %s in slot %d\n", str, idx);
1199			ff();
1200			return;
1201		}
1202		if (free < 0) {
1203			static int cnt = 0;
1204			if (cnt++ < 10) {
1205				fprintf(stderr, "ran out of client slots.\n");
1206				ff();
1207			}
1208			return;
1209		}
1210		clients[free] = strdup(str);
1211		client(str, 1);
1212	} else {
1213		if (str[0] == '#' || str[0] == '%') {
1214			if (sscanf(str+1, "%d", &i) == 1) {
1215				i--;
1216				if (0 <= i && i < CMAX) {
1217					if (clients[i] != NULL) {
1218						client(clients[i], 0);
1219						free(clients[i]);
1220						clients[i] = NULL;
1221						return;
1222					}
1223				}
1224			}
1225		} else if (!strcmp(str, "all")) {
1226			for (i=0; i < CMAX; i++) {
1227				if (clients[i] == NULL) {
1228					continue;
1229				}
1230				client(clients[i], 0);
1231				free(clients[i]);
1232				clients[i] = NULL;
1233			}
1234			return;
1235		}
1236
1237		i = find_client(str);
1238		if (i >= 0) {
1239			free(clients[i]);
1240			clients[i] = NULL;
1241			client(str, 0);
1242		}
1243	}
1244}
1245
1246static void restart_x11vnc(void) {
1247	int i, n = 0;
1248	Window win, active[WMAX];
1249	for (i=0; i < WMAX; i++) {
1250		win = watch[i];
1251		if (win == None) {
1252			continue;
1253		}
1254		if (state[i]) {
1255			active[n++] = win;
1256			stop(win);
1257		}
1258	}
1259	if (n) {
1260		usleep(1500 * 1000);
1261	}
1262	for (i=0; i < n; i++) {
1263		win = active[i];
1264		launch(win);
1265	}
1266}
1267
1268static unsigned long cmask = 0x3fc00000; /* 00111111110000000000000000000000 */
1269
1270static void init_cmask(void) {
1271	/* dependent on the X server implementation; XmuClientWindow better? */
1272	/* xc/programs/Xserver/include/resource.h */
1273	int didit = 0, res_cnt = 29, client_bits = 8;
1274
1275	if (getenv("X11VNC_APPSHARE_CLIENT_MASK")) {
1276		unsigned long cr;
1277		if (sscanf(getenv("X11VNC_APPSHARE_CLIENT_MASK"), "0x%lx", &cr) == 1) {
1278			cmask = cr;
1279			didit = 1;
1280		}
1281	} else if (getenv("X11VNC_APPSHARE_CLIENT_BITS")) {
1282		int cr = atoi(getenv("X11VNC_APPSHARE_CLIENT_BITS"));
1283		if (cr > 0) {
1284			client_bits = cr;
1285		}
1286	}
1287	if (!didit) {
1288		cmask = (((1 << client_bits) - 1) << (res_cnt-client_bits));
1289	}
1290	fprintf(stderr, "client_mask: 0x%08lx\n", cmask);
1291}
1292
1293static int same_app(Window win, Window app) {
1294	if ( (win & cmask) == (app & cmask) ) {
1295		return 1;
1296	} else {
1297		return 0;
1298	}
1299}
1300
1301static int ours(Window win) {
1302	int i;
1303	for (i=0; i < AMAX; i++) {
1304		if (apps[i] != None) {
1305			if (same_app(win, apps[i])) {
1306				return 1;
1307			}
1308		}
1309	}
1310	return 0;
1311}
1312
1313static void list_clients(void) {
1314	int i, n = 0;
1315	for (i=0; i < CMAX; i++) {
1316		if (clients[i] == NULL) {
1317			continue;
1318		}
1319		fprintf(stdout, "client[%02d] %s\n", ++n, clients[i]);
1320	}
1321	fprintf(stdout, "total clients: %d\n", n);
1322	ff();
1323}
1324
1325static void list_windows(void) {
1326	int i, n = 0;
1327	for (i=0; i < WMAX; i++) {
1328		char *name;
1329		Window win = watch[i];
1330		if (win == None) {
1331			continue;
1332		}
1333		get_wm_name(win, &name);
1334		fprintf(stdout, "window[%02d] 0x%08lx state: %d slot: %03d \"%s\"\n",
1335		    ++n, win, state[i], i, name);
1336		free(name);
1337	}
1338	fprintf(stdout, "total windows: %d\n", n);
1339	ff();
1340}
1341
1342static void list_apps(void) {
1343	int i, n = 0;
1344	for (i=0; i < AMAX; i++) {
1345		char *name;
1346		Window win = apps[i];
1347		if (win == None) {
1348			continue;
1349		}
1350		get_wm_name(win, &name);
1351		fprintf(stdout, "app[%02d] 0x%08lx state: %d slot: %03d \"%s\"\n",
1352		    ++n, win, state[i], i, name);
1353		free(name);
1354	}
1355	fprintf(stdout, "total apps: %d\n", n);
1356	ff();
1357}
1358
1359static int process_control(char *file, int check_clients) {
1360	int i, nnew = 0, seen[CMAX];
1361	char line[1024], *newctl[CMAX];
1362	FILE *f;
1363
1364	f = fopen(file, "r");
1365	if (!f) {
1366		return 1;
1367	}
1368	if (check_clients) {
1369		for (i=0; i < CMAX; i++) {
1370			seen[i] = 0;
1371		}
1372	}
1373	while (fgets(line, sizeof(line), f) != NULL) {
1374		char *q = strchr(line, '\n');
1375		if (q) *q = '\0';
1376
1377		if (appshare_debug) {
1378			fprintf(stderr, "check_control: %s\n", line);
1379			ff();
1380		}
1381
1382		q = lblanks(line);
1383		if (q[0] == '#') {
1384			continue;
1385		}
1386		if (!strcmp(q, "")) {
1387			continue;
1388		}
1389		if (strstr(q, "cmd=") == q) {
1390			char *cmd = q + strlen("cmd=");
1391			if (!strcmp(cmd, "quit")) {
1392				if (strcmp(control, file) && strstr(file, ".cmd")) {
1393					FILE *f2 = fopen(file, "w");
1394					if (f2) fclose(f2);
1395				}
1396				appshare_cleanup(0);
1397			} else if (!strcmp(cmd, "wait")) {
1398				return 0;
1399			} else if (strstr(cmd, "bcast:") == cmd) {
1400				;
1401			} else if (strstr(cmd, "del_window:") == cmd) {
1402				add_or_del_win(cmd + strlen("del_window:"), 0);
1403			} else if (strstr(cmd, "add_window:") == cmd) {
1404				add_or_del_win(cmd + strlen("add_window:"), 1);
1405			} else if (strstr(cmd, "del:") == cmd) {
1406				add_or_del_win(cmd + strlen("del:"), 0);
1407			} else if (strstr(cmd, "add:") == cmd) {
1408				add_or_del_win(cmd + strlen("add:"), 1);
1409			} else if (strstr(cmd, "del_client:") == cmd) {
1410				add_or_del_client(cmd + strlen("del_client:"), 0);
1411			} else if (strstr(cmd, "add_client:") == cmd) {
1412				add_or_del_client(cmd + strlen("add_client:"), 1);
1413			} else if (strstr(cmd, "-") == cmd) {
1414				add_or_del_client(cmd + strlen("-"), 0);
1415			} else if (strstr(cmd, "+") == cmd) {
1416				add_or_del_client(cmd + strlen("+"), 1);
1417			} else if (strstr(cmd, "del_app:") == cmd) {
1418				add_or_del_app(cmd + strlen("del_app:"), 0);
1419			} else if (strstr(cmd, "add_app:") == cmd) {
1420				add_or_del_app(cmd + strlen("add_app:"), 1);
1421			} else if (strstr(cmd, "debug:") == cmd) {
1422				appshare_debug = atoi(cmd + strlen("debug:"));
1423			} else if (strstr(cmd, "showmenus:") == cmd) {
1424				skip_menus = atoi(cmd + strlen("showmenus:"));
1425				skip_menus = !(skip_menus);
1426			} else if (strstr(cmd, "noexit:") == cmd) {
1427				exit_no_app_win = atoi(cmd + strlen("noexit:"));
1428				exit_no_app_win = !(exit_no_app_win);
1429			} else if (strstr(cmd, "use_forever:") == cmd) {
1430				use_forever = atoi(cmd + strlen("use_forever:"));
1431			} else if (strstr(cmd, "tree_depth:") == cmd) {
1432				tree_depth = atoi(cmd + strlen("tree_depth:"));
1433			} else if (strstr(cmd, "x11vnc_args:") == cmd) {
1434				x11vnc_args = strdup(cmd + strlen("x11vnc_args:"));
1435			} else if (strstr(cmd, "env:") == cmd) {
1436				putenv(cmd + strlen("env:"));
1437			} else if (strstr(cmd, "noop") == cmd) {
1438				;
1439			} else if (!strcmp(cmd, "restart")) {
1440				restart_x11vnc();
1441			} else if (!strcmp(cmd, "list_clients") || !strcmp(cmd, "lc")) {
1442				list_clients();
1443			} else if (!strcmp(cmd, "list_windows") || !strcmp(cmd, "lw")) {
1444				list_windows();
1445			} else if (!strcmp(cmd, "list_apps") || !strcmp(cmd, "la")) {
1446				list_apps();
1447			} else if (!strcmp(cmd, "list_all") || !strcmp(cmd, "ls")) {
1448				list_windows();
1449				fprintf(stderr, "\n");
1450				list_apps();
1451				fprintf(stderr, "\n");
1452				list_clients();
1453			} else if (!strcmp(cmd, "print_logs") || !strcmp(cmd, "pl")) {
1454				print_logs();
1455			} else if (!strcmp(cmd, "?") || !strcmp(cmd, "h") || !strcmp(cmd, "help")) {
1456				fprintf(stderr, "available commands:\n");
1457				fprintf(stderr, "\n");
1458				fprintf(stderr, "   quit restart noop x11vnc help ? ! !!\n");
1459				fprintf(stderr, "\n");
1460				fprintf(stderr, "   add_window:win  (add:win, add:pick)\n");
1461				fprintf(stderr, "   del_window:win  (del:win, del:pick, del:all)\n");
1462				fprintf(stderr, "   add_app:win     (add_app:pick)\n");
1463				fprintf(stderr, "   del_app:win     (del_app:pick, del_app:all)\n");
1464				fprintf(stderr, "   add_client:host (+host)\n");
1465				fprintf(stderr, "   del_client:host (-host, -all)\n");
1466				fprintf(stderr, "\n");
1467				fprintf(stderr, "   list_windows    (lw)\n");
1468				fprintf(stderr, "   list_apps       (la)\n");
1469				fprintf(stderr, "   list_clients    (lc)\n");
1470				fprintf(stderr, "   list_all        (ls)\n");
1471				fprintf(stderr, "   print_logs      (pl)\n");
1472				fprintf(stderr, "\n");
1473				fprintf(stderr, "   debug:n   showmenus:n   noexit:n\n");
1474			} else {
1475				fprintf(stderr, "unrecognized %s\n", q);
1476			}
1477			continue;
1478		}
1479		if (check_clients) {
1480			int idx = find_client(q);
1481			if (idx >= 0) {
1482				seen[idx] = 1;
1483			} else {
1484				newctl[nnew++] = strdup(q);
1485			}
1486		}
1487	}
1488	fclose(f);
1489
1490	if (check_clients) {
1491		for (i=0; i < CMAX; i++) {
1492			if (clients[i] == NULL) {
1493				continue;
1494			}
1495			if (!seen[i]) {
1496				client(clients[i], 0);
1497				free(clients[i]);
1498				clients[i] = NULL;
1499			}
1500		}
1501		for (i=0; i < nnew; i++) {
1502			int free = find_client(NULL);
1503			if (free < 0) {
1504				static int cnt = 0;
1505				if (cnt++ < 10) {
1506					fprintf(stderr, "ran out of client slots.\n");
1507					ff();
1508					break;
1509				}
1510				continue;
1511			}
1512			clients[free] = newctl[i];
1513			client(newctl[i], 1);
1514		}
1515	}
1516	return 1;
1517}
1518
1519static int check_control(void) {
1520	static int last_size = -1;
1521	static time_t last_mtime = 0;
1522	struct stat sb;
1523	char *control_cmd;
1524
1525	if (!control) {
1526		return 1;
1527	}
1528
1529	if (!strcmp(control, "internal")) {
1530		return 1;
1531	}
1532
1533	control_cmd = (char *)malloc(strlen(control) + strlen(".cmd") + 1);
1534	sprintf(control_cmd, "%s.cmd", control);
1535	if (stat(control_cmd, &sb) == 0) {
1536		FILE *f;
1537		if (sb.st_size > 0) {
1538			process_control(control_cmd, 0);
1539		}
1540		f = fopen(control_cmd, "w");
1541		if (f) {
1542			fclose(f);
1543		}
1544	}
1545	free(control_cmd);
1546
1547	if (stat(control, &sb) != 0) {
1548		return 1;
1549	}
1550	if (last_size == (int) sb.st_size && last_mtime == sb.st_mtime) {
1551		return 1;
1552	}
1553	last_size = (int) sb.st_size;
1554	last_mtime = sb.st_mtime;
1555
1556	return process_control(control, 1);
1557}
1558
1559static void update(void) {
1560	int i, app_ok = 0;
1561	if (last_event_type != PropertyNotify) {
1562		if (appshare_debug) fprintf(stderr, "\nupdate ...\n");
1563	} else if (appshare_debug > 1) {
1564		fprintf(stderr, "update ... propertynotify\n");
1565	}
1566	if (!check_control()) {
1567		return;
1568	}
1569	for (i=0; i < WMAX; i++) {
1570		Window win = watch[i];
1571		if (win == None) {
1572			continue;
1573		}
1574		if (!win_attr(win)) {
1575			destroy_win(win);
1576			continue;
1577		}
1578		if (find_app(win) >= 0) {
1579			app_ok++;
1580		}
1581		if (state[i] == 0) {
1582			if (attr.map_state == IsViewable) {
1583				if (skip_menus) {
1584					Window inside = check_inside(win);
1585					if (inside != None) {
1586						if (appshare_debug) {fprintf(stderr, "skip_menus: window 0x%lx is inside of 0x%lx, not tracking it.\n", win, inside); ff();}
1587						delete_win(win);
1588						continue;
1589					}
1590				}
1591				launch(win);
1592				state[i] = 1;
1593			}
1594		} else if (state[i] == 1) {
1595			if (attr.map_state != IsViewable) {
1596				stop(win);
1597				state[i] = 0;
1598			}
1599		}
1600	}
1601	if (exit_no_app_win && !app_ok) {
1602		for (i=0; i < AMAX; i++) {
1603			if (apps[i] != None) {
1604				fprintf(stdout, "main application window is gone: 0x%lx\n", apps[i]);
1605			}
1606		}
1607		ff();
1608		appshare_cleanup(0);
1609	}
1610	if (last_event_type != PropertyNotify) {
1611		if (appshare_debug) {fprintf(stderr, "update done.\n"); ff();}
1612	}
1613}
1614
1615static void exiter(char *msg, int rc) {
1616	fprintf(stderr, "%s", msg);
1617	ff();
1618	kill_helper_pid();
1619	exit(rc);
1620}
1621
1622static void set_trackdir(void) {
1623	char tmp[256];
1624	struct stat sb;
1625	if (!strcmp(trackdir, "none")) {
1626		trackdir = NULL;
1627		return;
1628	}
1629	if (!strcmp(trackdir, "unset")) {
1630		int fd;
1631		sprintf(tmp, "%s.XXXXXX", trackpre);
1632		fd = mkstemp(tmp);
1633		if (fd < 0) {
1634			strcat(tmp, ": failed to create file.\n");
1635			exiter(tmp, 1);
1636		}
1637		/* XXX race */
1638		close(fd);
1639		unlink(tmp);
1640		if (mkdir(tmp, 0700) != 0) {
1641			strcat(tmp, ": failed to create dir.\n");
1642			exiter(tmp, 1);
1643		}
1644		trackdir = strdup(tmp);
1645	}
1646	if (stat(trackdir, &sb) != 0) {
1647		if (mkdir(trackdir, 0700) != 0) {
1648			exiter("could not make trackdir.\n", 1);
1649		}
1650	} else if (! S_ISDIR(sb.st_mode)) {
1651		exiter("trackdir not a directory.\n", 1);
1652	}
1653	tracktmp = (char *) calloc(1000 + strlen(trackdir), 1);
1654}
1655
1656static void process_string(char *str) {
1657	FILE *f;
1658	char *file;
1659	if (trackdir) {
1660		sprintf(tracktmp, "%s/0xprop.cmd", trackdir);
1661		file = strdup(tracktmp);
1662	} else {
1663		char tmp[] = "/tmp/x11vnc-appshare.cmd.XXXXXX";
1664		int fd = mkstemp(tmp);
1665		if (fd < 0) {
1666			return;
1667		}
1668		file = strdup(tmp);
1669		close(fd);
1670	}
1671	f = fopen(file, "w");
1672	if (f) {
1673		fprintf(f, "%s", str);
1674		fclose(f);
1675		process_control(file, 0);
1676	}
1677	unlink(file);
1678	free(file);
1679}
1680
1681static void handle_shell(void) {
1682	struct timeval tv;
1683	static char lastline[1000];
1684	static int first = 1;
1685	fd_set rfds;
1686	int fd0 = fileno(stdin);
1687
1688	if (first) {
1689		memset(lastline, 0, sizeof(lastline));
1690		first = 0;
1691	}
1692
1693	FD_ZERO(&rfds);
1694	FD_SET(fd0, &rfds);
1695	tv.tv_sec = 0;
1696	tv.tv_usec = 0;
1697	select(fd0+1, &rfds, NULL, NULL, &tv);
1698	if (FD_ISSET(fd0, &rfds)) {
1699		char line[1000], line2[1010];
1700		if (fgets(line, sizeof(line), stdin) != NULL) {
1701			char *str = lblanks(line);
1702			char *q = strrchr(str, '\n');
1703			if (q) *q = '\0';
1704			if (strcmp(str, "")) {
1705				if (!strcmp(str, "!!")) {
1706					sprintf(line, "%s", lastline);
1707					fprintf(stderr, "%s\n", line);
1708					str = line;
1709				}
1710				if (strstr(str, "!") == str) {
1711					system(str+1);
1712				} else if (!strcmp(str, "x11vnc") || !strcmp(str, "ps")) {
1713					char *cmd = "ps -elf | egrep 'PID|x11vnc' | grep -v egrep";
1714					fprintf(stderr, "%s\n", cmd);
1715					system(cmd);
1716				} else {
1717					sprintf(line2, "cmd=%s", str);
1718					process_string(line2);
1719				}
1720				sprintf(lastline, "%s", str);
1721			}
1722		}
1723		fprintf(stderr, "\n%s", prompt); ff();
1724	}
1725}
1726
1727static void handle_prop_cmd(void) {
1728	char *value, *str, *done = "DONE";
1729
1730	if (cmd_atom == None) {
1731		return;
1732	}
1733
1734	value = get_xprop(cmd_atom_str, root);
1735	if (value == NULL) {
1736		return;
1737	}
1738
1739	str = lblanks(value);
1740	if (!strcmp(str, done)) {
1741		free(value);
1742		return;
1743	}
1744	if (strstr(str, "cmd=quit") == str || strstr(str, "\ncmd=quit")) {
1745		set_xprop(cmd_atom_str, root, done);
1746		appshare_cleanup(0);
1747	}
1748
1749	process_string(str);
1750
1751	free(value);
1752	set_xprop(cmd_atom_str, root, done);
1753}
1754
1755#define PREFIX if(appshare_debug) fprintf(stderr, "  %8.2f  0x%08lx : ", dnow() - start, ev.xany.window);
1756
1757static void monitor(void) {
1758#if !NO_X11
1759	XEvent ev;
1760	double start = dnow();
1761	int got_prop_cmd = 0;
1762
1763	if (shell) {
1764		update();
1765		fprintf(stderr, "\n\n");
1766		process_string("cmd=help");
1767		fprintf(stderr, "\n%s", prompt); ff();
1768	}
1769
1770	while (1) {
1771		int t;
1772
1773		if (XEventsQueued(dpy, QueuedAlready) == 0) {
1774			update();
1775			if (got_prop_cmd) {
1776				handle_prop_cmd();
1777			}
1778			got_prop_cmd = 0;
1779			if (shell) {
1780				handle_shell();
1781			}
1782		}
1783
1784		XNextEvent(dpy, &ev);
1785
1786		last_event_type = ev.type;
1787
1788		switch (ev.type) {
1789		case Expose:
1790			PREFIX
1791			if(appshare_debug) fprintf(stderr, "Expose %04dx%04d+%04d+%04d\n", ev.xexpose.width, ev.xexpose.height, ev.xexpose.x, ev.xexpose.y);
1792			break;
1793		case ConfigureNotify:
1794#if 0
1795			PREFIX
1796			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);
1797#endif
1798			break;
1799		case VisibilityNotify:
1800			PREFIX
1801			if (appshare_debug) {
1802			fprintf(stderr, "VisibilityNotify: ");
1803			t = ev.xvisibility.state;
1804			if (t == VisibilityFullyObscured)     fprintf(stderr, "VisibilityFullyObscured\n");
1805			if (t == VisibilityPartiallyObscured) fprintf(stderr, "VisibilityPartiallyObscured\n");
1806			if (t == VisibilityUnobscured)        fprintf(stderr, "VisibilityUnobscured\n");
1807			}
1808			break;
1809		case MapNotify:
1810			PREFIX
1811			if(appshare_debug) fprintf(stderr, "MapNotify      win: 0x%lx\n", ev.xmap.window);
1812			if (ours(ev.xmap.window)) {
1813				mapped(ev.xmap.window);
1814			}
1815			break;
1816		case UnmapNotify:
1817			PREFIX
1818			if(appshare_debug) fprintf(stderr, "UnmapNotify    win: 0x%lx\n", ev.xmap.window);
1819			if (ours(ev.xmap.window)) {
1820				unmapped(ev.xmap.window);
1821			}
1822			break;
1823		case MapRequest:
1824			PREFIX
1825			if(appshare_debug) fprintf(stderr, "MapRequest\n");
1826			break;
1827		case CreateNotify:
1828			PREFIX
1829			if(appshare_debug) fprintf(stderr, "CreateNotify parent: 0x%lx  win: 0x%lx\n", ev.xcreatewindow.parent, ev.xcreatewindow.window);
1830			if (ev.xcreatewindow.parent == root && ours(ev.xcreatewindow.window)) {
1831				if (find_win(ev.xcreatewindow.window) >= 0) {
1832					destroy_win(ev.xcreatewindow.window);
1833				}
1834				add_win(ev.xcreatewindow.window);
1835			}
1836			break;
1837		case DestroyNotify:
1838			PREFIX
1839			if(appshare_debug) fprintf(stderr, "DestroyNotify  win: 0x%lx\n", ev.xdestroywindow.window);
1840			if (ours(ev.xdestroywindow.window)) {
1841				destroy_win(ev.xdestroywindow.window);
1842			}
1843			break;
1844		case ConfigureRequest:
1845			PREFIX
1846			if(appshare_debug) fprintf(stderr, "ConfigureRequest\n");
1847			break;
1848		case CirculateRequest:
1849#if 0
1850			PREFIX
1851			if(appshare_debug) fprintf(stderr, "CirculateRequest parent: 0x%lx  win: 0x%lx\n", ev.xcirculaterequest.parent, ev.xcirculaterequest.window);
1852#endif
1853			break;
1854		case CirculateNotify:
1855#if 0
1856			PREFIX
1857			if(appshare_debug) fprintf(stderr, "CirculateNotify\n");
1858#endif
1859			break;
1860		case PropertyNotify:
1861#if 0
1862			PREFIX
1863			if(appshare_debug) fprintf(stderr, "PropertyNotify\n");
1864#endif
1865			if (cmd_atom != None && ev.xproperty.atom == cmd_atom) {
1866				got_prop_cmd++;
1867			}
1868			break;
1869		case ReparentNotify:
1870			PREFIX
1871			if(appshare_debug) fprintf(stderr, "ReparentNotify parent: 0x%lx  win: 0x%lx\n", ev.xreparent.parent, ev.xreparent.window);
1872			if (ours(ev.xreparent.window)) {
1873				if (ours(ev.xreparent.parent)) {
1874					destroy_win(ev.xreparent.window);
1875				} else if (ev.xreparent.parent == root) {
1876					/* ??? */
1877				}
1878			}
1879			break;
1880		default:
1881			PREFIX
1882			if(appshare_debug) fprintf(stderr, "Unknown: %d\n", ev.type);
1883			break;
1884		}
1885	}
1886#endif
1887}
1888
1889int appshare_main(int argc, char *argv[]) {
1890	int i;
1891	char *app_str = NULL;
1892	char *dpy_str = NULL;
1893	long xselectinput = 0;
1894#if NO_X11
1895	exiter("not compiled with X11\n", 1);
1896#else
1897	for (i=0; i < WMAX; i++) {
1898		watch[i] = None;
1899		state[i] = 0;
1900	}
1901	for (i=0; i < AMAX; i++) {
1902		apps[i]  = None;
1903	}
1904	for (i=0; i < CMAX; i++) {
1905		clients[i] = NULL;
1906	}
1907
1908	x11vnc = strdup(argv[0]);
1909
1910	for (i=1; i < argc; i++) {
1911		int end = (i == argc-1) ? 1 : 0;
1912		char *s = argv[i];
1913		if (strstr(s, "--") == s) {
1914			s++;
1915		}
1916
1917		if (!strcmp(s, "-h") || !strcmp(s, "-help")) {
1918			fprintf(stdout, "%s", usage);
1919			exit(0);
1920		} else if (!strcmp(s, "-id")) {
1921			id_opt = "-id";
1922			if (end) exiter("no -id value supplied\n", 1);
1923			app_str = strdup(argv[++i]);
1924		} else if (!strcmp(s, "-sid")) {
1925			id_opt = "-sid";
1926			if (end) exiter("no -sid value supplied\n", 1);
1927			app_str = strdup(argv[++i]);
1928		} else if (!strcmp(s, "-connect") || !strcmp(s, "-connect_or_exit") || !strcmp(s, "-coe")) {
1929			if (end) exiter("no -connect value supplied\n", 1);
1930			connect_to = strdup(argv[++i]);
1931		} else if (!strcmp(s, "-control")) {
1932			if (end) exiter("no -control value supplied\n", 1);
1933			control = strdup(argv[++i]);
1934			if (!strcmp(control, "shell")) {
1935				free(control);
1936				control = strdup("internal");
1937				shell = 1;
1938			}
1939		} else if (!strcmp(s, "-trackdir")) {
1940			if (end) exiter("no -trackdir value supplied\n", 1);
1941			trackdir = strdup(argv[++i]);
1942		} else if (!strcmp(s, "-display")) {
1943			if (end) exiter("no -display value supplied\n", 1);
1944			dpy_str = strdup(argv[++i]);
1945			set_env("DISPLAY", dpy_str);
1946		} else if (!strcmp(s, "-delay")) {
1947			if (end) exiter("no -delay value supplied\n", 1);
1948			helper_delay = atof(argv[++i]);
1949		} else if (!strcmp(s, "-args")) {
1950			if (end) exiter("no -args value supplied\n", 1);
1951			x11vnc_args = strdup(argv[++i]);
1952		} else if (!strcmp(s, "-env")) {
1953			if (end) exiter("no -env value supplied\n", 1);
1954			putenv(argv[++i]);
1955		} else if (!strcmp(s, "-debug")) {
1956			appshare_debug++;
1957		} else if (!strcmp(s, "-showmenus")) {
1958			skip_menus = 0;
1959		} else if (!strcmp(s, "-noexit")) {
1960			exit_no_app_win = 0;
1961		} else if (!strcmp(s, "-shell")) {
1962			shell = 1;
1963		} else if (!strcmp(s, "-nocmds") || !strcmp(s, "-safer")) {
1964			fprintf(stderr, "ignoring %s in -appshare mode.\n", s);
1965		} else if (!strcmp(s, "-appshare")) {
1966			;
1967		} else {
1968			fprintf(stderr, "unrecognized 'x11vnc -appshare' option: %s\n", s);
1969			exiter("", 1);
1970		}
1971	}
1972
1973	if (getenv("X11VNC_APPSHARE_DEBUG")) {
1974		appshare_debug = atoi(getenv("X11VNC_APPSHARE_DEBUG"));
1975	}
1976
1977	/* let user override name for multiple instances: */
1978	if (getenv("X11VNC_APPSHARE_COMMAND_PROPNAME")) {
1979		cmd_atom_str = strdup(getenv("X11VNC_APPSHARE_COMMAND_PROPNAME"));
1980	}
1981	if (getenv("X11VNC_APPSHARE_TICKER_PROPNAME")) {
1982		ticker_atom_str = strdup(getenv("X11VNC_APPSHARE_TICKER_PROPNAME"));
1983	}
1984
1985	if (shell) {
1986		if (!control || strcmp(control, "internal")) {
1987			exiter("mode -shell requires '-control internal'\n", 1);
1988		}
1989	}
1990
1991	if (connect_to == NULL && control != NULL) {
1992		struct stat sb;
1993		if (stat(control, &sb) == 0) {
1994			int len = 100 + sb.st_size;
1995			FILE *f = fopen(control, "r");
1996
1997			if (f) {
1998				char *line = (char *) malloc(len);
1999				connect_to = (char *) calloc(2 * len, 1);
2000				while (fgets(line, len, f) != NULL) {
2001					char *q = strchr(line, '\n');
2002					if (q) *q = '\0';
2003					q = lblanks(line);
2004					if (q[0] == '#') {
2005						continue;
2006					}
2007					if (connect_to[0] != '\0') {
2008						strcat(connect_to, ",");
2009					}
2010					strcat(connect_to, q);
2011				}
2012				fclose(f);
2013			}
2014			fprintf(stderr, "set -connect to: %s\n", connect_to);
2015		}
2016	}
2017	if (0 && connect_to == NULL && control == NULL) {
2018		exiter("no -connect host or -control file specified.\n", 1);
2019	}
2020
2021	if (control) {
2022		pid_t pid;
2023		parent_pid = getpid();
2024		pid = fork();
2025		if (pid == (pid_t) -1) {
2026			;
2027		} else if (pid == 0) {
2028			be_helper_pid(dpy_str);
2029			exit(0);
2030		} else {
2031			helper_pid = pid;
2032		}
2033	}
2034
2035	dpy = XOpenDisplay(dpy_str);
2036	if (!dpy) {
2037		exiter("cannot open display\n", 1);
2038	}
2039
2040	root = DefaultRootWindow(dpy);
2041
2042	xselectinput = SubstructureNotifyMask;
2043	if (helper_pid > 0) {
2044		ticker_atom = XInternAtom(dpy, ticker_atom_str, False);
2045		xselectinput |= PropertyChangeMask;
2046	}
2047	XSelectInput(dpy, root, xselectinput);
2048
2049	cmd_atom = XInternAtom(dpy, cmd_atom_str, False);
2050
2051	init_cmask();
2052
2053	sprintf(unique_tag, "X11VNC_APPSHARE_TAG=%d-tag", getpid());
2054
2055	start_time = dnow();
2056
2057	if (app_str == NULL) {
2058		exiter("no -id/-sid window specified.\n", 1);
2059	} else {
2060		char *p, *str = strdup(app_str);
2061		char *alist[AMAX];
2062		int i, n = 0;
2063
2064		p = strtok(str, ",");
2065		while (p) {
2066			if (n >= AMAX) {
2067				fprintf(stderr, "ran out of app slots: %s\n", app_str);
2068				exiter("", 1);
2069			}
2070			alist[n++] = strdup(p);
2071			p = strtok(NULL, ",");
2072		}
2073		free(str);
2074
2075		for (i=0; i < n; i++) {
2076			Window app = None;
2077			p = alist[i];
2078			app = parse_win(p);
2079			free(p);
2080
2081			if (app != None) {
2082				if (!ours(app)) {
2083					add_app(app);
2084				}
2085			}
2086		}
2087	}
2088
2089	set_trackdir();
2090
2091	signal(SIGINT,  appshare_cleanup);
2092	signal(SIGTERM, appshare_cleanup);
2093
2094	rfbLogEnable(0);
2095
2096	if (connect_to) {
2097		char *p, *str = strdup(connect_to);
2098		int n = 0;
2099		p = strtok(str, ",");
2100		while (p) {
2101			clients[n++] = strdup(p);
2102			p = strtok(NULL, ",");
2103		}
2104		free(str);
2105	} else {
2106		connect_to = strdup("");
2107	}
2108
2109	for (i=0; i < AMAX; i++) {
2110		if (apps[i] == None) {
2111			continue;
2112		}
2113		fprintf(stdout, "Using app win: 0x%08lx  root: 0x%08lx\n", apps[i], root);
2114	}
2115	fprintf(stdout, "\n");
2116
2117	monitor();
2118
2119	appshare_cleanup(0);
2120
2121#endif
2122	return 0;
2123}
2124
2125