xref: /inferno-os/emu/port/win-x11a.c (revision e45fa0eb0763b57d6fb0649c064bc3b95ccdea6c)
1 /*
2  * This implementation of the screen functions for X11 uses the
3  * portable implementation of the Inferno drawing operations (libmemdraw)
4  * to do the work, then has flushmemscreen copy the result to the X11 display.
5  * Thus it potentially supports all colour depths but with a possible
6  * performance penalty (although it tries to use the X11 shared memory extension
7  * to copy the result to the screen, which might reduce the latter).
8  *
9  *       CraigN
10  */
11 
12 #define _GNU_SOURCE 1
13 #define XTHREADS
14 #include "dat.h"
15 #include "fns.h"
16 #undef log2
17 #include <draw.h>
18 #include "cursor.h"
19 #include "keyboard.h"
20 #include <sys/types.h>
21 #include <sys/stat.h>
22 #include <fcntl.h>
23 
24 #define Colormap	XColormap
25 #define Cursor		XCursor
26 #define Display		XDisplay
27 #define Drawable	XDrawable
28 #define Font		XFont
29 #define GC		XGC
30 #define Point		XPoint
31 #define Rectangle	XRectangle
32 #define Screen		XScreen
33 #define Visual		XVisual
34 #define Window		XWindow
35 
36 #include <X11/Xlib.h>
37 #include <X11/Xatom.h>
38 #include <X11/Xutil.h>
39 #include <X11/keysym.h>
40 #include <X11/extensions/XShm.h>
41 
42 #include "keysym2ucs.h"
43 
44 #undef Colormap
45 #undef Cursor
46 #undef Display
47 #undef XDrawable
48 #undef Font
49 #undef GC
50 #undef Point
51 #undef Rectangle
52 #undef Screen
53 #undef Visual
54 #undef Window
55 
56 #include <sys/ipc.h>
57 #include <sys/shm.h>
58 
59 static int displaydepth;
60 extern ulong displaychan;
61 
62 enum
63 {
64 	DblTime	= 300		/* double click time in msec */
65 };
66 
67 /* screen data .... */
68 static uchar*	gscreendata;
69 static uchar*	xscreendata;
70 
71 XColor	map[256];	/* Inferno colormap array */
72 XColor	mapr[256];	/* Inferno red colormap array */
73 XColor	mapg[256];	/* Inferno green colormap array */
74 XColor	mapb[256];	/* Inferno blue colormap array */
75 XColor	map7[128];	/* Inferno colormap array */
76 uchar	map7to8[128][2];
77 
78 /* for copy/paste, lifted from plan9ports via drawterm */
79 static Atom clipboard;
80 static Atom utf8string;
81 static Atom targets;
82 static Atom text;
83 static Atom compoundtext;
84 
85 static Atom cursorchange;
86 
87 static XColormap		xcmap;		/* Default shared colormap  */
88 static int 		infernotox11[256]; /* Values for mapping between */
89 static int 		infernortox11[256]; /* Values for mapping between */
90 static int 		infernogtox11[256]; /* Values for mapping between */
91 static int 		infernobtox11[256]; /* Values for mapping between */
92 static int		triedscreen;
93 static XDrawable		xdrawable;
94 static void		xexpose(XEvent*);
95 static void		xmouse(XEvent*);
96 static void		xkeyboard(XEvent*);
97 static void		xsetcursor(XEvent*);
98 static void		xkbdproc(void*);
99 static void		xdestroy(XEvent*);
100 static void		xselect(XEvent*, XDisplay*);
101 static void		xproc(void*);
102 static void		xinitscreen(int, int, ulong, ulong*, int*);
103 static void		initxcmap(XWindow);
104 static XGC		creategc(XDrawable);
105 static void		graphicsgmap(XColor*, int);
106 static void		graphicscmap(XColor*);
107 static void		graphicsrgbmap(XColor*, XColor*, XColor*);
108 
109 static int		xscreendepth;
110 static	XDisplay*	xdisplay;	/* used holding draw lock */
111 static	XDisplay*	xmcon;	/* used only in xproc */
112 static	XDisplay*	xkbdcon;	/* used only in xkbdproc */
113 static	XDisplay*	xsnarfcon;	/* used holding clip.lk */
114 static XVisual		*xvis;
115 static XGC		xgc;
116 static XImage 		*img;
117 static int              is_shm;
118 
119 static int putsnarf, assertsnarf;
120 char *gkscanid = "emu_x11";
121 
122 /*
123  * The documentation for the XSHM extension implies that if the server
124  * supports XSHM but is not the local machine, the XShm calls will
125  * return False; but this turns out not to be the case.  Instead, the
126  * server throws a BadAccess error.  So, we need to catch X errors
127  * around all of our XSHM calls, sigh.
128  */
129 static int shm_got_x_error = 0;
130 static XErrorHandler old_handler = 0;
131 static XErrorHandler old_io_handler = 0;
132 
133 static int
134 shm_ehandler(XDisplay *dpy, XErrorEvent *error)
135 {
136 	shm_got_x_error = 1;
137 	return 0;
138 }
139 
140 static void
141 clean_errhandlers(void)
142 {
143 	/* remove X11 error handler(s) */
144 	if(old_handler)
145 		XSetErrorHandler(old_handler);
146 	old_handler = 0;
147 	if(old_io_handler)
148 		XSetErrorHandler(old_io_handler);
149 	old_io_handler = 0;
150 }
151 
152 static int
153 makesharedfb(void)
154 {
155 	XShmSegmentInfo *shminfo;
156 
157 	shminfo = malloc(sizeof(XShmSegmentInfo));
158 	if(shminfo == nil) {
159 		fprint(2, "emu: cannot allocate XShmSegmentInfo\n");
160 		cleanexit(0);
161 	}
162 
163 	/* setup to catch X11 error(s) */
164 	XSync(xdisplay, 0);
165 	shm_got_x_error = 0;
166 	if(old_handler != shm_ehandler)
167 		old_handler = XSetErrorHandler(shm_ehandler);
168 	if(old_io_handler != shm_ehandler)
169 		old_io_handler = XSetErrorHandler(shm_ehandler);
170 
171 	img = XShmCreateImage(xdisplay, xvis, xscreendepth, ZPixmap,
172 			      NULL, shminfo, Xsize, Ysize);
173 	XSync(xdisplay, 0);
174 
175 	/* did we get an X11 error? if so then try without shm */
176 	if(shm_got_x_error) {
177 		free(shminfo);
178 		shminfo = NULL;
179 		clean_errhandlers();
180 		return 0;
181 	}
182 
183 	if(img == nil) {
184 		fprint(2, "emu: cannot allocate virtual screen buffer\n");
185 		cleanexit(0);
186 	}
187 
188 	shminfo->shmid = shmget(IPC_PRIVATE, img->bytes_per_line * img->height, IPC_CREAT|0777);
189 	shminfo->shmaddr = img->data = shmat(shminfo->shmid, 0, 0);
190 	shminfo->readOnly = True;
191 
192 	if(!XShmAttach(xdisplay, shminfo)) {
193 		fprint(2, "emu: cannot allocate virtual screen buffer\n");
194 		cleanexit(0);
195 	}
196 	XSync(xdisplay, 0);
197 
198 	/*
199 	 * Delete the shared segment right now; the segment
200 	 * won't actually go away until both the client and
201 	 * server have deleted it.  The server will delete it
202 	 * as soon as the client disconnects, so we might as
203 	 * well delete our side now as later.
204 	 */
205 	shmctl(shminfo->shmid, IPC_RMID, 0);
206 
207 	/* did we get an X11 error? if so then try without shm */
208 	if(shm_got_x_error) {
209 		XDestroyImage(img);
210 		XSync(xdisplay, 0);
211 		free(shminfo);
212 		shminfo = NULL;
213 		clean_errhandlers();
214 		return 0;
215 	}
216 
217 	gscreendata = malloc(Xsize * Ysize * (displaydepth >> 3));
218 	if(gscreendata == nil) {
219 		fprint(2, "emu: cannot allocate screen buffer (%dx%dx%d)\n", Xsize, Ysize, displaydepth);
220 		cleanexit(0);
221 	}
222 	xscreendata = (uchar*)img->data;
223 
224 	clean_errhandlers();
225 	return 1;
226 }
227 
228 uchar*
229 attachscreen(Rectangle *r, ulong *chan, int *d, int *width, int *softscreen)
230 {
231 	int depth;
232 
233 	Xsize &= ~0x3;	/* ensure multiple of 4 */
234 
235 	r->min.x = 0;
236 	r->min.y = 0;
237 	r->max.x = Xsize;
238 	r->max.y = Ysize;
239 
240 	if(!triedscreen){
241 		xinitscreen(Xsize, Ysize, displaychan, chan, d);
242 		/*
243 		 * moved xproc from here to end since it could cause an expose event and
244 		 * hence a flushmemscreen before xscreendata is initialized
245 		 */
246 	}
247 	else{
248 		*chan = displaychan;
249 		*d = displaydepth;
250 	}
251 
252 	*width = (Xsize/4)*(*d/8);
253 	*softscreen = 1;
254 	displaychan = *chan;
255 	displaydepth = *d;
256 
257 	/* check for X Shared Memory Extension */
258 	is_shm = XShmQueryExtension(xdisplay);
259 
260 	if(!is_shm || !makesharedfb()){
261 		is_shm = 0;
262 		depth = xscreendepth;
263 		if(depth == 24)
264 			depth = 32;
265 
266 		/* allocate virtual screen */
267 		gscreendata = malloc(Xsize * Ysize * (displaydepth >> 3));
268 		xscreendata = malloc(Xsize * Ysize * (depth >> 3));
269 		if(gscreendata == nil || xscreendata == nil) {
270 			fprint(2, "emu: can not allocate virtual screen buffer (%dx%dx%d[%d])\n", Xsize, Ysize, displaydepth, depth);
271 			return 0;
272 		}
273 		img = XCreateImage(xdisplay, xvis, xscreendepth, ZPixmap, 0,
274 				   (char*)xscreendata, Xsize, Ysize, 8, Xsize * (depth >> 3));
275 		if(img == nil) {
276 			fprint(2, "emu: can not allocate virtual screen buffer (%dx%dx%d)\n", Xsize, Ysize, depth);
277 			return 0;
278 		}
279 
280 	}
281 
282 	if(!triedscreen){
283 		triedscreen = 1;
284 		kproc("xproc", xproc, xmcon, 0);
285 		kproc("xkbdproc", xkbdproc, xkbdcon, KPX11);	/* silly stack size for bloated X11 */
286 	}
287 
288 	return gscreendata;
289 }
290 
291 static void
292 copy32to32(Rectangle r)
293 {
294 	int dx, width;
295 	uchar *p, *ep, *cp;
296 	u32int v, w, *dp, *wp, *edp, *lp;
297 
298 	width = Dx(r);
299 	dx = Xsize - width;
300 	dp = (u32int*)(gscreendata + (r.min.y * Xsize + r.min.x) * 4);
301 	wp = (u32int*)(xscreendata + (r.min.y * Xsize + r.min.x) * 4);
302 	edp = (u32int*)(gscreendata + (r.max.y * Xsize + r.max.x) * 4);
303 	while(dp < edp) {
304 		lp = dp + width;
305 		while(dp < lp){
306 			v = *dp++;
307 			w = infernortox11[(v>>16)&0xff]<<16|infernogtox11[(v>>8)&0xff]<<8|infernobtox11[(v>>0)&0xff]<<0;
308 			*wp++ = w;
309 		}
310 		dp += dx;
311 		wp += dx;
312 	}
313 }
314 
315 static void
316 copy8to32(Rectangle r)
317 {
318 	int dx, width;
319 	uchar *p, *ep, *lp;
320 	u32int *wp;
321 
322 	width = Dx(r);
323 	dx = Xsize - width;
324 	p = gscreendata + r.min.y * Xsize + r.min.x;
325 	wp = (u32int *)(xscreendata + (r.min.y * Xsize + r.min.x) * 4);
326 	ep = gscreendata + r.max.y * Xsize + r.max.x;
327 	while(p < ep) {
328 		lp = p + width;
329 		while(p < lp)
330 			*wp++ = infernotox11[*p++];
331 		p += dx;
332 		wp += dx;
333 	}
334 }
335 
336 static void
337 copy8to24(Rectangle r)
338 {
339 	int dx, width, v;
340 	uchar *p, *cp, *ep, *lp;
341 
342 	width = Dx(r);
343 	dx = Xsize - width;
344 	p = gscreendata + r.min.y * Xsize + r.min.x;
345 	cp = xscreendata + (r.min.y * Xsize + r.min.x) * 3;
346 	ep = gscreendata + r.max.y * Xsize + r.max.x;
347 	while(p < ep) {
348 		lp = p + width;
349 		while(p < lp){
350 			v = infernotox11[*p++];
351 			cp[0] = (v>>16)&0xff;
352 			cp[1] = (v>>8)&0xff;
353 			cp[2] = (v>>0)&0xff;
354 			cp += 3;
355 		}
356 		p += dx;
357 		cp += 3*dx;
358 	}
359 }
360 
361 static void
362 copy8to16(Rectangle r)
363 {
364 	int dx, width;
365 	uchar *p, *ep, *lp;
366 	u16int *sp;
367 
368 	width = Dx(r);
369 	dx = Xsize - width;
370 	p = gscreendata + r.min.y * Xsize + r.min.x;
371 	sp = (unsigned short *)(xscreendata + (r.min.y * Xsize + r.min.x) * 2);
372 	ep = gscreendata + r.max.y * Xsize + r.max.x;
373 	while(p < ep) {
374 		lp = p + width;
375 		while(p < lp)
376 			*sp++ = infernotox11[*p++];
377 		p += dx;
378 		sp += dx;
379 	}
380 }
381 
382 static void
383 copy8to8(Rectangle r)
384 {
385 	int dx, width;
386 	uchar *p, *cp, *ep, *lp;
387 
388 	width = Dx(r);
389 	dx = Xsize - width;
390 	p = gscreendata + r.min.y * Xsize + r.min.x;
391 	cp = xscreendata + r.min.y * Xsize + r.min.x;
392 	ep = gscreendata + r.max.y * Xsize + r.max.x;
393 	while(p < ep) {
394 		lp = p + width;
395 		while(p < lp)
396 			*cp++ = infernotox11[*p++];
397 		p += dx;
398 		cp += dx;
399 	}
400 }
401 
402 static void
403 copy8topixel(Rectangle r)
404 {
405 	int x, y;
406 	uchar *p;
407 
408 	/* mainly for 4-bit greyscale */
409 	for (y = r.min.y; y < r.max.y; y++) {
410 		x = r.min.x;
411 		p = gscreendata + y * Xsize + x;
412 		while (x < r.max.x)
413 			XPutPixel(img, x++, y, infernotox11[*p++]);
414 	}
415 }
416 
417 void
418 flushmemscreen(Rectangle r)
419 {
420 	char chanbuf[16];
421 
422 	// Clip to screen
423 	if(r.min.x < 0)
424 		r.min.x = 0;
425 	if(r.min.y < 0)
426 		r.min.y = 0;
427 	if(r.max.x >= Xsize)
428 		r.max.x = Xsize - 1;
429 	if(r.max.y >= Ysize)
430                 r.max.y = Ysize - 1;
431 	if(r.max.x <= r.min.x || r.max.y <= r.min.y)
432 		return;
433 
434 	switch(displaydepth){
435 	case 32:
436 		copy32to32(r);
437 		break;
438 	case 8:
439 		switch(xscreendepth){
440 		case 24:
441 			/* copy8to24(r); */	/* doesn't happen? */
442 			/* break */
443 		case 32:
444 			copy8to32(r);
445 			break;
446 		case 16:
447 			copy8to16(r);
448 			break;
449 		case 8:
450 			copy8to8(r);
451 			break;
452 		default:
453 			copy8topixel(r);
454 			break;
455 		}
456 		break;
457 	default:
458 		fprint(2, "emu: bad display depth %d chan %s xscreendepth %d\n", displaydepth,
459 			chantostr(chanbuf, displaychan), xscreendepth);
460 		cleanexit(0);
461 	}
462 
463 	XLockDisplay(xdisplay);
464 	/* Display image on X11 */
465 	if(is_shm)
466 		XShmPutImage(xdisplay, xdrawable, xgc, img, r.min.x, r.min.y, r.min.x, r.min.y, Dx(r), Dy(r), 0);
467 	else
468 		XPutImage(xdisplay, xdrawable, xgc, img, r.min.x, r.min.y, r.min.x, r.min.y, Dx(r), Dy(r));
469 	XSync(xdisplay, 0);
470 	XUnlockDisplay(xdisplay);
471 }
472 
473 static int
474 revbyte(int b)
475 {
476 	int r;
477 
478 	r = 0;
479 	r |= (b&0x01) << 7;
480 	r |= (b&0x02) << 5;
481 	r |= (b&0x04) << 3;
482 	r |= (b&0x08) << 1;
483 	r |= (b&0x10) >> 1;
484 	r |= (b&0x20) >> 3;
485 	r |= (b&0x40) >> 5;
486 	r |= (b&0x80) >> 7;
487 	return r;
488 }
489 
490 void
491 setpointer(int x, int y)
492 {
493 	drawqlock();
494 	XLockDisplay(xdisplay);
495 	XWarpPointer(xdisplay, None, xdrawable, 0, 0, 0, 0, x, y);
496 	XFlush(xdisplay);
497 	XUnlockDisplay(xdisplay);
498 	drawqunlock();
499 }
500 
501 static void
502 xkbdproc(void *arg)
503 {
504 	XEvent event;
505 	XDisplay *xd;
506 
507 	xd = arg;
508 
509 	/* BEWARE: the value of up is not defined for this proc on some systems */
510 
511 	XLockDisplay(xd);	/* should be ours alone */
512 	XSelectInput(xd, xdrawable, KeyPressMask | KeyReleaseMask);
513 	for(;;){
514 		XNextEvent(xd, &event);
515 		xkeyboard(&event);
516 		xsetcursor(&event);
517 	}
518 }
519 
520 static void
521 xproc(void *arg)
522 {
523 	ulong mask;
524 	XEvent event;
525 	XDisplay *xd;
526 
527 	closepgrp(up->env->pgrp);
528 	closefgrp(up->env->fgrp);
529 	closeegrp(up->env->egrp);
530 	closesigs(up->env->sigs);
531 
532 	xd = arg;
533 	mask = ButtonPressMask|
534 		ButtonReleaseMask|
535 		PointerMotionMask|
536 		Button1MotionMask|
537 		Button2MotionMask|
538 		Button3MotionMask|
539 		Button4MotionMask|
540 		Button5MotionMask|
541 		ExposureMask|
542 		StructureNotifyMask;
543 
544 	XLockDisplay(xd);	/* should be ours alone */
545 	XSelectInput(xd, xdrawable, mask);
546 	for(;;){
547 		XNextEvent(xd, &event);
548 		xselect(&event, xd);
549 		xmouse(&event);
550 		xexpose(&event);
551 		xdestroy(&event);
552 	}
553 }
554 
555 /*
556  * this crud is here because X11 can put huge amount of data
557  * on the stack during keyboard translation and cursor changing(!).
558  * we do both in a dedicated process with lots of stack, perhaps even enough.
559  */
560 
561 enum {
562 	CursorSize=	32	/* biggest cursor size */
563 };
564 
565 typedef struct ICursor ICursor;
566 struct ICursor {
567 	int	inuse;
568 	int	modify;
569 	int	hotx;
570 	int	hoty;
571 	int	w;
572 	int	h;
573 	uchar	src[(CursorSize/8)*CursorSize];	/* image and mask bitmaps */
574 	uchar	mask[(CursorSize/8)*CursorSize];
575 };
576 static ICursor icursor;
577 
578 static void
579 xcurslock(void)
580 {
581 	while(_tas(&icursor.inuse) != 0)
582 		osyield();
583 }
584 
585 static void
586 xcursunlock(void)
587 {
588 	coherence();
589 	icursor.inuse = 0;
590 }
591 
592 static void
593 xcursnotify(void)
594 {
595 	XClientMessageEvent e;
596 
597 	memset(&e, 0, sizeof e);
598 	e.type = ClientMessage;
599 	e.window = xdrawable;
600 	e.message_type = cursorchange;
601 	e.format = 8;
602 	XLockDisplay(xdisplay);
603 	XSendEvent(xdisplay, xdrawable, True, KeyPressMask, (XEvent*)&e);
604 	XFlush(xdisplay);
605 	XUnlockDisplay(xdisplay);
606 }
607 
608 void
609 drawcursor(Drawcursor* c)
610 {
611 	uchar *bs, *bc, *ps, *pm;
612 	int i, j, w, h, bpl;
613 
614 	if(c->data == nil){
615 		drawqlock();
616 		if(icursor.h != 0){
617 			xcurslock();
618 			icursor.h = 0;
619 			icursor.modify = 1;
620 			xcursunlock();
621 		}
622 		xcursnotify();
623 		drawqunlock();
624 		return;
625 	}
626 
627 	drawqlock();
628 	xcurslock();
629 	icursor.modify = 0;	/* xsetcursor will now ignore it */
630 	xcursunlock();
631 
632 	h = (c->maxy-c->miny)/2;	/* image, then mask */
633 	if(h > CursorSize)
634 		h = CursorSize;
635 	bpl = bytesperline(Rect(c->minx, c->miny, c->maxx, c->maxy), 1);
636 	w = bpl;
637 	if(w > CursorSize/8)
638 		w = CursorSize/8;
639 
640 	ps = icursor.src;
641 	pm = icursor.mask;
642 	bc = c->data;
643 	bs = c->data + h*bpl;
644 	for(i = 0; i < h; i++){
645 		for(j = 0; j < bpl && j < w; j++) {
646 			*ps++ = revbyte(bs[j]);
647 			*pm++ = revbyte(bs[j] | bc[j]);
648 		}
649 		bs += bpl;
650 		bc += bpl;
651 	}
652 	icursor.h = h;
653 	icursor.w = w*8;
654 	icursor.hotx = c->hotx;
655 	icursor.hoty = c->hoty;
656 	icursor.modify = 1;
657 	xcursnotify();
658 	drawqunlock();
659 }
660 
661 static void
662 xsetcursor(XEvent *e)
663 {
664 	ICursor ic;
665 	XCursor xc;
666 	XColor fg, bg;
667 	Pixmap xsrc, xmask;
668 	static XCursor xcursor;
669 
670 	if(e->type != ClientMessage || !e->xclient.send_event || e->xclient.message_type != cursorchange)
671 		return;
672 
673 	xcurslock();
674 	if(icursor.modify == 0){
675 		xcursunlock();
676 		return;
677 	}
678 	icursor.modify = 0;
679 	if(icursor.h == 0){
680 		xcursunlock();
681 		/* set the default system cursor */
682 		if(xcursor != 0) {
683 			XFreeCursor(xkbdcon, xcursor);
684 			xcursor = 0;
685 		}
686 		XUndefineCursor(xkbdcon, xdrawable);
687 		XFlush(xkbdcon);
688 		return;
689 	}
690 	ic = icursor;
691 	xcursunlock();
692 
693 	xsrc = XCreateBitmapFromData(xkbdcon, xdrawable, (char*)ic.src, ic.w, ic.h);
694 	xmask = XCreateBitmapFromData(xkbdcon, xdrawable, (char*)ic.mask, ic.w, ic.h);
695 
696 	fg = map[0];
697 	bg = map[255];
698 	fg.pixel = infernotox11[0];
699 	bg.pixel = infernotox11[255];
700 	xc = XCreatePixmapCursor(xkbdcon, xsrc, xmask, &fg, &bg, -ic.hotx, -ic.hoty);
701 	if(xc != 0) {
702 		XDefineCursor(xkbdcon, xdrawable, xc);
703 		if(xcursor != 0)
704 			XFreeCursor(xkbdcon, xcursor);
705 		xcursor = xc;
706 	}
707 	XFreePixmap(xkbdcon, xsrc);
708 	XFreePixmap(xkbdcon, xmask);
709 	XFlush(xkbdcon);
710 }
711 
712 typedef struct Mg Mg;
713 struct Mg
714 {
715 	int	code;
716 	int	bit;
717 	int	len;
718 	ulong	mask;
719 };
720 
721 static int
722 maskx(Mg* g, int code, ulong mask)
723 {
724 	int i;
725 
726 	for(i=0; i<32; i++)
727 		if(mask & (1<<i))
728 			break;
729 	if(i == 32)
730 		return 0;
731 	g->code = code;
732 	g->bit = i;
733 	g->mask = mask;
734 	for(g->len = 0; i<32 && (mask & (1<<i))!=0; i++)
735 		g->len++;
736 	return 1;
737 }
738 
739 /*
740  * for a given depth, we need to check the available formats
741  * to find how many actual bits are used per pixel.
742  */
743 static int
744 xactualdepth(int screenno, int depth)
745 {
746 	XPixmapFormatValues *pfmt;
747 	int i, n;
748 
749 	pfmt = XListPixmapFormats(xdisplay, &n);
750 	for(i=0; i<n; i++)
751 		if(pfmt[i].depth == depth)
752 			return pfmt[i].bits_per_pixel;
753 	return -1;
754 }
755 
756 static int
757 xtruevisual(int screenno, int reqdepth, XVisualInfo *vi, ulong *chan)
758 {
759 	XVisual *xv;
760 	Mg r, g, b;
761 	int pad, d;
762 	ulong c;
763 	char buf[30];
764 
765 	if(XMatchVisualInfo(xdisplay, screenno, reqdepth, TrueColor, vi) ||
766 	   XMatchVisualInfo(xdisplay, screenno, reqdepth, DirectColor, vi)){
767 		xv = vi->visual;
768 		if(maskx(&r, CRed, xv->red_mask) &&
769 		   maskx(&g, CGreen, xv->green_mask) &&
770 		   maskx(&b, CBlue, xv->blue_mask)){
771 			d = xactualdepth(screenno, reqdepth);
772 			if(d < 0)
773 				return 0;
774 			pad = d - (r.len + g.len + b.len);
775 			if(0){
776 				fprint(2, "r: %8.8lux %d %d\ng: %8.8lux %d %d\nb: %8.8lux %d %d\n",
777 				 xv->red_mask, r.bit, r.len, xv->green_mask, g.bit, g.len, xv->blue_mask, b.bit, b.len);
778 			}
779 			if(r.bit > b.bit)
780 				c = CHAN3(CRed, r.len, CGreen, g.len, CBlue, b.len);
781 			else
782 				c = CHAN3(CBlue, b.len, CGreen, g.len, CRed, r.len);
783 			if(pad > 0)
784 				c |= CHAN1(CIgnore, pad) << 24;
785 			*chan = c;
786 			xscreendepth = reqdepth;
787 			if(0)
788 				fprint(2, "chan=%s reqdepth=%d bits=%d\n", chantostr(buf, c), reqdepth, d);
789 			return 1;
790 		}
791 	}
792 	return 0;
793 }
794 
795 static int
796 xmapvisual(int screenno, XVisualInfo *vi, ulong *chan)
797 {
798 	if(XMatchVisualInfo(xdisplay, screenno, 8, PseudoColor, vi) ||
799 	   XMatchVisualInfo(xdisplay, screenno, 8, StaticColor, vi)){
800 		*chan = CMAP8;
801 		xscreendepth = 8;
802 		return 1;
803 	}
804 	return 0;
805 }
806 
807 static void
808 xinitscreen(int xsize, int ysize, ulong reqchan, ulong *chan, int *d)
809 {
810 	char *argv[2];
811 	char *dispname;
812 	XWindow rootwin;
813 	XWMHints hints;
814 	XVisualInfo xvi;
815 	XScreen *screen;
816 	int rootscreennum;
817 	XTextProperty name;
818 	XClassHint classhints;
819 	XSizeHints normalhints;
820 	XSetWindowAttributes attrs;
821 	char buf[30];
822 	int i;
823 
824 	xdrawable = 0;
825 
826 	dispname = getenv("DISPLAY");
827 	if(dispname == nil)
828 		dispname = "not set";
829 	XInitThreads();
830 	xdisplay = XOpenDisplay(NULL);
831 	if(xdisplay == 0){
832 		fprint(2, "emu: win-x11 open %r, DISPLAY is %s\n", dispname);
833 		cleanexit(0);
834 	}
835 
836 	rootscreennum = DefaultScreen(xdisplay);
837 	rootwin = DefaultRootWindow(xdisplay);
838 	xscreendepth = DefaultDepth(xdisplay, rootscreennum);
839 	xvis = DefaultVisual(xdisplay, rootscreennum);
840 	screen = DefaultScreenOfDisplay(xdisplay);
841 	xcmap = DefaultColormapOfScreen(screen);
842 
843 	if(reqchan == 0){
844 		*chan = 0;
845 		if(xscreendepth <= 16){	/* try for better colour */
846 			xtruevisual(rootscreennum, 16, &xvi, chan) ||
847 			xtruevisual(rootscreennum, 15, &xvi, chan) ||
848 			xtruevisual(rootscreennum, 24, &xvi, chan) ||
849 			xmapvisual(rootscreennum, &xvi, chan);
850 		}else{
851 			xtruevisual(rootscreennum, xscreendepth, &xvi, chan) ||
852 			xtruevisual(rootscreennum, 24, &xvi, chan);
853 		}
854 		if(*chan == 0){
855 			fprint(2, "emu: could not find suitable x11 pixel format for depth %d on this display\n", xscreendepth);
856 			cleanexit(0);
857 		}
858 		reqchan = *chan;
859 		*d = chantodepth(reqchan);
860 		xvis = xvi.visual;
861 	}else{
862 		*chan = reqchan;		/* not every channel description will work */
863 		*d = chantodepth(reqchan);
864 		if(*d != xactualdepth(rootscreennum, *d)){
865 			fprint(2, "emu: current x11 display configuration does not support %s (depth %d) directly\n",
866 				chantostr(buf, reqchan), *d);
867 			cleanexit(0);
868 		}
869 	}
870 
871 	if(xvis->class != StaticColor) {
872 		if(TYPE(*chan) == CGrey)
873 			graphicsgmap(map, NBITS(reqchan));
874 		else{
875 			graphicscmap(map);
876 			graphicsrgbmap(mapr, mapg, mapb);
877 		}
878 		initxcmap(rootwin);
879 	}
880 
881 	memset(&attrs, 0, sizeof(attrs));
882 	attrs.colormap = xcmap;
883 	attrs.background_pixel = 0;
884 	attrs.border_pixel = 0;
885 	/* attrs.override_redirect = 1;*/ /* WM leave me alone! |CWOverrideRedirect */
886 	xdrawable = XCreateWindow(xdisplay, rootwin, 0, 0, xsize, ysize, 0, xscreendepth,
887 				  InputOutput, xvis, CWBackPixel|CWBorderPixel|CWColormap, &attrs);
888 
889 	/*
890 	 * set up property as required by ICCCM
891 	 */
892 	memset(&name, 0, sizeof(name));
893 	name.value = (uchar*)"inferno";
894 	name.encoding = XA_STRING;
895 	name.format = 8;
896 	name.nitems = strlen((char*)name.value);
897 
898 	memset(&normalhints, 0, sizeof(normalhints));
899 	normalhints.flags = USSize|PMaxSize;
900 	normalhints.max_width = normalhints.width = xsize;
901 	normalhints.max_height = normalhints.height = ysize;
902 	hints.flags = InputHint|StateHint;
903 	hints.input = 1;
904 	hints.initial_state = NormalState;
905 
906 	memset(&classhints, 0, sizeof(classhints));
907 	classhints.res_name = "inferno";
908 	classhints.res_class = "Inferno";
909 	argv[0] = "inferno";
910 	argv[1] = nil;
911 	XSetWMProperties(xdisplay, xdrawable,
912 		&name,			/* XA_WM_NAME property for ICCCM */
913 		&name,			/* XA_WM_ICON_NAME */
914 		argv,			/* XA_WM_COMMAND */
915 		1,			/* argc */
916 		&normalhints,		/* XA_WM_NORMAL_HINTS */
917 		&hints,			/* XA_WM_HINTS */
918 		&classhints);		/* XA_WM_CLASS */
919 
920 	XMapWindow(xdisplay, xdrawable);
921 	XFlush(xdisplay);
922 
923 	xgc = creategc(xdrawable);
924 
925 	xmcon = XOpenDisplay(NULL);
926 	xsnarfcon = XOpenDisplay(NULL);
927 	xkbdcon = XOpenDisplay(NULL);
928 	if(xmcon == 0 || xsnarfcon == 0 || xkbdcon == 0){
929 		fprint(2, "emu: win-x11 open %r, DISPLAY is %s\n", dispname);
930 		cleanexit(0);
931 	}
932 
933 	clipboard = XInternAtom(xmcon, "CLIPBOARD", False);
934 	utf8string = XInternAtom(xmcon, "UTF8_STRING", False);
935 	targets = XInternAtom(xmcon, "TARGETS", False);
936 	text = XInternAtom(xmcon, "TEXT", False);
937 	compoundtext = XInternAtom(xmcon, "COMPOUND_TEXT", False);
938 
939 	cursorchange = XInternAtom(xkbdcon, "TheCursorHasChanged", False);
940 
941 }
942 
943 static void
944 graphicsgmap(XColor *map, int d)
945 {
946 	int i, j, s, m, p;
947 
948 	s = 8-d;
949 	m = 1;
950 	while(--d >= 0)
951 		m *= 2;
952 	m = 255/(m-1);
953 	for(i=0; i < 256; i++){
954 		j = (i>>s)*m;
955 		p = 255-i;
956 		map[p].red = map[p].green = map[p].blue = (255-j)*0x0101;
957 		map[p].pixel = p;
958 		map[p].flags = DoRed|DoGreen|DoBlue;
959 	}
960 }
961 
962 static void
963 graphicscmap(XColor *map)
964 {
965 	int r, g, b, cr, cg, cb, v, num, den, idx, v7, idx7;
966 
967 	for(r=0; r!=4; r++) {
968 		for(g = 0; g != 4; g++) {
969 			for(b = 0; b!=4; b++) {
970 				for(v = 0; v!=4; v++) {
971 					den=r;
972 					if(g > den)
973 						den=g;
974 					if(b > den)
975 						den=b;
976 					/* divide check -- pick grey shades */
977 					if(den==0)
978 						cr=cg=cb=v*17;
979 					else {
980 						num=17*(4*den+v);
981 						cr=r*num/den;
982 						cg=g*num/den;
983 						cb=b*num/den;
984 					}
985 					idx = r*64 + v*16 + ((g*4 + b + v - r) & 15);
986 					/* was idx = 255 - idx; */
987 					map[idx].red = cr*0x0101;
988 					map[idx].green = cg*0x0101;
989 					map[idx].blue = cb*0x0101;
990 					map[idx].pixel = idx;
991 					map[idx].flags = DoRed|DoGreen|DoBlue;
992 
993 					v7 = v >> 1;
994 					idx7 = r*32 + v7*16 + g*4 + b;
995 					if((v & 1) == v7){
996 						map7to8[idx7][0] = idx;
997 						if(den == 0) { 		/* divide check -- pick grey shades */
998 							cr = ((255.0/7.0)*v7)+0.5;
999 							cg = cr;
1000 							cb = cr;
1001 						}
1002 						else {
1003 							num=17*15*(4*den+v7*2)/14;
1004 							cr=r*num/den;
1005 							cg=g*num/den;
1006 							cb=b*num/den;
1007 						}
1008 						map7[idx7].red = cr*0x0101;
1009 						map7[idx7].green = cg*0x0101;
1010 						map7[idx7].blue = cb*0x0101;
1011 						map7[idx7].pixel = idx7;
1012 						map7[idx7].flags = DoRed|DoGreen|DoBlue;
1013 					}
1014 					else
1015 						map7to8[idx7][1] = idx;
1016 				}
1017 			}
1018 		}
1019 	}
1020 }
1021 
1022 static void
1023 graphicsrgbmap(XColor *mapr, XColor *mapg, XColor *mapb)
1024 {
1025 	int i;
1026 
1027 	memset(mapr, 0, 256*sizeof(XColor));
1028 	memset(mapg, 0, 256*sizeof(XColor));
1029 	memset(mapb, 0, 256*sizeof(XColor));
1030 	for(i=0; i < 256; i++){
1031 		mapr[i].red = mapg[i].green = mapb[i].blue = i*0x0101;
1032 		mapr[i].pixel = mapg[i].pixel = mapb[i].pixel = i;
1033 		mapr[i].flags = mapg[i].flags = mapb[i].flags = DoRed|DoGreen|DoBlue;
1034 	}
1035 }
1036 
1037 /*
1038  * Initialize and install the Inferno colormap as a private colormap for this
1039  * application.  Inferno gets the best colors here when it has the cursor focus.
1040  */
1041 static void
1042 initxcmap(XWindow w)
1043 {
1044 	XColor c;
1045 	int i;
1046 
1047 	if(xscreendepth <= 1)
1048 		return;
1049 
1050 	switch(xvis->class){
1051 	case TrueColor:
1052 	case DirectColor:
1053 		for(i = 0; i < 256; i++) {
1054 			c = map[i];
1055 			/* find index into colormap for our RGB */
1056 			if(!XAllocColor(xdisplay, xcmap, &c)) {
1057 				fprint(2, "emu: win-x11 can't alloc color\n");
1058 				cleanexit(0);
1059 			}
1060 			infernotox11[map[i].pixel] = c.pixel;
1061 			if(xscreendepth >= 24){
1062 				c = mapr[i];
1063 				XAllocColor(xdisplay, xcmap, &c);
1064 				infernortox11[i] = (c.pixel>>16)&0xff;
1065 				c = mapg[i];
1066 				XAllocColor(xdisplay, xcmap, &c);
1067 				infernogtox11[i] = (c.pixel>>8)&0xff;
1068 				c = mapb[i];
1069 				XAllocColor(xdisplay, xcmap, &c);
1070 				infernobtox11[i] = (c.pixel>>0)&0xff;
1071 			}
1072 		}
1073 if(0){int i, j; for(i=0;i<256; i+=16){print("%3d", i); for(j=i; j<i+16; j++)print(" %2.2ux/%2.2ux/%2.2ux", infernortox11[j], infernogtox11[j],infernobtox11[j]); print("\n");}}
1074 		/* TO DO: if the map(s) used give the identity map, don't use the map during copy */
1075 		break;
1076 
1077 	case PseudoColor:
1078 		if(xtblbit == 0){
1079 			xcmap = XCreateColormap(xdisplay, w, xvis, AllocAll);
1080 			XStoreColors(xdisplay, xcmap, map, 256);
1081 			for(i = 0; i < 256; i++)
1082 				infernotox11[i] = i;
1083 			/* TO DO: the map is the identity, so don't need the map in copy */
1084 		} else {
1085 			for(i = 0; i < 128; i++) {
1086 				c = map7[i];
1087 				if(!XAllocColor(xdisplay, xcmap, &c)) {
1088 					fprint(2, "emu: win-x11 can't alloc colors in default map, don't use -7\n");
1089 					cleanexit(0);
1090 				}
1091 				infernotox11[map7to8[i][0]] = c.pixel;
1092 				infernotox11[map7to8[i][1]] = c.pixel;
1093 			}
1094 		}
1095 		break;
1096 
1097 	default:
1098 		xtblbit = 0;
1099 		fprint(2, "emu: win-x11 unsupported visual class %d\n", xvis->class);
1100 		break;
1101 	}
1102 }
1103 
1104 static void
1105 xdestroy(XEvent *e)
1106 {
1107 	XDestroyWindowEvent *xe;
1108 	if(e->type != DestroyNotify)
1109 		return;
1110 	xe = (XDestroyWindowEvent*)e;
1111 	if(xe->window == xdrawable)
1112 		cleanexit(0);
1113 }
1114 
1115 /*
1116  * Disable generation of GraphicsExpose/NoExpose events in the XGC.
1117  */
1118 static XGC
1119 creategc(XDrawable d)
1120 {
1121 	XGCValues gcv;
1122 
1123 	gcv.function = GXcopy;
1124 	gcv.graphics_exposures = False;
1125 	return XCreateGC(xdisplay, d, GCFunction|GCGraphicsExposures, &gcv);
1126 }
1127 
1128 static void
1129 xexpose(XEvent *e)
1130 {
1131 	Rectangle r;
1132 	XExposeEvent *xe;
1133 
1134 	if(e->type != Expose)
1135 		return;
1136 	xe = (XExposeEvent*)e;
1137 	r.min.x = xe->x;
1138 	r.min.y = xe->y;
1139 	r.max.x = xe->x + xe->width;
1140 	r.max.y = xe->y + xe->height;
1141 	drawqlock();
1142 	flushmemscreen(r);
1143 	drawqunlock();
1144 }
1145 
1146 static void
1147 xkeyboard(XEvent *e)
1148 {
1149 	int ind, md;
1150 	KeySym k;
1151 
1152 	if(gkscanq != nil && (e->type == KeyPress || e->type == KeyRelease)){
1153 		uchar ch = e->xkey.keycode;
1154 		if(e->xany.type == KeyRelease)
1155 			ch |= 0x80;
1156 		qproduce(gkscanq, &ch, 1);
1157 		return;
1158 	}
1159 
1160 	/*
1161 	 * I tried using XtGetActionKeysym, but it didn't seem to
1162 	 * do case conversion properly
1163 	 * (at least, with Xterminal servers and R4 intrinsics)
1164 	 */
1165 	if(e->xany.type != KeyPress)
1166 		return;
1167 
1168 	md = e->xkey.state;
1169 	ind = 0;
1170 	if(md & ShiftMask)
1171 		ind = 1;
1172 	if(0){
1173 		k = XKeycodeToKeysym(e->xany.display, (KeyCode)e->xkey.keycode, ind);
1174 
1175 		/* May have to try unshifted version */
1176 		if(k == NoSymbol && ind == 1)
1177 			k = XKeycodeToKeysym(e->xany.display, (KeyCode)e->xkey.keycode, 0);
1178 	}else
1179 		XLookupString((XKeyEvent*)e, NULL, 0, &k, NULL);
1180 
1181 	if(k == XK_Multi_key || k == NoSymbol)
1182 		return;
1183 	if(k&0xFF00){
1184 		switch(k){
1185 		case XK_BackSpace:
1186 		case XK_Tab:
1187 		case XK_Escape:
1188 		case XK_Delete:
1189 		case XK_KP_0:
1190 		case XK_KP_1:
1191 		case XK_KP_2:
1192 		case XK_KP_3:
1193 		case XK_KP_4:
1194 		case XK_KP_5:
1195 		case XK_KP_6:
1196 		case XK_KP_7:
1197 		case XK_KP_8:
1198 		case XK_KP_9:
1199 		case XK_KP_Divide:
1200 		case XK_KP_Multiply:
1201 		case XK_KP_Subtract:
1202 		case XK_KP_Add:
1203 		case XK_KP_Decimal:
1204 			k &= 0x7F;
1205 			break;
1206 		case XK_Linefeed:
1207 			k = '\r';
1208 			break;
1209 		case XK_KP_Space:
1210 			k = ' ';
1211 			break;
1212 		case XK_Home:
1213 		case XK_KP_Home:
1214 			k = Home;
1215 			break;
1216 		case XK_Left:
1217 		case XK_KP_Left:
1218 			k = Left;
1219 			break;
1220 		case XK_Up:
1221 		case XK_KP_Up:
1222 			k = Up;
1223 			break;
1224 		case XK_Down:
1225 		case XK_KP_Down:
1226 			k = Down;
1227 			break;
1228 		case XK_Right:
1229 		case XK_KP_Right:
1230 			k = Right;
1231 			break;
1232 		case XK_Page_Down:
1233 		case XK_KP_Page_Down:
1234 			k = Pgdown;
1235 			break;
1236 		case XK_End:
1237 		case XK_KP_End:
1238 			k = End;
1239 			break;
1240 		case XK_Page_Up:
1241 		case XK_KP_Page_Up:
1242 			k = Pgup;
1243 			break;
1244 		case XK_Insert:
1245 		case XK_KP_Insert:
1246 			k = Ins;
1247 			break;
1248 		case XK_KP_Enter:
1249 		case XK_Return:
1250 			k = '\n';
1251 			break;
1252 		case XK_Alt_L:
1253 		case XK_Alt_R:
1254 			k = Latin;
1255 			break;
1256 		case XK_Shift_L:
1257 		case XK_Shift_R:
1258 		case XK_Control_L:
1259 		case XK_Control_R:
1260 		case XK_Caps_Lock:
1261 		case XK_Shift_Lock:
1262 
1263 		case XK_Meta_L:
1264 		case XK_Meta_R:
1265 		case XK_Super_L:
1266 		case XK_Super_R:
1267 		case XK_Hyper_L:
1268 		case XK_Hyper_R:
1269 			return;
1270 		default:                /* not ISO-1 or tty control */
1271  			if(k>0xff){
1272 				k = keysym2ucs(k); /* supplied by X */
1273 				if(k == -1)
1274 					return;
1275 			}
1276 			break;
1277 		}
1278 	}
1279 
1280 	/* Compensate for servers that call a minus a hyphen */
1281 	if(k == XK_hyphen)
1282 		k = XK_minus;
1283 	/* Do control mapping ourselves if translator doesn't */
1284 	if(md & ControlMask)
1285 		k &= 0x9f;
1286 	if(0){
1287 		if(k == '\t' && ind)
1288 			k = BackTab;
1289 
1290 		if(md & Mod1Mask)
1291 			k = APP|(k&0xff);
1292 	}
1293 	if(k == NoSymbol)
1294 		return;
1295 
1296         gkbdputc(gkbdq, k);
1297 }
1298 
1299 static void
1300 xmouse(XEvent *e)
1301 {
1302 	int s, dbl;
1303 	XButtonEvent *be;
1304 	XMotionEvent *me;
1305 	XEvent motion;
1306 	int x, y, b;
1307 	static ulong lastb, lastt;
1308 
1309 	if(putsnarf != assertsnarf){
1310 		assertsnarf = putsnarf;
1311 		XSetSelectionOwner(xmcon, XA_PRIMARY, xdrawable, CurrentTime);
1312 		if(clipboard != None)
1313 			XSetSelectionOwner(xmcon, clipboard, xdrawable, CurrentTime);
1314 		XFlush(xmcon);
1315 	}
1316 
1317 	dbl = 0;
1318 	switch(e->type){
1319 	case ButtonPress:
1320 		be = (XButtonEvent *)e;
1321 		/*
1322 		 * Fake message, just sent to make us announce snarf.
1323 		 * Apparently state and button are 16 and 8 bits on
1324 		 * the wire, since they are truncated by the time they
1325 		 * get to us.
1326 		 */
1327 		if(be->send_event
1328 		&& (~be->state&0xFFFF)==0
1329 		&& (~be->button&0xFF)==0)
1330 			return;
1331 		x = be->x;
1332 		y = be->y;
1333 		s = be->state;
1334 		if(be->button == lastb && be->time - lastt < DblTime)
1335 			dbl = 1;
1336 		lastb = be->button;
1337 		lastt = be->time;
1338 		switch(be->button){
1339 		case 1:
1340 			s |= Button1Mask;
1341 			break;
1342 		case 2:
1343 			s |= Button2Mask;
1344 			break;
1345 		case 3:
1346 			s |= Button3Mask;
1347 			break;
1348 		case 4:
1349 			s |= Button4Mask;
1350 			break;
1351 		case 5:
1352 			s |= Button5Mask;
1353 			break;
1354 		}
1355 		break;
1356 	case ButtonRelease:
1357 		be = (XButtonEvent *)e;
1358 		x = be->x;
1359 		y = be->y;
1360 		s = be->state;
1361 		switch(be->button){
1362 		case 1:
1363 			s &= ~Button1Mask;
1364 			break;
1365 		case 2:
1366 			s &= ~Button2Mask;
1367 			break;
1368 		case 3:
1369 			s &= ~Button3Mask;
1370 			break;
1371 		case 4:
1372 			s &= ~Button4Mask;
1373 			break;
1374 		case 5:
1375 			s &= ~Button5Mask;
1376 			break;
1377 		}
1378 		break;
1379 	case MotionNotify:
1380 		me = (XMotionEvent *) e;
1381 
1382 		/* remove excess MotionNotify events from queue and keep last one */
1383 		while(XCheckTypedWindowEvent(xmcon, xdrawable, MotionNotify, &motion) == True)
1384 			me = (XMotionEvent *) &motion;
1385 
1386 		s = me->state;
1387 		x = me->x;
1388 		y = me->y;
1389 		break;
1390 	default:
1391 		return;
1392 	}
1393 
1394 	b = 0;
1395 	if(s & Button1Mask)
1396 		b |= 1;
1397 	if(s & Button2Mask)
1398 		b |= 2;
1399 	if(s & Button3Mask)
1400 		b |= 4;
1401 	if(s & Button4Mask)
1402 		b |= 8;
1403 	if(s & Button5Mask)
1404 		b |= 16;
1405 	if(dbl)
1406 		b |= 1<<8;
1407 
1408 	mousetrack(b, x, y, 0);
1409 }
1410 
1411 #include "x11-keysym2ucs.c"
1412 
1413 /*
1414  * Cut and paste.  Just couldn't stand to make this simple...
1415  */
1416 
1417 enum{
1418 	SnarfSize=	100*1024
1419 };
1420 
1421 typedef struct Clip Clip;
1422 struct Clip
1423 {
1424 	char buf[SnarfSize];
1425 	QLock lk;
1426 };
1427 Clip clip;
1428 
1429 #undef long	/* sic */
1430 #undef ulong
1431 
1432 static char*
1433 _xgetsnarf(XDisplay *xd)
1434 {
1435 	uchar *data, *xdata;
1436 	Atom clipboard, type, prop;
1437 	unsigned long len, lastlen, dummy;
1438 	int fmt, i;
1439 	XWindow w;
1440 
1441 	qlock(&clip.lk);
1442 	/*
1443 	 * Have we snarfed recently and the X server hasn't caught up?
1444 	 */
1445 	if(putsnarf != assertsnarf)
1446 		goto mine;
1447 
1448 	/*
1449 	 * Is there a primary selection (highlighted text in an xterm)?
1450 	 */
1451 	clipboard = XA_PRIMARY;
1452 	w = XGetSelectionOwner(xd, XA_PRIMARY);
1453 	if(w == xdrawable){
1454 	mine:
1455 		data = (uchar*)strdup(clip.buf);
1456 		goto out;
1457 	}
1458 
1459 	/*
1460 	 * If not, is there a clipboard selection?
1461 	 */
1462 	if(w == None && clipboard != None){
1463 		clipboard = clipboard;
1464 		w = XGetSelectionOwner(xd, clipboard);
1465 		if(w == xdrawable)
1466 			goto mine;
1467 	}
1468 
1469 	/*
1470 	 * If not, give up.
1471 	 */
1472 	if(w == None){
1473 		data = nil;
1474 		goto out;
1475 	}
1476 
1477 	/*
1478 	 * We should be waiting for SelectionNotify here, but it might never
1479 	 * come, and we have no way to time out.  Instead, we will clear
1480 	 * local property #1, request our buddy to fill it in for us, and poll
1481 	 * until he's done or we get tired of waiting.
1482 	 *
1483 	 * We should try to go for utf8string instead of XA_STRING,
1484 	 * but that would add to the polling.
1485 	 */
1486 	prop = 1;
1487 	XChangeProperty(xd, xdrawable, prop, XA_STRING, 8, PropModeReplace, (uchar*)"", 0);
1488 	XConvertSelection(xd, clipboard, XA_STRING, prop, xdrawable, CurrentTime);
1489 	XFlush(xd);
1490 	lastlen = 0;
1491 	for(i=0; i<10 || (lastlen!=0 && i<30); i++){
1492 		osmillisleep(100);
1493 		XGetWindowProperty(xd, xdrawable, prop, 0, 0, 0, AnyPropertyType,
1494 			&type, &fmt, &dummy, &len, &data);
1495 		if(lastlen == len && len > 0)
1496 			break;
1497 		lastlen = len;
1498 	}
1499 	if(i == 10){
1500 		data = nil;
1501 		goto out;
1502 	}
1503 	/* get the property */
1504 	data = nil;
1505 	XGetWindowProperty(xd, xdrawable, prop, 0, SnarfSize/sizeof(unsigned long), 0,
1506 		AnyPropertyType, &type, &fmt, &len, &dummy, &xdata);
1507 	if((type != XA_STRING && type != utf8string) || len == 0){
1508 		if(xdata)
1509 			XFree(xdata);
1510 		data = nil;
1511 	}else{
1512 		if(xdata){
1513 			data = (uchar*)strdup((char*)xdata);
1514 			XFree(xdata);
1515 		}else
1516 			data = nil;
1517 	}
1518 out:
1519 	qunlock(&clip.lk);
1520 	return (char*)data;
1521 }
1522 
1523 static void
1524 _xputsnarf(XDisplay *xd, char *data)
1525 {
1526 	XButtonEvent e;
1527 
1528 	if(strlen(data) >= SnarfSize)
1529 		return;
1530 	qlock(&clip.lk);
1531 	strcpy(clip.buf, data);
1532 
1533 	/* leave note for mouse proc to assert selection ownership */
1534 	putsnarf++;
1535 
1536 	/* send mouse a fake event so snarf is announced */
1537 	memset(&e, 0, sizeof e);
1538 	e.type = ButtonPress;
1539 	e.window = xdrawable;
1540 	e.state = ~0;
1541 	e.button = ~0;
1542 	XSendEvent(xd, xdrawable, True, ButtonPressMask, (XEvent*)&e);
1543 	XFlush(xd);
1544 	qunlock(&clip.lk);
1545 }
1546 
1547 static void
1548 xselect(XEvent *e, XDisplay *xd)
1549 {
1550 	char *name;
1551 	XEvent r;
1552 	XSelectionRequestEvent *xe;
1553 	Atom a[4];
1554 
1555 	if(e->xany.type != SelectionRequest)
1556 		return;
1557 
1558 	memset(&r, 0, sizeof r);
1559 	xe = (XSelectionRequestEvent*)e;
1560 if(0) iprint("xselect target=%d requestor=%d property=%d selection=%d\n",
1561 	xe->target, xe->requestor, xe->property, xe->selection);
1562 	r.xselection.property = xe->property;
1563 	if(xe->target == targets){
1564 		a[0] = XA_STRING;
1565 		a[1] = utf8string;
1566 		a[2] = text;
1567 		a[3] = compoundtext;
1568 
1569 		XChangeProperty(xd, xe->requestor, xe->property, xe->target,
1570 			8, PropModeReplace, (uchar*)a, sizeof a);
1571 	}else if(xe->target == XA_STRING || xe->target == utf8string || xe->target == text || xe->target == compoundtext){
1572 		/* if the target is STRING we're supposed to reply with Latin1 XXX */
1573 		qlock(&clip.lk);
1574 		XChangeProperty(xd, xe->requestor, xe->property, xe->target,
1575 			8, PropModeReplace, (uchar*)clip.buf, strlen(clip.buf));
1576 		qunlock(&clip.lk);
1577 	}else{
1578 		iprint("get %d\n", xe->target);
1579 		name = XGetAtomName(xd, xe->target);
1580 		if(name == nil)
1581 			iprint("XGetAtomName failed\n");
1582 		else if(strcmp(name, "TIMESTAMP") != 0)
1583 			iprint("%s: cannot handle selection request for '%s' (%d)\n", argv0, name, (int)xe->target);
1584 		r.xselection.property = None;
1585 	}
1586 
1587 	r.xselection.display = xe->display;
1588 	/* r.xselection.property filled above */
1589 	r.xselection.target = xe->target;
1590 	r.xselection.type = SelectionNotify;
1591 	r.xselection.requestor = xe->requestor;
1592 	r.xselection.time = xe->time;
1593 	r.xselection.send_event = True;
1594 	r.xselection.selection = xe->selection;
1595 	XSendEvent(xd, xe->requestor, False, 0, &r);
1596 	XFlush(xd);
1597 }
1598 
1599 char*
1600 clipread(void)
1601 {
1602 	char *p;
1603 
1604 	if(xsnarfcon == nil)
1605 		return nil;
1606 	XLockDisplay(xsnarfcon);
1607 	p = _xgetsnarf(xsnarfcon);
1608 	XUnlockDisplay(xsnarfcon);
1609 	return p;
1610 }
1611 
1612 int
1613 clipwrite(char *buf)
1614 {
1615 	if(xsnarfcon == nil)
1616 		return 0;
1617 	XLockDisplay(xsnarfcon);
1618 	_xputsnarf(xsnarfcon, buf);
1619 	XUnlockDisplay(xsnarfcon);
1620 	return 0;
1621 }
1622