/* -- gui.c -- */

#include "x11vnc.h"
#include "xevents.h"
#include "win_utils.h"
#include "remote.h"
#include "cleanup.h"
#include "xwrappers.h"
#include "connections.h"

#include "tkx11vnc.h"

#define SYSTEM_TRAY_REQUEST_DOCK    0
#define SYSTEM_TRAY_BEGIN_MESSAGE   1
#define SYSTEM_TRAY_CANCEL_MESSAGE  2
#define XEMBED_VERSION 0
#define XEMBED_MAPPED  (1 << 0)

int icon_mode = 0;		/* hack for -gui tray/icon */
char *icon_mode_file = NULL;
FILE *icon_mode_fh = NULL;
int icon_mode_socks[ICON_MODE_SOCKS];
int tray_manager_ok = 0;
Window tray_request = None;
Window tray_window = None;
int tray_unembed = 0;
pid_t run_gui_pid = 0;


char *get_gui_code(void);
int tray_embed(Window iconwin, int remove);
void do_gui(char *opts, int sleep);


static Window tweak_tk_window_id(Window win);
static int tray_manager_running(Display *d, Window *manager);
static void run_gui(char *gui_xdisplay, int connect_to_x11vnc, int start_x11vnc,
    int simple_gui, pid_t parent, char *gui_opts);


char *get_gui_code(void) {
	return gui_code;
}

static Window tweak_tk_window_id(Window win) {
	char *name = NULL;
	Window parent, new;

#if NO_X11
	return None;
#else
	/* hack for tk, does not report outermost window */
	new = win;
	parent = parent_window(win, &name);
	if (parent && name != NULL) {
		lowercase(name);
		if (strstr(name, "wish") || strstr(name, "x11vnc")) {
			new = parent;
			rfbLog("tray_embed: using parent: %s\n", name);
		}
	}
	if (name != NULL) {
		XFree_wr(name);
	}
	return new;
#endif	/* NO_X11 */
}

int tray_embed(Window iconwin, int remove) {
	XEvent ev;
	XErrorHandler old_handler;
	Window manager;
	Atom xembed_info;
	Atom tatom;
	XWindowAttributes attr;
	long info[2] = {XEMBED_VERSION, XEMBED_MAPPED};
	long data = 0;

	RAWFB_RET(0)
#if NO_X11
	return 0;
#else

	if (remove) {
		if (!valid_window(iconwin, &attr, 1)) {
			return 0;
		}
		iconwin = tweak_tk_window_id(iconwin);
		trapped_xerror = 0;
		old_handler = XSetErrorHandler(trap_xerror);

		/*
		 * unfortunately no desktops seem to obey this
		 * part of the XEMBED spec yet...
		 */
		XReparentWindow(dpy, iconwin, rootwin, 0, 0);

		XSetErrorHandler(old_handler);
		if (trapped_xerror) {
			trapped_xerror = 0;
			return 0;
		}
		trapped_xerror = 0;
		return 1;
	}

	xembed_info = XInternAtom(dpy, "_XEMBED_INFO", False);
	if (xembed_info == None) {
		return 0;
	}

	if (!tray_manager_running(dpy, &manager)) {
		return 0;
	}

	memset(&ev, 0, sizeof(ev));
	ev.xclient.type = ClientMessage;
	ev.xclient.window = manager;
	ev.xclient.message_type = XInternAtom(dpy, "_NET_SYSTEM_TRAY_OPCODE",
	    False);
	ev.xclient.format = 32;
	ev.xclient.data.l[0] = CurrentTime;
	ev.xclient.data.l[1] = SYSTEM_TRAY_REQUEST_DOCK;
	ev.xclient.data.l[2] = iconwin;
	ev.xclient.data.l[3] = 0;
	ev.xclient.data.l[4] = 0;

	if (!valid_window(iconwin, &attr, 1)) {
		return 0;
	}

	iconwin = tweak_tk_window_id(iconwin);
	ev.xclient.data.l[2] = iconwin;

	XUnmapWindow(dpy, iconwin);

	trapped_xerror = 0;
	old_handler = XSetErrorHandler(trap_xerror);

	XSendEvent(dpy, manager, False, NoEventMask, &ev);
	XSync(dpy, False);

	if (trapped_xerror) {
		XSetErrorHandler(old_handler);
		trapped_xerror = 0;
		return 0;
	}

	XChangeProperty(dpy, iconwin, xembed_info, xembed_info, 32,
	    PropModeReplace, (unsigned char *)&info, 2);

	/* kludge for KDE evidently needed... */
	tatom = XInternAtom(dpy, "KWM_DOCKWINDOW", False);
	XChangeProperty(dpy, iconwin, tatom, tatom, 32, PropModeReplace,
	    (unsigned char *)&data, 1);
	tatom = XInternAtom(dpy, "_KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR", False);
	XChangeProperty(dpy, iconwin, tatom, XA_WINDOW, 32, PropModeReplace,
	    (unsigned char *)&data, 1);

	XSetErrorHandler(old_handler);
	trapped_xerror = 0;
	return 1;
#endif	/* NO_X11 */
}

