C++程序  |  1207行  |  28.52 KB

/*
   Copyright (C) 2002-2010 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.
*/

/* -- pointer.c -- */

#include "x11vnc.h"
#include "xwrappers.h"
#include "keyboard.h"
#include "xinerama.h"
#include "xrecord.h"
#include "win_utils.h"
#include "cursor.h"
#include "userinput.h"
#include "connections.h"
#include "cleanup.h"
#include "unixpw.h"
#include "v4l.h"
#include "linuxfb.h"
#include "uinput.h"
#include "scan.h"
#include "macosx.h"
#include "screen.h"

int pointer_queued_sent = 0;

void initialize_pointer_map(char *pointer_remap);
void do_button_mask_change(int mask, int button);
void pointer_event(int mask, int x, int y, rfbClientPtr client);
void initialize_pipeinput(void);
int check_pipeinput(void);
void update_x11_pointer_position(int x, int y);


static void buttonparse(int from, char **s);
static void update_x11_pointer_mask(int mask);
static void pipe_pointer(int mask, int x, int y, rfbClientPtr client);

/*
 * pointer event (motion and button click) handling routines.
 */
typedef struct ptrremap {
	KeySym keysym;
	KeyCode keycode;
	int end;
	int button;
	int down;
	int up;
} prtremap_t;

#define MAX_BUTTON_EVENTS 50
static prtremap_t pointer_map[MAX_BUTTONS+1][MAX_BUTTON_EVENTS];

/*
 * For parsing the -buttonmap sections, e.g. "4" or ":Up+Up+Up:"
 */
static void buttonparse(int from, char **s) {
#if (0 && NO_X11)
	if (!from || !s) {}
	return;
#else
	char *q;
	int to, i;
	int modisdown[256];

	q = *s;

	for (i=0; i<256; i++) {
		modisdown[i] = 0;
	}

	if (*q == ':') {
		/* :sym1+sym2+...+symN: format */
		int l = 0, n = 0;
		char list[1000];
		char *t, *kp = q + 1;
		KeyCode kcode;

		while (*(kp+l) != ':' && *(kp+l) != '\0') {
			/* loop to the matching ':' */
			l++;
			if (l >= 1000) {
				rfbLog("buttonparse: keysym list too long: "
				    "%s\n", q);
				break;
			}
		}
		*(kp+l) = '\0';
		strncpy(list, kp, l);
		list[l] = '\0';
		rfbLog("remap button %d using \"%s\"\n", from, list);

		/* loop over tokens separated by '+' */
		t = strtok(list, "+");
		while (t) {
			KeySym ksym;
			unsigned int ui;
			int i;
			if (n >= MAX_BUTTON_EVENTS - 20) {
				rfbLog("buttonparse: too many button map "
					"events: %s\n", list);
				break;
			}
			if (sscanf(t, "0x%x", &ui) == 1) {
				ksym = (KeySym) ui;	/* hex value */
			} else {
				X_LOCK;
				ksym = XStringToKeysym(t); /* string value */
				X_UNLOCK;
			}
			if (ksym == NoSymbol) {
				/* see if Button<N> "keysym" was used: */
				if (sscanf(t, "Button%d", &i) == 1) {
					rfbLog("   event %d: button %d\n",
					    from, n+1, i);
					if (i == 0) i = -1; /* bah */
					pointer_map[from][n].keysym  = NoSymbol;
					pointer_map[from][n].keycode = NoSymbol;
					pointer_map[from][n].button = i;
					pointer_map[from][n].end    = 0;
					pointer_map[from][n].down   = 0;
					pointer_map[from][n].up     = 0;
				} else {
					rfbLog("buttonparse: ignoring unknown "
					    "keysym: %s\n", t);
					n--;
				}
			} else if (dpy) {
				/*
				 * XXX may not work with -modtweak or -xkb
				 */
				char *str;
				X_LOCK;
#if NO_X11
				kcode = NoSymbol;
#else
				kcode = XKeysymToKeycode(dpy, ksym);
#endif

				pointer_map[from][n].keysym  = ksym;
				pointer_map[from][n].keycode = kcode;
				pointer_map[from][n].button = 0;
				pointer_map[from][n].end    = 0;
				if (! ismodkey(ksym) ) {
					/* do both down then up */
					pointer_map[from][n].down = 1;
					pointer_map[from][n].up   = 1;
				} else {
					if (modisdown[kcode]) {
						pointer_map[from][n].down = 0;
						pointer_map[from][n].up   = 1;
						modisdown[kcode] = 0;
					} else {
						pointer_map[from][n].down = 1;
						pointer_map[from][n].up   = 0;
						modisdown[kcode] = 1;
					}
				}
				str = XKeysymToString(ksym);
				rfbLog("   event %d: keysym %s (0x%x) -> "
				    "keycode 0x%x down=%d up=%d\n", n+1,
				    str ? str : "null", ksym, kcode,
				    pointer_map[from][n].down,
				    pointer_map[from][n].up);
				X_UNLOCK;
			}
			t = strtok(NULL, "+");
			n++;
		}

		/* we must release any modifiers that are still down: */
		for (i=0; i<256; i++) {
			kcode = (KeyCode) i;
			if (n >= MAX_BUTTON_EVENTS) {
				rfbLog("buttonparse: too many button map "
					"events: %s\n", list);
				break;
			}
			if (modisdown[kcode]) {
				pointer_map[from][n].keysym  = NoSymbol;
				pointer_map[from][n].keycode = kcode;
				pointer_map[from][n].button = 0;
				pointer_map[from][n].end    = 0;
				pointer_map[from][n].down = 0;
				pointer_map[from][n].up   = 1;
				modisdown[kcode] = 0;
				n++;
			}
		}

		/* advance the source pointer position */
		(*s) += l+2;
	} else {
		/* single digit format */
		char str[2];
		str[0] = *q;
		str[1] = '\0';

		to = atoi(str);
		if (to < 1) {
			rfbLog("skipping invalid remap button \"%d\" for button"
			    " %d from string \"%s\"\n",
			    to, from, str);
		} else {
			rfbLog("remap button %d using \"%s\"\n", from, str);
			rfbLog("   button: %d -> %d\n", from, to);
			pointer_map[from][0].keysym  = NoSymbol;
			pointer_map[from][0].keycode = NoSymbol;
			pointer_map[from][0].button = to;
			pointer_map[from][0].end    = 0;
			pointer_map[from][0].down   = 0;
			pointer_map[from][0].up     = 0;
		}
		/* advance the source pointer position */
		(*s)++;
	}
#endif	/* NO_X11 */
}

