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