static int tray_manager_running(Display *d, Window *manager) {
	char tray_string[100];
	Atom tray_manager;
	Window tray_win;

	RAWFB_RET(0)
#if NO_X11
	return 0;
#else

	if (manager) {
		*manager = None;
	}
	sprintf(tray_string, "_NET_SYSTEM_TRAY_S%d", scr);

	tray_manager = XInternAtom(d, tray_string, True);
	if (tray_manager == None) {
		return 0;
	}

	tray_win = XGetSelectionOwner(d, tray_manager);
	if (manager) {
		*manager = tray_win;
	}

	if (tray_win == None) {
		return 0;
	} else {
		return 1;
	}
#endif	/* NO_X11 */
}

static char *gui_geometry = NULL;
static int icon_in_tray = 0;
static char *icon_mode_embed_id = NULL;
static char *icon_mode_font = NULL;
static char *icon_mode_params = NULL;

static int got_sigusr1 = 0;

static void sigusr1 (int sig) {
	got_sigusr1 = 1;
	if (0) sig = 0;
}

static void run_gui(char *gui_xdisplay, int connect_to_x11vnc, int start_x11vnc,
    int simple_gui, pid_t parent, char *gui_opts) {
	char *x11vnc_xdisplay = NULL;
	char extra_path[] = ":/usr/local/bin:/usr/bin/X11:/usr/sfw/bin"
	    ":/usr/X11R6/bin:/usr/openwin/bin:/usr/dt/bin";
	char cmd[100];
	char *wish = NULL, *orig_path, *full_path, *tpath, *p;
	char *old_xauth = NULL;
	int try_max = 4, sleep = 300, totms;
	pid_t mypid = getpid();
	FILE *pipe, *tmpf;

if (0) fprintf(stderr, "run_gui: %s -- %d %d\n", gui_xdisplay, connect_to_x11vnc, (int) parent);
	if (*gui_code == '\0') {
		rfbLog("gui: gui not compiled into this program.\n");
		exit(0);
	}
	if (getenv("DISPLAY") != NULL) {
		/* worst case */
		x11vnc_xdisplay = strdup(getenv("DISPLAY"));
	}
	if (use_dpy) {
		/* better */
		x11vnc_xdisplay = strdup(use_dpy);
	}
	if (connect_to_x11vnc) {
		int rc, i;
		rfbLogEnable(1);
		if (! client_connect_file) {
			if (getenv("XAUTHORITY") != NULL) {
				old_xauth = strdup(getenv("XAUTHORITY"));
			} else {
				old_xauth = strdup("");
			}
			dpy = XOpenDisplay_wr(x11vnc_xdisplay); 
			if (! dpy && auth_file) {
				set_env("XAUTHORITY", auth_file);
				dpy = XOpenDisplay_wr(x11vnc_xdisplay);
			}
			if (! dpy && ! x11vnc_xdisplay) {
				/* worstest case */
				x11vnc_xdisplay = strdup(":0");
				dpy = XOpenDisplay_wr(x11vnc_xdisplay); 
			}
			if (! dpy) {
				rfbLog("gui: could not open x11vnc "
				    "display: %s\n", NONUL(x11vnc_xdisplay));
#ifdef MACOSX
				goto macjump;
#endif
				exit(1);
			}
			scr = DefaultScreen(dpy);
			rootwin = RootWindow(dpy, scr);
			initialize_vnc_connect_prop();
			initialize_x11vnc_remote_prop();
		}

		macjump:
		
		signal(SIGUSR1, sigusr1);
		got_sigusr1 = 0;
		totms = 0;
		while (totms < 3500) {
			usleep(50*1000);
			totms += 50;
			if (got_sigusr1) {
				fprintf(stderr, "\n");
				if (! quiet) rfbLog("gui: got SIGUSR1\n");
				break;
			}
			if (! start_x11vnc && totms >= 150) {
				break;
			}
		}
		signal(SIGUSR1, SIG_DFL);
		if (! got_sigusr1) fprintf(stderr, "\n");

		if (!quiet && ! got_sigusr1) {
			rfbLog("gui: trying to contact a x11vnc server at X"
			    " display %s ...\n", NONUL(x11vnc_xdisplay));
		}

		for (i=0; i<try_max; i++) {
			if (! got_sigusr1) {
				if (!quiet) {
					rfbLog("gui: pinging %s try=%d ...\n",
					    NONUL(x11vnc_xdisplay), i+1);
				}
				rc = send_remote_cmd("qry=ping", 1, 1);
				if (rc == 0) {
					break;
				}
			} else {
				rc = 0;
				break;
			}
			if (parent && mypid != parent && kill(parent, 0) != 0) {
				rfbLog("gui: parent process %d has gone"
				    " away: bailing out.\n", parent);
				rc = 1;
				break;
			}
			usleep(sleep*1000);
		}
		set_env("X11VNC_XDISPLAY", x11vnc_xdisplay);
		if (getenv("XAUTHORITY") != NULL) {
			set_env("X11VNC_AUTH_FILE", getenv("XAUTHORITY"));
		}
		if (rc == 0) {
			rfbLog("gui: ping succeeded.\n");
			set_env("X11VNC_CONNECT", "1");
		} else {
			rfbLog("gui: could not connect to: '%s', try"
			    " again manually.\n", x11vnc_xdisplay);
		}
		if (client_connect_file) {
			set_env("X11VNC_CONNECT_FILE", client_connect_file);
		}
		if (dpy)  {
			XCloseDisplay_wr(dpy);
			dpy = NULL;
		}
		if (old_xauth) {
			if (*old_xauth == '\0') {
				/* wasn't set, hack it out if it is now */
				char *xauth = getenv("XAUTHORITY");
				if (xauth) {
					*(xauth-2) = '_';	/* yow */
				}
			} else {
				set_env("XAUTHORITY", old_xauth);
			}
			free(old_xauth);
		}
		rfbLogEnable(0);
	}

	orig_path = getenv("PATH");
	if (! orig_path) {
		orig_path = strdup("/bin:/usr/bin:/usr/bin/X11");
	}
	full_path = (char *) malloc(strlen(orig_path)+strlen(extra_path)+1);
	strcpy(full_path, orig_path);
	strcat(full_path, extra_path);

	tpath = strdup(full_path);
	p = strtok(tpath, ":");

	while (p) {
		char *try;
		struct stat sbuf;
		char *wishes[] = {"wish", "wish8.3", "wish8.4", "wish8.5",
		    "wish8.0"};
		int nwishes = 3, i;

		try = (char *) malloc(strlen(p) + 1 + strlen("wish8.4") + 1);
		for (i=0; i<nwishes; i++) {
			sprintf(try, "%s/%s", p, wishes[i]);
			if (stat(try, &sbuf) == 0) {
				/* assume executable, should check mode */
				wish = wishes[i];
			}
		}
		free(try);
		if (wish) {
			break;
		}
		p = strtok(NULL, ":");
	}
	free(tpath);
	if (!wish) {
		wish = strdup("wish");
	}
	set_env("PATH", full_path);
	set_env("DISPLAY", gui_xdisplay);
	set_env("X11VNC_PROG", program_name);
	set_env("X11VNC_CMDLINE", program_cmdline);
	set_env("X11VNC_WISHCMD", wish);
	if (simple_gui) {
		set_env("X11VNC_SIMPLE_GUI", "1");
	}
	if (gui_opts) {
		set_env("X11VNC_GUI_PARAMS", gui_opts);
	}
	if (gui_geometry) {
		set_env("X11VNC_GUI_GEOM", gui_geometry);
	}
	if (start_x11vnc) {
		set_env("X11VNC_STARTED", "1");
	}
	if (icon_mode) {
		set_env("X11VNC_ICON_MODE", "1");
		if (icon_mode_file) {
			set_env("X11VNC_CLIENT_FILE", icon_mode_file);
		}
		if (icon_in_tray) {
			if (tray_manager_ok) {
				set_env("X11VNC_ICON_MODE", "TRAY:RUNNING");
			} else {
				set_env("X11VNC_ICON_MODE", "TRAY");
			}
		} else {
			set_env("X11VNC_ICON_MODE", "ICON");
		}
		if (icon_mode_params) {
			char *p, *str = strdup(icon_mode_params);
			p = strtok(str, ":-/,.+");
			while (p) {
				if(strstr(p, "setp") == p) {
					set_env("X11VNC_ICON_SETPASS", "1");
				} else if(strstr(p, "noadvanced") == p) {
					set_env("X11VNC_ICON_NOADVANCED", "1");
				} else if(strstr(p, "minimal") == p) {
					set_env("X11VNC_ICON_MINIMAL", "1");
				} else if (strstr(p, "0x") == p) {
					set_env("X11VNC_ICON_EMBED_ID", p);
					icon_mode_embed_id = strdup(p);
				}
				p = strtok(NULL, ":-/,.+");
			}
			free(str);
		}
	}
	if (icon_mode_font) {
		set_env("X11VNC_ICON_FONT", icon_mode_font);
	}

	/* gui */
	if (no_external_cmds || !cmd_ok("gui")) {
		fprintf(stderr, "cannot run external commands in -nocmds "
		    "mode:\n");
		fprintf(stderr, "   \"%s\"\n", "gui + wish");
		fprintf(stderr, "   exiting.\n");
		fflush(stderr);
		exit(1);
	}

	tmpf = tmpfile();
	if (tmpf == NULL) {
		/* if no tmpfile, use a pipe */
		if (icon_mode_embed_id) {
			if (strlen(icon_mode_embed_id) < 20) {
				strcat(cmd, " -use ");
				strcat(cmd, icon_mode_embed_id);
			}
		}
		close_exec_fds();
		pipe = popen(cmd, "w");
		if (! pipe) {
			fprintf(stderr, "could not run: %s\n", cmd);
			perror("popen");
		}
		fprintf(pipe, "%s", gui_code);
		pclose(pipe);
	} else {
		/*
		 * we prefer a tmpfile since then this x11vnc process
		 * will then be gone, otherwise the x11vnc program text
		 * will still be in use.
		 */
		int n = fileno(tmpf);
		fprintf(tmpf, "%s", gui_code);
		fflush(tmpf);
		rewind(tmpf);
		dup2(n, 0);
		close(n);
		if (icon_mode_embed_id) {
			execlp(wish, wish, "-", "-use", icon_mode_embed_id,
			    (char *) NULL); 
		} else {
			execlp(wish, wish, "-", (char *) NULL); 
		}
		fprintf(stderr, "could not exec wish: %s -\n", wish);
		perror("execlp");
	}
	exit(0);
}