/*
 * process the -buttonmap string
 */
void initialize_pointer_map(char *pointer_remap) {
	unsigned char map[MAX_BUTTONS];
	int i, k;
	/*
	 * This routine counts the number of pointer buttons on the X
	 * server (to avoid problems, even crashes, if a client has more
	 * buttons).  And also initializes any pointer button remapping
	 * from -buttonmap option.
	 */
	
	if (!raw_fb_str) {
#if NO_X11
		num_buttons = 5;
#else
		X_LOCK;
		num_buttons = XGetPointerMapping(dpy, map, MAX_BUTTONS);
		X_UNLOCK;
		rfbLog("The X server says there are %d mouse buttons.\n", num_buttons);
#endif
	} else {
		num_buttons = 5;
		rfbLog("Manually set num_buttons to: %d\n", num_buttons);
	}

	if (num_buttons < 0) {
		num_buttons = 0;
	}

	/* FIXME: should use info in map[] */
	for (i=1; i<= MAX_BUTTONS; i++) {
		for (k=0; k < MAX_BUTTON_EVENTS; k++) {
			pointer_map[i][k].end = 1;
		}
		pointer_map[i][0].keysym  = NoSymbol;
		pointer_map[i][0].keycode = NoSymbol;
		pointer_map[i][0].button = i;
		pointer_map[i][0].end    = 0;
		pointer_map[i][0].down   = 0;
		pointer_map[i][0].up     = 0;
	}

	if (pointer_remap && *pointer_remap != '\0') {
		/* -buttonmap, format is like: 12-21=2 */
		char *p, *q, *remap = strdup(pointer_remap);	
		int n;

		if ((p = strchr(remap, '=')) != NULL) {
			/* undocumented max button number */
			n = atoi(p+1);	
			*p = '\0';
			if (n < num_buttons || num_buttons == 0) {
				num_buttons = n;
			} else {
				rfbLog("warning: increasing number of mouse "
				    "buttons from %d to %d\n", num_buttons, n);
				num_buttons = n;
			}
		}
		if ((q = strchr(remap, '-')) != NULL) {
			/*
			 * The '-' separates the 'from' and 'to' lists,
			 * then it is kind of like tr(1).  
			 */
			char str[2];
			int from;

			rfbLog("remapping pointer buttons using string:\n");
			rfbLog("   \"%s\"\n", remap);

			p = remap;
			q++;
			i = 0;
			str[1] = '\0';
			while (*p != '-') {
				str[0] = *p;
				from = atoi(str);
				buttonparse(from, &q);
				p++;
			}
		}
		free(remap);
	}
}

