diff options
author | runge <runge@karlrunge.com> | 2009-12-02 22:09:51 -0500 |
---|---|---|
committer | runge <runge@karlrunge.com> | 2009-12-02 22:09:51 -0500 |
commit | 00a9a0ea4d0f642b34b4423ea867099b52edf078 (patch) | |
tree | c9df2a624681358103c80e79847fd415cf3a8e2f /x11vnc/appshare.c | |
parent | f40b0111827677625d81b7b7fcd001ce285adf69 (diff) | |
download | libtdevnc-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.c | 2116 |
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; +} + |