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/* -- macosxCGS.c -- */
34
35/*
36 * We need to keep this separate from nearly everything else, e.g. rfb.h
37 * and the other stuff, otherwise it does not work properly, mouse drags
38 * will not work!!
39 */
40void macosxCGS_unused(void) {}
41
42#if (defined(__MACH__) && defined(__APPLE__))
43
44#include <ApplicationServices/ApplicationServices.h>
45#include <Cocoa/Cocoa.h>
46#include <Carbon/Carbon.h>
47
48extern CGDirectDisplayID displayID;
49
50void macosxCGS_get_all_windows(void);
51int macosxCGS_get_qlook(int);
52void macosxGCS_set_pasteboard(char *str, int len);
53
54typedef CGError       CGSError;
55typedef long          CGSWindowCount;
56typedef void *        CGSConnectionID;
57typedef int           CGSWindowID;
58typedef CGSWindowID*  CGSWindowIDList;
59typedef CGWindowLevel CGSWindowLevel;
60typedef NSRect        CGSRect;
61
62extern CGSConnectionID _CGSDefaultConnection ();
63
64extern CGSError CGSGetOnScreenWindowList (CGSConnectionID cid,
65    CGSConnectionID owner, CGSWindowCount listCapacity,
66    CGSWindowIDList list, CGSWindowCount *listCount);
67
68extern CGSError CGSGetWindowList (CGSConnectionID cid,
69    CGSConnectionID owner, CGSWindowCount listCapacity,
70    CGSWindowIDList list, CGSWindowCount *listCount);
71
72extern CGSError CGSGetScreenRectForWindow (CGSConnectionID cid,
73    CGSWindowID wid, CGSRect *rect);
74
75extern CGWindowLevel CGSGetWindowLevel (CGSConnectionID cid,
76    CGSWindowID wid, CGSWindowLevel *level);
77
78typedef enum _CGSWindowOrderingMode {
79    kCGSOrderAbove                =  1, /* Window is ordered above target. */
80    kCGSOrderBelow                = -1, /* Window is ordered below target. */
81    kCGSOrderOut                  =  0  /* Window is removed from the on-screen window list. */
82} CGSWindowOrderingMode;
83
84extern OSStatus CGSOrderWindow(const CGSConnectionID cid,
85    const CGSWindowID wid, CGSWindowOrderingMode place, CGSWindowID relativeToWindowID);
86
87static CGSConnectionID cid = NULL;
88
89extern void macosx_log(char *);
90
91int macwinmax = 0;
92typedef struct windat {
93	int win;
94	int x, y;
95	int width, height;
96	int level;
97	int mapped;
98	int clipped;
99	int ncache_only;
100} windat_t;
101
102extern int ncache;
103
104#define MAXWINDAT 4096
105windat_t macwins[MAXWINDAT];
106static CGSWindowID _wins_all[MAXWINDAT];
107static CGSWindowID _wins_mapped[MAXWINDAT];
108static CGSWindowCount _wins_all_cnt, _wins_mapped_cnt;
109static int _wins_int[MAXWINDAT];
110
111#define WINHISTNUM 32768
112#define WINHISTMAX 4
113char whist[WINHISTMAX][WINHISTNUM];
114int whist_idx = -1;
115int qlook[WINHISTNUM];
116
117char is_exist     = 0x1;
118char is_mapped    = 0x2;
119char is_clipped   = 0x4;
120char is_offscreen = 0x8;
121
122extern double dnow(void);
123extern double dnowx(void);
124
125extern int dpy_x, dpy_y;
126extern int macosx_icon_anim_time;
127
128extern void macosx_add_mapnotify(int, int, int);
129extern void macosx_add_create(int, int);
130extern void macosx_add_destroy(int, int);
131extern void macosx_add_visnotify(int, int, int);
132
133int CGS_levelmax;
134int CGS_levels[16];
135
136int macosxCGS_get_qlook(int w) {
137	if (w >= WINHISTNUM) {
138		return -1;
139	}
140	return qlook[w];
141}
142
143int macosxCGS_find_index(int w) {
144	static int last_index = -1;
145	int idx;
146
147	if (last_index >= 0) {
148		if (macwins[last_index].win == w) {
149			return last_index;
150		}
151	}
152
153	idx = macosxCGS_get_qlook(w);
154	if (idx >= 0) {
155		if (macwins[idx].win == w) {
156			last_index = idx;
157			return idx;
158		}
159	}
160
161	for (idx=0; idx < macwinmax; idx++) {
162		if (macwins[idx].win == w) {
163			last_index = idx;
164			return idx;
165		}
166	}
167	return -1;
168}
169
170#if 0
171extern void usleep(unsigned long usec);
172#else
173extern int usleep(useconds_t usec);
174#endif
175
176int macosxCGS_follow_animation_win(int win, int idx, int grow) {
177	double t = dnow();
178	int diffs = 0;
179	int x, y, w, h;
180	int xp = -1, yp = -1, wp = -1, hp = -1;
181	CGSRect rect;
182	CGSError err;
183
184	int reps = 0;
185
186	if (cid == NULL) {
187		cid = _CGSDefaultConnection();
188		if (cid == NULL) {
189			return 0;
190		}
191	}
192
193	if (idx < 0) {
194		idx = macosxCGS_find_index(win);
195	}
196	if (idx < 0) {
197		return 0;
198	}
199
200	while (dnow() < t + 0.001 * macosx_icon_anim_time)  {
201		err = CGSGetScreenRectForWindow(cid, win, &rect);
202		if (err != 0) {
203			break;
204		}
205		x = (int) rect.origin.x;
206		y = (int) rect.origin.y;
207		w = (int) rect.size.width;
208		h = (int) rect.size.height;
209
210		if (grow) {
211			macwins[idx].x      = x;
212			macwins[idx].y      = y;
213			macwins[idx].width  = w;
214			macwins[idx].height = h;
215		}
216
217		if (0) fprintf(stderr, " chase: %03dx%03d+%03d+%03d  %d\n", w, h, x, y, win);
218		if (x == xp && y == yp && w == wp && h == hp)  {
219			reps++;
220			if (reps >= 2) {
221				break;
222			}
223		} else {
224			diffs++;
225			reps = 0;
226		}
227		xp = x;
228		yp = y;
229		wp = w;
230		hp = h;
231		usleep(50 * 1000);
232	}
233	if (diffs >= 2) {
234		return 1;
235	} else {
236		return 0;
237	}
238}
239
240extern int macosx_check_clipped(int win, int *list, int n);
241extern int macosx_check_offscreen(int win);
242
243static int check_clipped(int win) {
244	int i, n = 0, win2;
245	for (i = 0; i < (int) _wins_mapped_cnt; i++) {
246		win2 = (int) _wins_mapped[i];
247		if (win2 == win) {
248			break;
249		}
250		_wins_int[n++] = win2;
251	}
252	return macosx_check_clipped(win, _wins_int, n);
253}
254
255static int check_offscreen(int win) {
256	return macosx_check_offscreen(win);
257}
258
259extern int macosx_ncache_macmenu;
260
261
262void macosxCGS_get_all_windows(void) {
263	static double last = 0.0;
264	static int totcnt = 0;
265	double dt = 0.0, now = dnow();
266	int i, db = 0, whist_prv = 0, maxwin = 0, whist_skip = 0;
267	CGSWindowCount cap = (CGSWindowCount) MAXWINDAT;
268	CGSError err;
269
270	CGS_levelmax = 0;
271	CGS_levels[CGS_levelmax++] = (int) kCGDraggingWindowLevel;	/* 500 ? */
272	if (0) CGS_levels[CGS_levelmax++] = (int) kCGHelpWindowLevel;		/* 102 ? */
273	if (macosx_ncache_macmenu) CGS_levels[CGS_levelmax++] = (int) kCGPopUpMenuWindowLevel;	/* 101 pulldown menu */
274	CGS_levels[CGS_levelmax++] = (int) kCGMainMenuWindowLevelKey;	/*  24 ? */
275	CGS_levels[CGS_levelmax++] = (int) kCGModalPanelWindowLevel;	/*   8 open dialog box */
276	CGS_levels[CGS_levelmax++] = (int) kCGFloatingWindowLevel;	/*   3 ? */
277	CGS_levels[CGS_levelmax++] = (int) kCGNormalWindowLevel;	/*   0 regular window */
278
279	if (cid == NULL) {
280		cid = _CGSDefaultConnection();
281		if (cid == NULL) {
282			return;
283		}
284	}
285
286	if (dt > 0.0 && now < last + dt) {
287		return;
288	}
289
290	last = now;
291
292	macwinmax = 0;
293
294	totcnt++;
295
296	if (ncache > 0) {
297		whist_prv = whist_idx++;
298		if (whist_prv < 0) {
299			whist_skip = 1;
300			whist_prv = 0;
301		}
302		whist_idx = whist_idx % WINHISTMAX;
303		for (i=0; i < WINHISTNUM; i++) {
304			whist[whist_idx][i] = 0;
305			qlook[i] = -1;
306		}
307	}
308
309	err = CGSGetWindowList(cid, NULL, cap, _wins_all, &_wins_all_cnt);
310
311if (db) fprintf(stderr, "cnt: %d err: %d\n", (int) _wins_all_cnt, err);
312
313	if (err != 0) {
314		return;
315	}
316
317	for (i=0; i < (int) _wins_all_cnt; i++) {
318		CGSRect rect;
319		CGSWindowLevel level;
320		int j, keepit = 0;
321		err = CGSGetScreenRectForWindow(cid, _wins_all[i], &rect);
322		if (err != 0) {
323			continue;
324		}
325		if (rect.origin.x == 0 && rect.origin.y == 0) {
326			if (rect.size.width == dpy_x) {
327				if (rect.size.height == dpy_y) {
328					continue;
329				}
330			}
331		}
332		err = CGSGetWindowLevel(cid, _wins_all[i], &level);
333		if (err != 0) {
334			continue;
335		}
336		for (j=0; j<CGS_levelmax; j++) {
337			if ((int) level == CGS_levels[j]) {
338				keepit = 1;
339				break;
340			}
341		}
342		if (! keepit) {
343			continue;
344		}
345
346		macwins[macwinmax].level  = (int) level;
347		macwins[macwinmax].win    = (int) _wins_all[i];
348		macwins[macwinmax].x      = (int) rect.origin.x;
349		macwins[macwinmax].y      = (int) rect.origin.y;
350		macwins[macwinmax].width  = (int) rect.size.width;
351		macwins[macwinmax].height = (int) rect.size.height;
352		macwins[macwinmax].mapped = 0;
353		macwins[macwinmax].clipped = 0;
354		macwins[macwinmax].ncache_only = 0;
355		if (level == kCGPopUpMenuWindowLevel) {
356			macwins[macwinmax].ncache_only = 1;
357		}
358
359if (0 || db) fprintf(stderr, "i=%03d ID: %06d  x: %03d  y: %03d  w: %03d h: %03d level: %d\n", i, _wins_all[i],
360    (int) rect.origin.x, (int) rect.origin.y,(int) rect.size.width, (int) rect.size.height, (int) level);
361
362		if (macwins[macwinmax].win < WINHISTNUM) {
363			qlook[macwins[macwinmax].win] = macwinmax;
364			if (macwins[macwinmax].win > maxwin) {
365				maxwin = macwins[macwinmax].win;
366			}
367		}
368
369		macwinmax++;
370	}
371
372	err = CGSGetOnScreenWindowList(cid, NULL, cap, _wins_mapped, &_wins_mapped_cnt);
373
374if (db) fprintf(stderr, "cnt: %d err: %d\n", (int) _wins_mapped_cnt, err);
375
376	if (err != 0) {
377		return;
378	}
379
380	for (i=0; i < (int) _wins_mapped_cnt; i++) {
381		int j, idx = -1;
382		int win = (int) _wins_mapped[i];
383
384		if (0 <= win && win < WINHISTNUM) {
385			j = qlook[win];
386			if (j >= 0 && macwins[j].win == win) {
387				idx = j;
388			}
389		}
390		if (idx < 0) {
391			for (j=0; j < macwinmax; j++) {
392				if (macwins[j].win == win) {
393					idx = j;
394					break;
395				}
396			}
397		}
398		if (idx >= 0) {
399			macwins[idx].mapped = 1;
400		}
401	}
402
403	if (ncache > 0) {
404		int nv= 0, NBMAX = 64;
405		int nv_win[64];
406		int nv_lvl[64];
407		int nv_vis[64];
408
409		for (i=0; i < macwinmax; i++) {
410			int win = macwins[i].win;
411			char prev, curr;
412
413			if (win >= WINHISTNUM) {
414				continue;
415			}
416
417			whist[whist_idx][win] |= is_exist;
418			if (macwins[i].mapped) {
419				whist[whist_idx][win] |= is_mapped;
420				if (check_clipped(win)) {
421					whist[whist_idx][win] |= is_clipped;
422					macwins[i].clipped = 1;
423				}
424				if (check_offscreen(win)) {
425					whist[whist_idx][win] |= is_offscreen;
426				}
427			} else {
428				whist[whist_idx][win] |= is_offscreen;
429			}
430
431			curr = whist[whist_idx][win];
432			prev = whist[whist_prv][win];
433
434			if (whist_skip) {
435				;
436			} else if ( !(prev & is_mapped) && (curr & is_mapped)) {
437				/* MapNotify */
438				if (0) fprintf(stderr, "MapNotify:   %d/%d  %d               %.4f tot=%d\n", prev, curr, win, dnowx(), totcnt);
439				macosx_add_mapnotify(win, macwins[i].level, 1);
440				if (0) macosxCGS_follow_animation_win(win, i, 1);
441
442			} else if ( !(curr & is_mapped) && (prev & is_mapped)) {
443				/* UnmapNotify */
444				if (0) fprintf(stderr, "UnmapNotify: %d/%d  %d               %.4f A tot=%d\n", prev, curr, win, dnowx(), totcnt);
445				macosx_add_mapnotify(win, macwins[i].level, 0);
446			} else if ( !(prev & is_exist) && (curr & is_exist)) {
447				/* CreateNotify */
448				if (0) fprintf(stderr, "CreateNotify:%d/%d  %d               %.4f whist: %d/%d 0x%x tot=%d\n", prev, curr, win, dnowx(), whist_prv, whist_idx, win, totcnt);
449				macosx_add_create(win, macwins[i].level);
450				if (curr & is_mapped) {
451					if (0) fprintf(stderr, "MapNotify:   %d/%d  %d               %.4f tot=%d\n", prev, curr, win, dnowx(), totcnt);
452					macosx_add_mapnotify(win, macwins[i].level, 1);
453				}
454			}
455			if (whist_skip) {
456				;
457			} else if (nv >= NBMAX) {
458				;
459			} else if (!(curr & is_mapped)) {
460				;
461			} else if (!(prev & is_mapped)) {
462				if (1) {
463					;
464				} else if (curr & is_clipped) {
465					if (0) fprintf(stderr, "VisibNotify: %d/%d  %d               OBS tot=%d\n", prev, curr, win, totcnt);
466					nv_win[nv] = win;
467					nv_lvl[nv] = macwins[i].level;
468					nv_vis[nv++] = 1;
469				} else {
470					if (0) fprintf(stderr, "VisibNotify: %d/%d  %d               UNOBS tot=%d\n", prev, curr, win, totcnt);
471					nv_win[nv] = win;
472					nv_lvl[nv] = macwins[i].level;
473					nv_vis[nv++] = 0;
474				}
475			} else {
476				if        ( !(prev & is_clipped) &&  (curr & is_clipped) ) {
477					if (0) fprintf(stderr, "VisibNotify: %d/%d  %d               OBS tot=%d\n", prev, curr, win, totcnt);
478					nv_win[nv] = win;
479					nv_lvl[nv] = macwins[i].level;
480					nv_vis[nv++] = 1;
481				} else if (  (prev & is_clipped) && !(curr & is_clipped) ) {
482					if (0) fprintf(stderr, "VisibNotify: %d/%d  %d               UNOBS tot=%d\n", prev, curr, win, totcnt);
483					nv_win[nv] = win;
484					nv_lvl[nv] = macwins[i].level;
485					nv_vis[nv++] = 0;
486				}
487			}
488		}
489		for (i=0; i < maxwin; i++) {
490			char prev, curr;
491			int win = i;
492			int q = qlook[i];
493			int lvl = 0;
494
495			if (whist_skip) {
496				break;
497			}
498
499			if (q >= 0) {
500				lvl = macwins[q].level;
501			}
502			curr = whist[whist_idx][win];
503			prev = whist[whist_prv][win];
504			if (!(curr & is_exist) && (prev & is_exist)) {
505				if (prev & is_mapped) {
506					if (0) fprintf(stderr, "UnmapNotify: %d/%d  %d               %.4f B tot=%d\n", prev, curr, win, dnowx(), totcnt);
507					macosx_add_mapnotify(win, lvl, 0);
508				}
509				/* DestroyNotify */
510				if (0) fprintf(stderr, "DestroNotify:%d/%d  %d               %.4f tot=%d\n", prev, curr, win, dnowx(), totcnt);
511				macosx_add_destroy(win, lvl);
512			}
513		}
514		if (nv) {
515			int k;
516			for (k = 0; k < nv; k++) {
517				macosx_add_visnotify(nv_win[k], nv_lvl[k], nv_vis[k]);
518			}
519		}
520	}
521}
522
523#if 1
524NSLock *pblock = nil;
525NSString *pbstr = nil;
526NSString *cuttext = nil;
527
528int pbcnt = -1;
529NSStringEncoding pbenc = NSWindowsCP1252StringEncoding;
530
531void macosxGCS_initpb(void) {
532	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
533	pblock = [[NSLock alloc] init];
534	if (![NSPasteboard generalPasteboard]) {
535		macosx_log("macosxGCS_initpb: **PASTEBOARD INACCESSIBLE**.\n");
536		macosx_log("macosxGCS_initpb: Clipboard exchange will NOT work.\n");
537		macosx_log("macosxGCS_initpb: Start x11vnc *inside* Aqua for Clipboard.\n");
538		pbcnt = 0;
539		pbstr = [[NSString alloc] initWithString:@"\e<PASTEBOARD INACCESSIBLE>\e"];
540	}
541	[pool release];
542}
543
544void macosxGCS_set_pasteboard(char *str, int len) {
545	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
546	if (pbcnt != 0) {
547		[pblock lock];
548		[cuttext release];
549		cuttext = [[NSString alloc] initWithData:[NSData dataWithBytes:str length:len] encoding: pbenc];
550		if ([[NSPasteboard generalPasteboard] declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil]) {
551			NS_DURING
552				[[NSPasteboard generalPasteboard] setString:cuttext forType:NSStringPboardType];
553			NS_HANDLER
554				fprintf(stderr, "macosxGCS_set_pasteboard: problem writing to pasteboard\n");
555			NS_ENDHANDLER
556		} else {
557			fprintf(stderr, "macosxGCS_set_pasteboard: problem writing to pasteboard\n");
558		}
559		[cuttext release];
560		cuttext = nil;
561		[pblock unlock];
562	}
563	[pool release];
564}
565
566extern void macosx_send_sel(char *, int);
567
568void macosxGCS_poll_pb(void) {
569
570	static double dlast = 0.0;
571	double now = dnow();
572
573	if (now < dlast + 0.2) {
574		return;
575	}
576	dlast = now;
577
578   {
579	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
580	[pblock lock];
581	if (pbcnt != [[NSPasteboard generalPasteboard] changeCount]) {
582		pbcnt = [[NSPasteboard generalPasteboard] changeCount];
583		[pbstr release];
584		pbstr = nil;
585		if ([[NSPasteboard generalPasteboard] availableTypeFromArray:[NSArray arrayWithObject:NSStringPboardType]]) {
586			pbstr = [[[NSPasteboard generalPasteboard] stringForType:NSStringPboardType] copy];
587			if (pbstr) {
588				NSData *str = [pbstr dataUsingEncoding:pbenc allowLossyConversion:YES];
589				if ([str length]) {
590					macosx_send_sel((char *) [str bytes], [str length]);
591				}
592			}
593		}
594	}
595	[pblock unlock];
596	[pool release];
597   }
598}
599#endif
600
601#endif	/* __APPLE__ */
602
603