/*
 * Send a pointer position event to the X server.
 */
void update_x11_pointer_position(int x, int y) {
#if NO_X11
	RAWFB_RET_VOID
	if (!x || !y) {}
	return;
#else
	int rc;

	RAWFB_RET_VOID

	X_LOCK;
	if (!always_inject && cursor_x == x && cursor_y == y) {
		;
	} else if (use_xwarppointer) {
		/*
		 * off_x and off_y not needed with XWarpPointer since
		 * window is used:
		 */
		XWarpPointer(dpy, None, window, 0, 0, 0, 0, x + coff_x,
		    y + coff_y);
	} else {
		XTestFakeMotionEvent_wr(dpy, scr, x + off_x + coff_x,
		    y + off_y + coff_y, CurrentTime);
	}
	X_UNLOCK;

	if (cursor_x != x || cursor_y != y) {
		last_pointer_motion_time = dnow();
	}

	cursor_x = x;
	cursor_y = y;

	/* record the x, y position for the rfb screen as well. */
	cursor_position(x, y);

	/* change the cursor shape if necessary */
	rc = set_cursor(x, y, get_which_cursor());
	cursor_changes += rc;

	last_event = last_input = last_pointer_input = time(NULL);
#endif	/* NO_X11 */
}

void do_button_mask_change(int mask, int button) {
#if NO_X11
	if (!mask || !button) {}
	return;
#else
	int mb, k, i = button-1;

	/*
	 * this expands to any pointer_map button -> keystrokes
	 * remappings.  Usually just k=0 and we send one button event.
	 */
	for (k=0; k < MAX_BUTTON_EVENTS; k++) {
		int bmask = (mask & (1<<i));

		if (pointer_map[i+1][k].end) {
			break;
		}

		if (pointer_map[i+1][k].button) {
			/* send button up or down */

			mb = pointer_map[i+1][k].button;
			if ((num_buttons && mb > num_buttons) || mb < 1) {
				rfbLog("ignoring mouse button out of "
				    "bounds: %d>%d mask: 0x%x -> 0x%x\n",
				    mb, num_buttons, button_mask, mask);
				continue;
			}
			if (debug_pointer) {
				rfbLog("pointer(): sending button %d"
				    " %s (event %d)\n", mb, bmask
				    ? "down" : "up", k+1);
			}
			XTestFakeButtonEvent_wr(dpy, mb, (mask & (1<<i))
			    ? True : False, CurrentTime);
		} else {
			/* send keysym up or down */
			KeyCode key = pointer_map[i+1][k].keycode;
			int up   = pointer_map[i+1][k].up;
			int down = pointer_map[i+1][k].down;

			if (! bmask) {
				/* do not send keysym on button up */
				continue; 
			}
			if (debug_pointer && dpy) {
				char *str = XKeysymToString(XKeycodeToKeysym(
                                    dpy, key, 0));
				rfbLog("pointer(): sending button %d "
				    "down as keycode 0x%x (event %d)\n",
				    i+1, key, k+1);
				rfbLog("           down=%d up=%d keysym: "
				    "%s\n", down, up, str ? str : "null");
			}
			if (down) {
				XTestFakeKeyEvent_wr(dpy, key, True,
				    CurrentTime);
			}
			if (up) {
				XTestFakeKeyEvent_wr(dpy, key, False,
				    CurrentTime);
			}
		}
	}
#endif	/* NO_X11 */
}

/*
 * Send a pointer button event to the X server.
 */