void do_gui(char *opts, int sleep) {
	char *s, *p;
	char *old_xauth = NULL;
	char *gui_xdisplay = NULL;
	int got_gui_xdisplay = 0;
	int start_x11vnc = 1;
	int connect_to_x11vnc = 0;
	int simple_gui = 0, none_gui = 0;
	Display *test_dpy;

	if (opts) {
		s = strdup(opts);
	} else {
		s = strdup("");
	}

	if (use_dpy) {
		/* worst case */
		gui_xdisplay = strdup(use_dpy);
		
	}
	if (getenv("DISPLAY") != NULL) {
		/* better */
		gui_xdisplay = strdup(getenv("DISPLAY"));
	}

	p = strtok(s, ",");

	while(p) {
		if (*p == '\0') {
			;
		} else if (strchr(p, ':') != NULL) {
			/* best */
			if (gui_xdisplay) {
				free(gui_xdisplay);
			}
			gui_xdisplay = strdup(p);
			got_gui_xdisplay = 1;
		} else if (!strcmp(p, "wait")) {
			start_x11vnc = 0;
			connect_to_x11vnc = 0;
		} else if (!strcmp(p, "none")) {
			none_gui = 1;
		} else if (!strcmp(p, "conn") || !strcmp(p, "connect")) {
			start_x11vnc = 0;
			connect_to_x11vnc = 1;
		} else if (!strcmp(p, "ez") || !strcmp(p, "simple")) {
			simple_gui = 1;
		} else if (strstr(p, "iconfont") == p) {
			char *q;
			if ((q = strchr(p, '=')) != NULL) {
				icon_mode_font = strdup(q+1);
			}
		} else if (!strcmp(p, "full")) {
			;
		} else if (strstr(p, "tray") == p || strstr(p, "icon") == p) {
			char *q;
			icon_mode = 1;
			if ((q = strchr(p, '=')) != NULL) {
				icon_mode_params = strdup(q+1);
				if (strstr(icon_mode_params, "setp")) {
					deny_all = 1;
				}
			}
			if (strstr(p, "tray") == p) {
				icon_in_tray = 1;
			}
		} else if (strstr(p, "geom") == p) {
			char *q;
			if ((q = strchr(p, '=')) != NULL) {
				gui_geometry = strdup(q+1);
			}
		} else {
			fprintf(stderr, "unrecognized gui opt: %s\n", p);
		}
		
		p = strtok(NULL, ",");
	}
	free(s);

	if (none_gui) {
		if (!start_x11vnc) {
			exit(0);
		}
		return;
	}
	if (start_x11vnc) {
		connect_to_x11vnc = 1;
	}

#ifdef MACOSX
	goto startit;
#endif

	if (icon_mode && !got_gui_xdisplay) {
		/* for tray mode, prefer the polled DISPLAY */
		if (use_dpy) {
			if (gui_xdisplay) {
				free(gui_xdisplay);
			}
			gui_xdisplay = strdup(use_dpy);
		}
	}

	if (! gui_xdisplay) {
		fprintf(stderr, "error: cannot determine X DISPLAY for gui"
		    " to display on.\n");
		exit(1);
	}
	if (!quiet) {
		fprintf(stderr, "starting gui, trying display: %s\n",
		    gui_xdisplay);
	}
	test_dpy = XOpenDisplay_wr(gui_xdisplay);
	if (! test_dpy && auth_file) {
		if (getenv("XAUTHORITY") != NULL) {
			old_xauth = strdup(getenv("XAUTHORITY"));
		}
		set_env("XAUTHORITY", auth_file);
		test_dpy = XOpenDisplay_wr(gui_xdisplay);
	}
	if (! test_dpy) {
		if (! old_xauth && getenv("XAUTHORITY") != NULL) {
			old_xauth = strdup(getenv("XAUTHORITY"));
		}
		set_env("XAUTHORITY", "");
		test_dpy = XOpenDisplay_wr(gui_xdisplay);
	}
	if (! test_dpy) {
		fprintf(stderr, "error: cannot connect to gui X DISPLAY: %s\n",
		    gui_xdisplay);
		exit(1);
	}
	if (icon_mode && icon_in_tray) {
		if (tray_manager_running(test_dpy, NULL)) {
			tray_manager_ok = 1;
		} else {
			tray_manager_ok = 0;
		}
	}
	XCloseDisplay_wr(test_dpy);

	startit:

	if (start_x11vnc) {

#if LIBVNCSERVER_HAVE_FORK
		/* fork into the background now */
		int p;
		pid_t parent = getpid();

		if (icon_mode) {
			char tf[100]; 
			double dn = dnow();
			struct stat sbuf;
			/* FIXME */
			dn = dn - ((int) dn);
			sprintf(tf, "/tmp/x11vnc.tray%d%d", (int) (1000000*dn),
			    (int) getpid());
			unlink(tf);
			/* race begins.. */
			if (stat(tf, &sbuf) == 0) {
				icon_mode = 0;
			} else {
				icon_mode_fh = fopen(tf, "w");
				if (! icon_mode_fh) {
					icon_mode = 0;
				} else {
					chmod(tf, 0400);
					icon_mode_file = strdup(tf);
					fprintf(icon_mode_fh, "none\n");
					fprintf(icon_mode_fh, "none\n");
					fflush(icon_mode_fh);
					if (! got_connect_once) {
						/* want -forever for tray */
						connect_once = 0;
					}
				}
			}
		}

		if ((p = fork()) > 0)  {
			;	/* parent */
		} else if (p == -1) {
			fprintf(stderr, "could not fork\n");
			perror("fork");
			clean_up_exit(1);
		} else {
			if (sleep > 0) {
				usleep(sleep * 1000 * 1000);
			}
			run_gui(gui_xdisplay, connect_to_x11vnc, start_x11vnc,
			    simple_gui, parent, opts);
			exit(1);
		}
		if (connect_to_x11vnc) {
			run_gui_pid = p;
		}
#else
		fprintf(stderr, "system does not support fork: start "
		    "x11vnc in the gui.\n");
		start_x11vnc = 0;
#endif
	}
	if (!start_x11vnc) {
		run_gui(gui_xdisplay, connect_to_x11vnc, start_x11vnc,
		    simple_gui, 0, opts);
		exit(1);
	}
	if (old_xauth) {
		set_env("XAUTHORITY", old_xauth);
	}
}