summaryrefslogtreecommitdiffstats
path: root/x11vnc/appshare.c
diff options
context:
space:
mode:
authorrunge <runge@karlrunge.com>2009-12-02 22:09:51 -0500
committerrunge <runge@karlrunge.com>2009-12-02 22:09:51 -0500
commit00a9a0ea4d0f642b34b4423ea867099b52edf078 (patch)
treec9df2a624681358103c80e79847fd415cf3a8e2f /x11vnc/appshare.c
parentf40b0111827677625d81b7b7fcd001ce285adf69 (diff)
downloadlibtdevnc-00a9a0ea4d0f642b34b4423ea867099b52edf078.tar.gz
libtdevnc-00a9a0ea4d0f642b34b4423ea867099b52edf078.zip
x11vnc: -appshare mode for sharing an application windows instead of the
entire desktop. map port + 5500 in reverse connect. Add id_cmd remote control functions for id (and other) windows. Allow zero port in SSL reverse connections. Adjust delays between multiple reverse connections; X11VNC_REVERSE_SLEEP_MAX env var. Add some missing mutex locks; add INPUT_LOCK and threads_drop_input. More safety in -threads mode for new framebuffer change. Fix some stderr leaking in -inetd mode.
Diffstat (limited to 'x11vnc/appshare.c')
-rw-r--r--x11vnc/appshare.c2116
1 files changed, 2116 insertions, 0 deletions
diff --git a/x11vnc/appshare.c b/x11vnc/appshare.c
new file mode 100644
index 0000000..72c2bdd
--- /dev/null
+++ b/x11vnc/appshare.c
@@ -0,0 +1,2116 @@
+/*
+ Copyright (C) 2002-2009 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"
+" 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"
+;
+
+#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) {
+ 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 LIBVNCSERVER_HAVE_SYS_WAIT_H && LIBVNCSERVER_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;
+
+ 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;
+ }
+ }
+ }
+ }
+ 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;
+
+ 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;
+ }
+
+ ok = XQueryTree(dpy, top, &r, &parent, &list, &nchild);
+ if (ok) {
+ int i;
+ for (i=0; i < 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 < nchild; i++) {
+ w = list[i];
+ if (w == None || ours(w)) {
+ continue;
+ }
+ recurse_search(level+1, level_max, w, app, nw);
+ }
+ }
+ if (list) {
+ XFree(list);
+ }
+}
+
+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], *new[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 {
+ new[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] = new[i];
+ client(new[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")) {
+ 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;
+}
+