static void update_x11_pointer_mask(int mask) {
#if NO_X11
	last_event = last_input = last_pointer_input = time(NULL);

	RAWFB_RET_VOID
	if (!mask) {}
	return;
#else
	int snapped = 0, xr_mouse = 1, i;
	last_event = last_input = last_pointer_input = time(NULL);

	RAWFB_RET_VOID

	if (mask != button_mask) {
		last_pointer_click_time = dnow();
	}

	if (nofb) {
		xr_mouse = 0;
	} else if (!strcmp(scroll_copyrect, "never")) {
		xr_mouse = 0;
	} else if (!strcmp(scroll_copyrect, "keys")) {
		xr_mouse = 0;
	} else if (skip_cr_when_scaling("scroll")) {
		xr_mouse = 0;
	} else if (xrecord_skip_button(mask, button_mask)) {
		xr_mouse = 0;
	}

	if (mask && use_xrecord && ! xrecording && xr_mouse) {
		static int px, py, x, y, w, h, got_wm_frame;
		static XWindowAttributes attr;
		Window frame = None, mwin = None;
		int skip = 0;

		if (!button_mask) {
			X_LOCK;
			if (get_wm_frame_pos(&px, &py, &x, &y, &w, &h,
			    &frame, &mwin)) {
				got_wm_frame = 1;
if (debug_scroll > 1) fprintf(stderr, "wm_win: 0x%lx\n", mwin);
				if (mwin != None) {
					if (!valid_window(mwin, &attr, 1)) {
						mwin = None;
					}
				}
			} else {
				got_wm_frame = 0;
			}
			X_UNLOCK;
		}
		if (got_wm_frame) {
			if (wireframe && near_wm_edge(x, y, w, h, px, py)) {
				/* step out of wireframe's way */
				skip = 1;
			} else {
				int ok = 0;
				int btn4 = (1<<3);
				int btn5 = (1<<4);

				if (near_scrollbar_edge(x, y, w, h, px, py)) {
					ok = 1;
				}
				if (mask & (btn4|btn5)) {
					/* scroll wheel mouse */
					ok = 1;
				}
				if (mwin != None) {
					/* skinny internal window */
					int w = attr.width;
					int h = attr.height;
					if (h > 10 * w || w > 10 * h) {
if (debug_scroll > 1) fprintf(stderr, "internal scrollbar: %dx%d\n", w, h);
						ok = 1;
					}
				}
				if (! ok) {
					skip = 1;
				}
			}
		}

		if (! skip) {
			xrecord_watch(1, SCR_MOUSE);
			snapshot_stack_list(0, 0.50);
			snapped = 1;
			if (button_mask) {
				xrecord_set_by_mouse = 1;
			} else {
				update_stack_list();
				xrecord_set_by_mouse = 2;
			}
		}
	}

	if (mask && !button_mask) {
		/* button down, snapshot the stacking list before flushing */
		if (wireframe && !wireframe_in_progress &&
		    strcmp(wireframe_copyrect, "never")) {
			if (! snapped) {
				snapshot_stack_list(0, 0.0);
			}
		}
	}

	X_LOCK;

	/* look for buttons that have be clicked or released: */
	for (i=0; i < MAX_BUTTONS; i++) {
	    if ( (button_mask & (1<<i)) != (mask & (1<<i)) ) {
		if (debug_pointer) {
			rfbLog("pointer(): mask change: mask: 0x%x -> "
			    "0x%x button: %d\n", button_mask, mask,i+1);
		}
		do_button_mask_change(mask, i+1);	/* button # is i+1 */
	    }
	}

	X_UNLOCK;

	/*
	 * Remember the button state for next time and also for the
	 * -nodragging case:
	 */
	button_mask_prev = button_mask;
	button_mask = mask;
#endif	/* NO_X11 */
}

/* for -pipeinput */


static void pipe_pointer(int mask, int x, int y, rfbClientPtr client) {
	int can_input = 0, uid = 0;
	allowed_input_t input;
	ClientData *cd = (ClientData *) client->clientData;
	char hint[MAX_BUTTONS * 20];

	if (pipeinput_int == PIPEINPUT_VID) {
		v4l_pointer_command(mask, x, y, client);
	} else if (pipeinput_int == PIPEINPUT_CONSOLE) {
		console_pointer_command(mask, x, y, client);
	} else if (pipeinput_int == PIPEINPUT_UINPUT) {
		uinput_pointer_command(mask, x, y, client);
	} else if (pipeinput_int == PIPEINPUT_MACOSX) {
		macosx_pointer_command(mask, x, y, client);
	} else if (pipeinput_int == PIPEINPUT_VNC) {
		vnc_reflect_send_pointer(x, y, mask);
	}
	if (pipeinput_fh == NULL) {
		return;
	}

	if (! view_only) {
		get_allowed_input(client, &input);
		if (input.motion || input.button) {
			can_input = 1;	/* XXX distinguish later */
		}
	}

	if (cd) {
		uid = cd->uid;
	}
	if (! can_input) {
		uid = -uid;
	}

	hint[0] = '\0';
	if (mask == button_mask) {
		strcat(hint, "None");
	} else {
		int i, old, newb, m = 1, cnt = 0;
		for (i=0; i<MAX_BUTTONS; i++) {
			char s[20];

			old = button_mask & m;
			newb = mask & m;
			m = m << 1;

			if (old == newb) {
				continue;
			}
			if (hint[0] != '\0') {
				strcat(hint, ",");
			}
			if (newb && ! old) {
				sprintf(s, "ButtonPress-%d", i+1);
				cnt++;
			} else if (! newb && old)  {
				sprintf(s, "ButtonRelease-%d", i+1);
				cnt++;
			}
			strcat(hint, s);
		}
		if (! cnt) {
			strcpy(hint, "None");
		}
	}

	fprintf(pipeinput_fh, "Pointer %d %d %d %d %s\n", uid, x, y,
	    mask, hint);
	fflush(pipeinput_fh);
	check_pipeinput();
}

