1/*
2   Copyright (C) 2002-2010 Karl J. Runge <runge@karlrunge.com>
3   All rights reserved.
4
5This file is part of x11vnc.
6
7x11vnc is free software; you can redistribute it and/or modify
8it under the terms of the GNU General Public License as published by
9the Free Software Foundation; either version 2 of the License, or (at
10your option) any later version.
11
12x11vnc is distributed in the hope that it will be useful,
13but WITHOUT ANY WARRANTY; without even the implied warranty of
14MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15GNU General Public License for more details.
16
17You should have received a copy of the GNU General Public License
18along with x11vnc; if not, write to the Free Software
19Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA
20or see <http://www.gnu.org/licenses/>.
21
22In addition, as a special exception, Karl J. Runge
23gives permission to link the code of its release of x11vnc with the
24OpenSSL project's "OpenSSL" library (or with modified versions of it
25that use the same license as the "OpenSSL" library), and distribute
26the linked executables.  You must obey the GNU General Public License
27in all respects for all of the code used other than "OpenSSL".  If you
28modify this file, you may extend this exception to your version of the
29file, but you are not obligated to do so.  If you do not wish to do
30so, delete this exception statement from your version.
31*/
32
33/* -- win_utils.c -- */
34
35#include "x11vnc.h"
36#include "xinerama.h"
37#include "winattr_t.h"
38#include "cleanup.h"
39#include "xwrappers.h"
40#include "connections.h"
41#include "xrandr.h"
42#include "macosx.h"
43
44winattr_t *stack_list = NULL;
45int stack_list_len = 0;
46int stack_list_num = 0;
47
48
49Window parent_window(Window win, char **name);
50int valid_window(Window win, XWindowAttributes *attr_ret, int bequiet);
51Bool xtranslate(Window src, Window dst, int src_x, int src_y, int *dst_x,
52    int *dst_y, Window *child, int bequiet);
53int get_window_size(Window win, int *w, int *h);
54void snapshot_stack_list(int free_only, double allowed_age);
55int get_boff(void);
56int get_bwin(void);
57void update_stack_list(void);
58Window query_pointer(Window start);
59unsigned int mask_state(void);
60int pick_windowid(unsigned long *num);
61Window descend_pointer(int depth, Window start, char *name_info, int len);
62void id_cmd(char *cmd);
63
64
65Window parent_window(Window win, char **name) {
66#if !NO_X11
67	Window r, parent;
68	Window *list;
69	XErrorHandler old_handler;
70	unsigned int nchild;
71	int rc;
72#endif
73
74	if (name != NULL) {
75		*name = NULL;
76	}
77	RAWFB_RET(None)
78#if NO_X11
79	nox11_exit(1);
80	if (!name || !win) {}
81	return None;
82#else
83
84	old_handler = XSetErrorHandler(trap_xerror);
85	trapped_xerror = 0;
86	rc = XQueryTree_wr(dpy, win, &r, &parent, &list, &nchild);
87	XSetErrorHandler(old_handler);
88
89	if (! rc || trapped_xerror) {
90		trapped_xerror = 0;
91		return None;
92	}
93	trapped_xerror = 0;
94
95	if (list) {
96		XFree_wr(list);
97	}
98	if (parent && name) {
99		XFetchName(dpy, parent, name);
100	}
101	return parent;
102#endif	/* NO_X11 */
103}
104
105/* trapping utility to check for a valid window: */
106int valid_window(Window win, XWindowAttributes *attr_ret, int bequiet) {
107	XWindowAttributes attr, *pattr;
108#if !NO_X11
109	XErrorHandler old_handler;
110	int ok = 0;
111#endif
112
113	if (attr_ret == NULL) {
114		pattr = &attr;
115	} else {
116		pattr = attr_ret;
117	}
118
119	if (win == None) {
120		return 0;
121	}
122
123#ifdef MACOSX
124	if (macosx_console) {
125		return macosx_valid_window(win, attr_ret);
126	}
127#endif
128
129	RAWFB_RET(0)
130
131#if NO_X11
132	nox11_exit(1);
133	if (!win || !attr_ret || !bequiet) {}
134	return 0;
135#else
136
137	old_handler = XSetErrorHandler(trap_xerror);
138	trapped_xerror = 0;
139	if (XGetWindowAttributes(dpy, win, pattr)) {
140		ok = 1;
141	}
142	if (trapped_xerror && trapped_xerror_event) {
143		if (! quiet && ! bequiet) {
144			rfbLog("valid_window: trapped XError: %s (0x%lx)\n",
145			    xerror_string(trapped_xerror_event), win);
146		}
147		ok = 0;
148	}
149	XSetErrorHandler(old_handler);
150	trapped_xerror = 0;
151
152	return ok;
153#endif	/* NO_X11 */
154}
155
156Bool xtranslate(Window src, Window dst, int src_x, int src_y, int *dst_x,
157    int *dst_y, Window *child, int bequiet) {
158	XErrorHandler old_handler = NULL;
159	Bool ok = False;
160
161	RAWFB_RET(False)
162#if NO_X11
163	nox11_exit(1);
164	if (!src || !dst || !src_x || !src_y || !dst_x || !dst_y || !child || !bequiet) {}
165	if (!old_handler || !ok) {}
166	return False;
167#else
168
169	trapped_xerror = 0;
170	old_handler = XSetErrorHandler(trap_xerror);
171	if (XTranslateCoordinates(dpy, src, dst, src_x, src_y, dst_x,
172	    dst_y, child)) {
173		ok = True;
174	}
175	if (trapped_xerror && trapped_xerror_event) {
176		if (! quiet && ! bequiet) {
177			rfbLog("xtranslate: trapped XError: %s (0x%lx)\n",
178			    xerror_string(trapped_xerror_event), src);
179		}
180		ok = False;
181	}
182	XSetErrorHandler(old_handler);
183	trapped_xerror = 0;
184
185	return ok;
186#endif	/* NO_X11 */
187}
188
189int get_window_size(Window win, int *w, int *h) {
190	XWindowAttributes attr;
191	/* valid_window? */
192	if (valid_window(win, &attr, 1)) {
193		*w = attr.width;
194		*h = attr.height;
195		return 1;
196	} else {
197		return 0;
198	}
199}
200
201/*
202 * For use in the -wireframe stuff, save the stacking order of the direct
203 * children of the root window.  Ideally done before we send ButtonPress
204 * to the X server.
205 */
206void snapshot_stack_list(int free_only, double allowed_age) {
207	static double last_snap = 0.0, last_free = 0.0;
208	double now;
209	int num, rc, i, j;
210	unsigned int ui;
211	Window r, w;
212	Window *list;
213
214	if (! stack_list) {
215		stack_list = (winattr_t *) malloc(256*sizeof(winattr_t));
216		stack_list_num = 0;
217		stack_list_len = 256;
218	}
219
220	dtime0(&now);
221	if (free_only) {
222		/* we really don't free it, just reset to zero windows */
223		stack_list_num = 0;
224		last_free = now;
225		return;
226	}
227
228	if (stack_list_num && now < last_snap + allowed_age) {
229		return;
230	}
231
232	stack_list_num = 0;
233	last_free = now;
234
235#ifdef MACOSX
236	if (! macosx_console) {
237		RAWFB_RET_VOID
238	}
239#else
240	RAWFB_RET_VOID
241#endif
242
243#if NO_X11 && !defined(MACOSX)
244	num = rc = i = j = 0;	/* compiler warnings */
245	ui = 0;
246	r = w = None;
247	list = NULL;
248	return;
249#else
250
251	X_LOCK;
252	/* no need to trap error since rootwin */
253	rc = XQueryTree_wr(dpy, rootwin, &r, &w, &list, &ui);
254	num = (int) ui;
255
256	if (! rc) {
257		stack_list_num = 0;
258		last_free = now;
259		last_snap = 0.0;
260		X_UNLOCK;
261		return;
262	}
263
264	last_snap = now;
265	if (num > stack_list_len + blackouts) {
266		int n = 2*num;
267		free(stack_list);
268		stack_list = (winattr_t *) malloc(n*sizeof(winattr_t));
269		stack_list_len = n;
270	}
271	j = 0;
272	for (i=0; i<num; i++) {
273		stack_list[j].win = list[i];
274		stack_list[j].fetched = 0;
275		stack_list[j].valid = 0;
276		stack_list[j].time = now;
277		j++;
278	}
279	for (i=0; i<blackouts; i++) {
280		stack_list[j].win = get_boff() + 1;
281		stack_list[j].fetched = 1;
282		stack_list[j].valid = 1;
283		stack_list[j].x = blackr[i].x1;
284		stack_list[j].y = blackr[i].y1;
285		stack_list[j].width  = blackr[i].x2 - blackr[i].x1;
286		stack_list[j].height = blackr[i].y2 - blackr[i].y1;
287		stack_list[j].time = now;
288		stack_list[j].map_state = IsViewable;
289		stack_list[j].rx = -1;
290		stack_list[j].ry = -1;
291		j++;
292
293if (0) fprintf(stderr, "blackr: %d %dx%d+%d+%d\n", i,
294	stack_list[j-1].width, stack_list[j-1].height,
295	stack_list[j-1].x, stack_list[j-1].y);
296
297	}
298	stack_list_num = num + blackouts;
299	if (debug_wireframe > 1) {
300		fprintf(stderr, "snapshot_stack_list: num=%d len=%d\n",
301		    stack_list_num, stack_list_len);
302	}
303
304	XFree_wr(list);
305	X_UNLOCK;
306#endif	/* NO_X11 */
307}
308
309int get_boff(void) {
310	if (macosx_console) {
311		return 0x1000000;
312	} else {
313		return 0;
314	}
315}
316
317int get_bwin(void) {
318	return 10;
319}
320
321void update_stack_list(void) {
322	int k;
323	double now;
324	XWindowAttributes attr;
325	int boff, bwin;
326
327	if (! stack_list) {
328		return;
329	}
330	if (! stack_list_num) {
331		return;
332	}
333
334	dtime0(&now);
335
336	boff = get_boff();
337	bwin = get_bwin();
338
339	X_LOCK;
340	for (k=0; k < stack_list_num; k++) {
341		Window win = stack_list[k].win;
342		if (win != None && boff <= (int) win && (int) win < boff + bwin) {
343			;	/* special, blackout */
344		} else if (!valid_window(win, &attr, 1)) {
345			stack_list[k].valid = 0;
346		} else {
347			stack_list[k].valid = 1;
348			stack_list[k].x = attr.x;
349			stack_list[k].y = attr.y;
350			stack_list[k].width = attr.width;
351			stack_list[k].height = attr.height;
352			stack_list[k].border_width = attr.border_width;
353			stack_list[k].depth = attr.depth;
354			stack_list[k].class = attr.class;
355			stack_list[k].backing_store = attr.backing_store;
356			stack_list[k].map_state = attr.map_state;
357
358			/* root_x, root_y not used for stack_list usage: */
359			stack_list[k].rx = -1;
360			stack_list[k].ry = -1;
361		}
362		stack_list[k].fetched = 1;
363		stack_list[k].time = now;
364	}
365	X_UNLOCK;
366if (0) fprintf(stderr, "update_stack_list[%d]: %.4f  %.4f\n", stack_list_num, now - x11vnc_start, dtime(&now));
367}
368
369Window query_pointer(Window start) {
370	int rx, ry;
371#if !NO_X11
372	Window r, c;	/* compiler warnings */
373	int wx, wy;
374	unsigned int mask;
375#endif
376
377#ifdef MACOSX
378	if (macosx_console) {
379		macosx_get_cursor_pos(&rx, &ry);
380	}
381#endif
382
383	RAWFB_RET(None)
384
385#if NO_X11
386	if (!start) { rx = ry = 0; }
387	return None;
388#else
389	if (start == None) {
390		start = rootwin;
391	}
392	if (XQueryPointer_wr(dpy, start, &r, &c, &rx, &ry, &wx, &wy, &mask)) {
393		return c;
394	} else {
395		return None;
396	}
397#endif	/* NO_X11 */
398}
399
400unsigned int mask_state(void) {
401#if NO_X11
402	RAWFB_RET(0)
403	return 0;
404#else
405	Window r, c;
406	int rx, ry, wx, wy;
407	unsigned int mask;
408
409	RAWFB_RET(0)
410
411	if (XQueryPointer_wr(dpy, rootwin, &r, &c, &rx, &ry, &wx, &wy, &mask)) {
412		return mask;
413	} else {
414		return 0;
415	}
416#endif	/* NO_X11 */
417}
418
419int pick_windowid(unsigned long *num) {
420	char line[512];
421	int ok = 0, n = 0, msec = 10, secmax = 15;
422	FILE *p;
423
424	RAWFB_RET(0)
425
426	if (use_dpy) {
427		set_env("DISPLAY", use_dpy);
428	}
429	/* id */
430	if (no_external_cmds || !cmd_ok("id")) {
431		rfbLogEnable(1);
432		rfbLog("cannot run external commands in -nocmds mode:\n");
433		rfbLog("   \"%s\"\n", "xwininfo");
434		rfbLog("   exiting.\n");
435		clean_up_exit(1);
436	}
437	close_exec_fds();
438	p = popen("xwininfo", "r");
439
440	if (! p) {
441		return 0;
442	}
443
444	fprintf(stderr, "\n");
445	fprintf(stderr, "  Please select the window for x11vnc to poll\n");
446	fprintf(stderr, "  by clicking the mouse in that window.\n");
447	fprintf(stderr, "\n");
448
449	while (msec * n++ < 1000 * secmax) {
450		unsigned long tmp;
451		char *q;
452		fd_set set;
453		struct timeval tv;
454
455		if (screen && screen->clientHead) {
456			/* they may be doing the pointer-pick thru vnc: */
457			int nfds;
458			tv.tv_sec = 0;
459			tv.tv_usec = msec * 1000;
460			FD_ZERO(&set);
461			FD_SET(fileno(p), &set);
462
463			nfds = select(fileno(p)+1, &set, NULL, NULL, &tv);
464
465			if (nfds == 0 || nfds < 0) {
466				/*
467				 * select timedout or error.
468				 * note this rfbPE takes about 30ms too:
469				 */
470				rfbPE(-1);
471				XFlush_wr(dpy);
472				continue;
473			}
474		}
475
476		if (fgets(line, 512, p) == NULL) {
477			break;
478		}
479		q = strstr(line, " id: 0x");
480		if (q) {
481			q += 5;
482			if (sscanf(q, "0x%lx ", &tmp) == 1) {
483				ok = 1;
484				*num = tmp;
485				fprintf(stderr, "  Picked: 0x%lx\n\n", tmp);
486				break;
487			}
488		}
489	}
490	pclose(p);
491	return ok;
492}
493
494Window descend_pointer(int depth, Window start, char *name_info, int len) {
495#if NO_X11
496	RAWFB_RET(None)
497	if (!depth || !start || !name_info || !len) {}
498	return None;
499#else
500	Window r, c, clast = None;
501	int i, rx, ry, wx, wy;
502	int written = 0, filled = 0;
503	char *store = NULL;
504	unsigned int m;
505	static XClassHint *classhint = NULL;
506	static char *nm_cache = NULL;
507	static int nm_cache_len = 0;
508	static Window prev_start = None;
509
510	RAWFB_RET(None)
511
512	if (! classhint) {
513		classhint = XAllocClassHint();
514	}
515
516	if (! nm_cache) {
517		nm_cache = (char *) malloc(1024);
518		nm_cache_len = 1024;
519		nm_cache[0] = '\0';
520	}
521	if (name_info && nm_cache_len < len) {
522		if (nm_cache) {
523			free(nm_cache);
524		}
525		nm_cache_len = 2*len;
526		nm_cache = (char *) malloc(nm_cache_len);
527	}
528
529	if (name_info) {
530		if (start != None && start == prev_start) {
531			store = NULL;
532			strncpy(name_info, nm_cache, len);
533		} else {
534			store = name_info;
535			name_info[0] = '\0';
536		}
537	}
538
539	if (start != None) {
540		c = start;
541		if (name_info) {
542			prev_start = start;
543		}
544	} else {
545		c = rootwin;
546	}
547
548	for (i=0; i<depth; i++) {
549		clast = c;
550		if (store && ! filled) {
551			char *name;
552			if (XFetchName(dpy, clast, &name) && name != NULL) {
553				int l = strlen(name);
554				if (written + l+2 < len) {
555					strcat(store, "^^");
556					written += 2;
557					strcat(store, name);
558					written += l;
559				} else {
560					filled = 1;
561				}
562				XFree_wr(name);
563			}
564		}
565		if (store && classhint && ! filled) {
566			classhint->res_name = NULL;
567			classhint->res_class = NULL;
568			if (XGetClassHint(dpy, clast, classhint)) {
569				int l = 0;
570				if (classhint->res_class) {
571					l += strlen(classhint->res_class);
572				}
573				if (classhint->res_name) {
574					l += strlen(classhint->res_name);
575				}
576				if (written + l+4 < len) {
577					strcat(store, "##");
578					if (classhint->res_class) {
579						strcat(store,
580						    classhint->res_class);
581					}
582					strcat(store, "++");
583					if (classhint->res_name) {
584						strcat(store,
585						    classhint->res_name);
586					}
587					written += l+4;
588				} else {
589					filled = 1;
590				}
591				if (classhint->res_class) {
592					XFree_wr(classhint->res_class);
593				}
594				if (classhint->res_name) {
595					XFree_wr(classhint->res_name);
596				}
597			}
598		}
599		if (! XQueryPointer_wr(dpy, c, &r, &c, &rx, &ry, &wx, &wy, &m)) {
600			break;
601		}
602		if (! c) {
603			break;
604		}
605	}
606	if (start != None && name_info) {
607		strncpy(nm_cache, name_info, nm_cache_len);
608	}
609
610	return clast;
611#endif	/* NO_X11 */
612}
613
614void id_cmd(char *cmd) {
615	int rc, dx = 0, dy = 0, dw = 0, dh = 0;
616	int x0, y0, w0, h0;
617	int x, y, w, h, do_move = 0, do_resize = 0;
618	int disp_x = DisplayWidth(dpy, scr);
619	int disp_y = DisplayHeight(dpy, scr);
620	Window win = subwin;
621	XWindowAttributes attr;
622	XErrorHandler old_handler = NULL;
623	Window twin;
624
625	if (!cmd || !strcmp(cmd, "")) {
626		return;
627	}
628	if (strstr(cmd, "win=") == cmd) {
629		if (! scan_hexdec(cmd + strlen("win="), &win)) {
630			rfbLog("id_cmd: incorrect win= hex/dec number: %s\n", cmd);
631			return;
632		} else {
633			char *q = strchr(cmd, ':');
634			if (!q) {
635				rfbLog("id_cmd: incorrect win=...: hex/dec number: %s\n", cmd);
636				return;
637			}
638			rfbLog("id_cmd:%s set window id to 0x%lx\n", cmd, win);
639			cmd = q+1;
640		}
641	}
642	if (!win) {
643		rfbLog("id_cmd:%s not in sub-window mode or no win=0xNNNN.\n", cmd);
644		return;
645	}
646#if !NO_X11
647	X_LOCK;
648	if (!valid_window(win, &attr, 1)) {
649		X_UNLOCK;
650		return;
651	}
652	w0 = w = attr.width;
653	h0 = h = attr.height;
654	old_handler = XSetErrorHandler(trap_xerror);
655	trapped_xerror = 0;
656	XTranslateCoordinates(dpy, win, rootwin, 0, 0, &x, &y, &twin);
657	x0 = x;
658	y0 = y;
659	if (strstr(cmd, "move:") == cmd) {
660		if (sscanf(cmd, "move:%d%d", &dx, &dy) == 2) {
661			x = x + dx;
662			y = y + dy;
663			do_move = 1;
664		}
665	} else if (strstr(cmd, "resize:") == cmd) {
666		if (sscanf(cmd, "resize:%d%d", &dw, &dh) == 2) {
667			w = w + dw;
668			h = h + dh;
669			do_move = 1;
670			do_resize = 1;
671		}
672	} else if (strstr(cmd, "geom:") == cmd) {
673		if (parse_geom(cmd+strlen("geom:"), &w, &h, &x, &y, disp_x, disp_y)) {
674			do_move = 1;
675			do_resize = 1;
676			if (w <= 0) {
677				w = w0;
678			}
679			if (h <= 0) {
680				h = h0;
681			}
682			if (scaling && getenv("X11VNC_APPSHARE_ACTIVE")) {
683				x /= scale_fac_x;
684				y /= scale_fac_y;
685			}
686		}
687	} else if (!strcmp(cmd, "raise")) {
688		rc = XRaiseWindow(dpy, win);
689		rfbLog("id_cmd:%s rc=%d\n", cmd, rc);
690	} else if (!strcmp(cmd, "lower")) {
691		rc = XLowerWindow(dpy, win);
692		rfbLog("id_cmd:%s rc=%d\n", cmd, rc);
693	} else if (!strcmp(cmd, "map")) {
694		rc= XMapRaised(dpy, win);
695		rfbLog("id_cmd:%s rc=%d\n", cmd, rc);
696	} else if (!strcmp(cmd, "unmap")) {
697		rc= XUnmapWindow(dpy, win);
698		rfbLog("id_cmd:%s rc=%d\n", cmd, rc);
699	} else if (!strcmp(cmd, "iconify")) {
700		rc= XIconifyWindow(dpy, win, scr);
701		rfbLog("id_cmd:%s rc=%d\n", cmd, rc);
702	} else if (strstr(cmd, "wm_name:") == cmd) {
703		rc= XStoreName(dpy, win, cmd+strlen("wm_name:"));
704		rfbLog("id_cmd:%s rc=%d\n", cmd, rc);
705	} else if (strstr(cmd, "icon_name:") == cmd) {
706		rc= XSetIconName(dpy, win, cmd+strlen("icon_name:"));
707		rfbLog("id_cmd:%s rc=%d\n", cmd, rc);
708	} else if (!strcmp(cmd, "wm_delete")) {
709		XClientMessageEvent ev;
710		memset(&ev, 0, sizeof(ev));
711		ev.type = ClientMessage;
712		ev.send_event = True;
713		ev.display = dpy;
714		ev.window = win;
715		ev.message_type = XInternAtom(dpy, "WM_PROTOCOLS", False);
716		ev.format = 32;
717		ev.data.l[0] = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
718		rc = XSendEvent(dpy, win, False, 0, (XEvent *) &ev);
719		rfbLog("id_cmd:%s rc=%d\n", cmd, rc);
720	} else {
721		rfbLog("id_cmd:%s unrecognized command.\n", cmd);
722	}
723	if (do_move || do_resize) {
724		if (w >= disp_x) {
725			w = disp_x - 4;
726		}
727		if (h >= disp_y) {
728			h = disp_y - 4;
729		}
730		if (w < 1) {
731			w = 1;
732		}
733		if (h < 1) {
734			h = 1;
735		}
736		if (x + w > disp_x) {
737			x = disp_x - w - 1;
738		}
739		if (y + h > disp_y) {
740			y = disp_y - h - 1;
741		}
742		if (x < 0) {
743			x = 1;
744		}
745		if (y < 0) {
746			y = 1;
747		}
748		rc = 0;
749		rc += XMoveWindow(dpy, win, x, y);
750		off_x = x;
751		off_y = y;
752
753		rc += XResizeWindow(dpy, win, w, h);
754
755		rfbLog("id_cmd:%s rc=%d dx=%d dy=%d dw=%d dh=%d %dx%d+%d+%d -> %dx%d+%d+%d\n",
756		    cmd, rc, dx, dy, dw, dh, w0, h0, x0, y0, w, h, x, h);
757	}
758	XSync(dpy, False);
759	XSetErrorHandler(old_handler);
760	if (trapped_xerror) {
761		rfbLog("id_cmd:%s trapped_xerror.\n", cmd);
762	}
763	trapped_xerror = 0;
764	if (do_resize) {
765		rfbLog("id_cmd:%s calling check_xrandr_event.\n", cmd);
766		check_xrandr_event("id_cmd");
767	}
768	X_UNLOCK;
769#endif
770}
771
772