/*
 * Actual callback from libvncserver when it gets a pointer event.
 * This may queue pointer events rather than sending them immediately
 * to the X server. (see update_x11_pointer*())
 */
void pointer_event(int mask, int x, int y, rfbClientPtr client) {
	allowed_input_t input;
	int sent = 0, buffer_it = 0;
	double now;

	if (threads_drop_input) {
		return;
	}

	if (mask >= 0) {
		got_pointer_calls++;
	}

	if (debug_pointer && mask >= 0) {
		static int show_motion = -1;
		static double last_pointer = 0.0;
		double tnow, dt;
		static int last_x, last_y;
		if (show_motion == -1) {
			if (getenv("X11VNC_DB_NOMOTION")) {
				show_motion = 0;
			} else {
				show_motion = 1;
			}
		}
		dtime0(&tnow);
		tnow -= x11vnc_start;
		dt = tnow - last_pointer;
		last_pointer = tnow;
		if (show_motion) {
			rfbLog("# pointer(mask: 0x%x, x:%4d, y:%4d) "
			    "dx: %3d dy: %3d dt: %.4f t: %.4f\n", mask, x, y,
			    x - last_x, y - last_y, dt, tnow);
		}
		last_x = x;
		last_y = y;
	}

	if (unixpw_in_progress) {
		return;
	}

	get_allowed_input(client, &input);

	if (rotating) {
		rotate_coords_inverse(x, y, &x, &y, -1, -1);
	}

	if (scaling) {
		/* map from rfb size to X11 size: */
		x = ((double) x / scaled_x) * dpy_x;
		x = nfix(x, dpy_x);
		y = ((double) y / scaled_y) * dpy_y;
		y = nfix(y, dpy_y);
	}

	INPUT_LOCK;

	if ((pipeinput_fh != NULL || pipeinput_int) && mask >= 0) {
		pipe_pointer(mask, x, y, client);	/* MACOSX here. */
		if (! pipeinput_tee) {
			if (! view_only || raw_fb) {	/* raw_fb hack */
				got_user_input++;
				got_pointer_input++;
				last_pointer_client = client;
				last_pointer_time = dnow();
				last_event = last_input = last_pointer_input = time(NULL);
			}
			if (input.motion) {
				/* raw_fb hack track button state */
				button_mask_prev = button_mask;
				button_mask = mask;
			}
			if (!view_only && (input.motion || input.button)) {
				last_rfb_ptr_injected = dnow();
			}
			INPUT_UNLOCK;
			return;
		}
	}

	if (view_only) {
		INPUT_UNLOCK;
		return;
	}

	now = dnow();

	if (mask >= 0) {
		/*
		 * mask = -1 is a special case call from scan_for_updates()
		 * to flush the event queue; there is no real pointer event.
		 */
		if (! input.motion && ! input.button) {
			INPUT_UNLOCK;
			return;
		}

		got_user_input++;
		got_pointer_input++;
		last_pointer_client = client;

		last_pointer_time = now;
		last_rfb_ptr_injected = dnow();

		if (blackout_ptr && blackouts) {
			int b, ok = 1;
			/* see if it goes into the blacked out region */
			for (b=0; b < blackouts; b++) {
				if (x < blackr[b].x1 || x > blackr[b].x2) {
					continue;
				}
				if (y < blackr[b].y1 || y > blackr[b].y2) {
					continue;
				}
				/* x1 <= x <= x2 and y1 <= y <= y2 */
				ok = 0;
				break;
			}
			if (! ok) {
				if (debug_pointer) {
				    rfbLog("pointer(): blackout_ptr skipping "
					"x=%d y=%d in rectangle %d,%d %d,%d\n", x, y,
					blackr[b].x1, blackr[b].y1,
					blackr[b].x2, blackr[b].y2);
				}
				INPUT_UNLOCK;
				return;
			}
		}
	}

	/*
	 * The following is hopefully an improvement wrt response during
	 * pointer user input (window drags) for the threaded case.
	 * See check_user_input() for the more complicated things we do
	 * in the non-threaded case.
	 */
	if ((use_threads && pointer_mode != 1) || pointer_flush_delay > 0.0) {
#		define NEV 32
		/* storage for the event queue */
		static int nevents = 0;
		static int ev[NEV][3];
		int i;
		/* timer things */
		static double dt = 0.0, tmr = 0.0, maxwait = 0.4;

		if (pointer_flush_delay > 0.0) {
			maxwait = pointer_flush_delay;
		}
		if (mask >= 0) {
			if (fb_copy_in_progress || pointer_flush_delay > 0.0) {
				buffer_it = 1;
			}
		}

		POINTER_LOCK;

		/* 
		 * If the framebuffer is being copied in another thread
		 * (scan_for_updates()), we will queue up to 32 pointer
		 * events for later.  The idea is by delaying these input
		 * events, the screen is less likely to change during the
		 * copying period, and so will give rise to less window
		 * "tearing".
		 *
		 * Tearing is not completely eliminated because we do
		 * not suspend work in the other libvncserver threads.
		 * Maybe that is a possibility with a mutex...
		 */
		if (buffer_it) {
			/* 
			 * mask = -1 is an all-clear signal from
			 * scan_for_updates().
			 *
			 * dt is a timer in seconds; we only queue for so long.
			 */
			dt += dtime(&tmr);

			if (nevents < NEV && dt < maxwait) {
				i = nevents++;
				ev[i][0] = mask;
				ev[i][1] = x;
				ev[i][2] = y;
				if (! input.button) {
					ev[i][0] = -1;
				}
				if (! input.motion) {
					ev[i][1] = -1;
					ev[i][2] = -1;
				}
				if (debug_pointer) {
					rfbLog("pointer(): deferring event %d"
					    " %.4f\n", i, tmr - x11vnc_start);
				}
				POINTER_UNLOCK;
				INPUT_UNLOCK;
				return;
			}
		}

		/* time to send the queue */
		for (i=0; i<nevents; i++) {
			int sent = 0;
			if (mask < 0 && client != NULL) {
				/* hack to only push the latest event */
				if (i < nevents - 1) {
					if (debug_pointer) {
						rfbLog("- skip deferred event:"
						    " %d\n", i);
					}
					continue;
				}
			}
			if (debug_pointer) {
				rfbLog("pointer(): sending event %d %.4f\n",
				    i+1, dnowx());
			}
			if (ev[i][1] >= 0) {
				update_x11_pointer_position(ev[i][1], ev[i][2]);
				sent = 1;
			}
			if (ev[i][0] >= 0) {
				update_x11_pointer_mask(ev[i][0]);
				sent = 1;
			}

			if (sent) {
				pointer_queued_sent++;
			}
		}
		if (nevents && dt > maxwait) {
		    if (dpy) {	/* raw_fb hack */
			if (mask < 0) {
				if (debug_pointer) {
					rfbLog("pointer(): calling XFlush "
					    "%.4f\n", dnowx());
				}
				X_LOCK;
				XFlush_wr(dpy);	
				X_UNLOCK;
			}
		    }
		}
		nevents = 0;	/* reset everything */
		dt = 0.0;
		dtime0(&tmr);

		POINTER_UNLOCK;
	}
	if (mask < 0) {		/* -1 just means flush the event queue */
		if (debug_pointer) {
			rfbLog("pointer(): flush only.  %.4f\n",
			    dnowx());
		}
		INPUT_UNLOCK;
		return;
	}

	/* update the X display with the event: */
	if (input.motion) {
		update_x11_pointer_position(x, y);
		sent = 1;
	}
	if (input.button) {
		if (mask != button_mask) {
			button_change_x = cursor_x;
			button_change_y = cursor_y;
		}
		update_x11_pointer_mask(mask);
		sent = 1;
	}

	if (! dpy) {
		;
	} else if (nofb && sent) {
		/* 
		 * nofb is for, e.g. Win2VNC, where fastest pointer
		 * updates are desired.
		 */
		X_LOCK;
		XFlush_wr(dpy);
		X_UNLOCK;
	} else if (buffer_it) {
		if (debug_pointer) {
			rfbLog("pointer(): calling XFlush+"
			    "%.4f\n", dnowx());
		}
		X_LOCK;
		XFlush_wr(dpy);	
		X_UNLOCK;
	}
	INPUT_UNLOCK;
}

void initialize_pipeinput(void) {
	char *p = NULL;

	if (pipeinput_fh != NULL) {
		rfbLog("closing pipeinput stream: %p\n", pipeinput_fh);
		pclose(pipeinput_fh);
		pipeinput_fh = NULL;
	}

	pipeinput_tee = 0;
	if (pipeinput_opts) {
		free(pipeinput_opts);
		pipeinput_opts = NULL;
	}

	if (! pipeinput_str) {
		return;
	}

	/* look for options:  tee, reopen, ... */
	if (strstr(pipeinput_str, "UINPUT") == pipeinput_str) {
		;
	} else {
		p = strchr(pipeinput_str, ':');
	}
	if (p != NULL) {
		char *str, *opt, *q;
		int got = 0;
		*p = '\0';
		str = strdup(pipeinput_str);
		opt = strdup(pipeinput_str);
		*p = ':';
		q = strtok(str, ",");
		while (q) {
			if (!strcmp(q, "key") || !strcmp(q, "keycodes")) {
				got = 1;
			}
			if (!strcmp(q, "reopen")) {
				got = 1;
			}
			if (!strcmp(q, "tee")) {
				pipeinput_tee = 1;
				got = 1;
			}
			q = strtok(NULL, ",");
		}
		if (got) {
			pipeinput_opts = opt;
		} else {
			free(opt);
		}
		free(str);
		p++;
	} else {
		p = pipeinput_str;
	}
if (0) fprintf(stderr, "initialize_pipeinput: %s -- %s\n", pipeinput_str, p);

	if (!strcmp(p, "VID")) {
		pipeinput_int = PIPEINPUT_VID;
		return;
	} else if (strstr(p, "CONSOLE") == p) {
		int tty = 0, n;
		char dev[32];
		if (sscanf(p, "CONSOLE%d", &n) == 1) {
			tty = n;
		}
		sprintf(dev, "/dev/tty%d", tty);
		pipeinput_cons_fd = open(dev, O_WRONLY);
		if (pipeinput_cons_fd >= 0) {
			rfbLog("pipeinput: using linux console: %s\n", dev);
			if (pipeinput_cons_dev) {
				free(pipeinput_cons_dev);
			}
			pipeinput_cons_dev = strdup(dev);
			pipeinput_int = PIPEINPUT_CONSOLE;
		} else {
			rfbLog("pipeinput: could not open: %s\n", dev);
			rfbLogPerror("open");
			rfbLog("You may need to be root to open %s.\n", dev);
			rfbLog("\n");
		}
		return;
	} else if (strstr(p, "UINPUT") == p) {
		char *q = strchr(p, ':');
		if (q) {
			parse_uinput_str(q+1);
		}
		pipeinput_int = PIPEINPUT_UINPUT;
		initialize_uinput();
		return;
	} else if (strstr(p, "MACOSX") == p) {
		pipeinput_int = PIPEINPUT_MACOSX;
		return;
	} else if (strstr(p, "VNC") == p) {
		pipeinput_int = PIPEINPUT_VNC;
		return;
	}

	set_child_info();
	/* pipeinput */
	if (no_external_cmds || !cmd_ok("pipeinput")) {
		rfbLogEnable(1);
		rfbLog("cannot run external commands in -nocmds mode:\n");
		rfbLog("   \"%s\"\n", p);
		rfbLog("   exiting.\n");
		clean_up_exit(1);
	}
	rfbLog("pipeinput: starting: \"%s\"...\n", p);
	close_exec_fds();
	pipeinput_fh = popen(p, "w");

	if (! pipeinput_fh) {
		rfbLog("popen(\"%s\", \"w\") failed.\n", p);
		rfbLogPerror("popen");
		rfbLog("Disabling -pipeinput mode.\n");
		return;
	}

	fprintf(pipeinput_fh, "%s",
"# \n"
"# Format of the -pipeinput stream:\n"
"# --------------------------------\n"
"#\n"
"# Lines like these beginning with '#' are to be ignored.\n"
"#\n"
"# Pointer events (mouse motion and button clicks) come in the form:\n"
"#\n"
"#\n"
"# Pointer <client#> <x> <y> <mask> <hint>\n"
"#\n"
"#\n"
"# The <client#> is a decimal integer uniquely identifying the client\n"
"# that generated the event.  If it is negative that means this event\n"
"# would have been discarded since the client was viewonly.\n"
"#\n"
"# <x> and <y> are decimal integers reflecting the position on the screen\n"
"# the event took place at.\n"
"#\n"
"# <mask> is the button mask indicating the button press state, as normal\n"
"# 0 means no buttons pressed, 1 means button 1 is down 3 (11) means buttons\n"
"# 1 and 2 are down, etc.\n"
"#\n"
"# <hint> is a string containing no spaces and may be ignored.\n"
"# It contains some interpretation about what has happened.\n"
"# It can be:\n"
"#\n"
"#	None		(nothing to report)\n"
"#	ButtonPress-N	(this event will cause button-N to be pressed) \n"
"#	ButtonRelease-N	(this event will cause button-N to be released) \n"
"#\n"
"# if two more more buttons change state in one event they are listed\n"
"# separated by commas.\n"
"#\n"
"# One might parse a Pointer line with:\n"
"#\n"
"# int client, x, y, mask; char hint[100];\n"
"# sscanf(line, \"Pointer %d %d %d %d %s\", &client, &x, &y, &mask, hint);\n"
"#\n"
"#\n"
"# Keysym events (keyboard presses and releases) come in the form:\n"
"#\n"
"#\n"
"# Keysym <client#> <down> <keysym#> <keysym-name> <hint>\n"
"#\n"
"#\n"
"# The <client#> is as with Pointer.\n"
"#\n"
"# <down> is a decimal either 1 or 0 indicating KeyPress or KeyRelease,\n"
"# respectively.\n"
"#\n"
"# <keysym#> is a decimal integer incidating the Keysym of the event.\n"
"#\n"
"# <keysym-name> is the corresponding Keysym name.\n"
"#\n"
"# See the file /usr/include/X11/keysymdef.h for the mappings.\n"
"# You basically remove the leading 'XK_' prefix from the macro name in\n"
"# that file to get the Keysym name.\n"
"#\n"
"# One might parse a Keysym line with:\n"
"#\n"
"# int client, down, keysym; char name[100], hint[100];\n"
"# sscanf(line, \"Keysym %d %d %d %s %s\", &client, &down, &keysym, name, hint);\n"
"#\n"
"# The <hint> value is currently just None, KeyPress, or KeyRelease.\n"
"#\n"
"# In the future <hint> will provide a hint for the sequence of KeyCodes\n"
"# (i.e. keyboard scancodes) that x11vnc would inject to an X display to\n"
"# simulate the Keysym.\n"
"#\n"
"# You see, some Keysyms will require more than one injected Keycode to\n"
"# generate the symbol.  E.g. the Keysym \"ampersand\" going down usually\n"
"# requires a Shift key going down, then the key with the \"&\" on it going\n"
"# down, and, perhaps, the Shift key going up (that is how x11vnc does it).\n"
"#\n"
"# The Keysym => Keycode(s) stuff gets pretty messy.  Hopefully the Keysym\n"
"# info will be enough for most purposes (having identical keyboards on\n"
"# both sides helps).\n"
"#\n"
"# Parsing example for perl:\n"
"#\n"
"# while (<>) {\n"
"#     chomp;\n"
"#     if (/^Pointer/) {\n"
"#         my ($p, $client, $x, $y, $mask, $hint) = split(' ', $_, 6);\n"
"#         do_pointer($client, $x, $y, $mask, $hint);\n"
"#     } elsif (/^Keysym/) {\n"
"#         my ($k, $client, $down, $keysym, $name, $hint) = split(' ', $_, 6);\n"
"#         do_keysym($client, $down, $keysym, $name, $hint);\n"
"#     }\n"
"# }\n"
"#\n"
"#\n"
"# Here comes your stream.  The following token will always indicate the\n"
"# end of this informational text:\n"
"# END_OF_TOP\n"
);
	fflush(pipeinput_fh);
	if (raw_fb_str) {
		/* the pipe program may actually create the fb */
		sleep(1);
	}
}

int check_pipeinput(void) {
	if (! pipeinput_fh) {
		return 1;
	}
	if (ferror(pipeinput_fh)) {
		rfbLog("pipeinput pipe has ferror. %p\n", pipeinput_fh);
		
		if (pipeinput_opts && strstr(pipeinput_opts, "reopen")) {
			rfbLog("restarting -pipeinput pipe...\n");
			initialize_pipeinput();
			if (pipeinput_fh) {
				return 1;
			} else {
				return 0;
			}
		} else {
			rfbLog("closing -pipeinput pipe...\n");
			pclose(pipeinput_fh);
			pipeinput_fh = NULL;
			return 0;
		}
	}
	return 1;
}