| /* | 
 |  * Window system protocol server. | 
 |  * Use select and a single proc and single stack | 
 |  * to avoid aggravating the X11 library, which is | 
 |  * subtle and quick to anger. | 
 |  */ | 
 |  | 
 | // #define SHOWEVENT | 
 |  | 
 | #include <u.h> | 
 | #include <sys/select.h> | 
 | #include <errno.h> | 
 | #ifdef SHOWEVENT | 
 | #include <stdio.h> | 
 | #endif | 
 | #include "x11-inc.h" | 
 |  | 
 | #include <libc.h> | 
 | #include <draw.h> | 
 | #include <memdraw.h> | 
 | #include <memlayer.h> | 
 | #include <keyboard.h> | 
 | #include <mouse.h> | 
 | #include <cursor.h> | 
 | #include <drawfcall.h> | 
 | #include "x11-memdraw.h" | 
 | #include "devdraw.h" | 
 |  | 
 | #undef time | 
 |  | 
 | #define MouseMask (\ | 
 | 	ButtonPressMask|\ | 
 | 	ButtonReleaseMask|\ | 
 | 	PointerMotionMask|\ | 
 | 	Button1MotionMask|\ | 
 | 	Button2MotionMask|\ | 
 | 	Button3MotionMask) | 
 |  | 
 | #define Mask MouseMask|ExposureMask|StructureNotifyMask|KeyPressMask|KeyReleaseMask|EnterWindowMask|LeaveWindowMask|FocusChangeMask | 
 |  | 
 | typedef struct Kbdbuf Kbdbuf; | 
 | typedef struct Mousebuf Mousebuf; | 
 | typedef struct Fdbuf Fdbuf; | 
 | typedef struct Tagbuf Tagbuf; | 
 |  | 
 | struct Kbdbuf | 
 | { | 
 | 	Rune r[32]; | 
 | 	int ri; | 
 | 	int wi; | 
 | 	int stall; | 
 | }; | 
 |  | 
 | struct Mousebuf | 
 | { | 
 | 	Mouse m[32]; | 
 | 	int ri; | 
 | 	int wi; | 
 | 	int stall; | 
 | 	int resized; | 
 | }; | 
 |  | 
 | struct Tagbuf | 
 | { | 
 | 	int t[32]; | 
 | 	int ri; | 
 | 	int wi; | 
 | }; | 
 |  | 
 | struct Fdbuf | 
 | { | 
 | 	uchar buf[2*MAXWMSG]; | 
 | 	uchar *rp; | 
 | 	uchar *wp; | 
 | 	uchar *ep; | 
 | }; | 
 |  | 
 | Kbdbuf kbd; | 
 | Mousebuf mouse; | 
 | Fdbuf fdin; | 
 | Fdbuf fdout; | 
 | Tagbuf kbdtags; | 
 | Tagbuf mousetags; | 
 |  | 
 | void fdslide(Fdbuf*); | 
 | void runmsg(Wsysmsg*); | 
 | void replymsg(Wsysmsg*); | 
 | void runxevent(XEvent*); | 
 | void matchkbd(void); | 
 | void matchmouse(void); | 
 | int fdnoblock(int); | 
 |  | 
 | int chatty; | 
 | int drawsleep; | 
 | int fullscreen; | 
 |  | 
 | Rectangle windowrect; | 
 | Rectangle screenrect; | 
 |  | 
 | void | 
 | usage(void) | 
 | { | 
 | 	fprint(2, "usage: devdraw (don't run directly)\n"); | 
 | 	exits("usage"); | 
 | } | 
 |  | 
 | void | 
 | bell(void *v, char *msg) | 
 | { | 
 | 	if(strcmp(msg, "alarm") == 0) | 
 | 		drawsleep = drawsleep ? 0 : 1000; | 
 | 	noted(NCONT); | 
 | } | 
 |  | 
 | void | 
 | main(int argc, char **argv) | 
 | { | 
 | 	int n, top, firstx; | 
 | 	fd_set rd, wr, xx; | 
 | 	Wsysmsg m; | 
 | 	XEvent event; | 
 |  | 
 | 	/* | 
 | 	 * Move the protocol off stdin/stdout so that | 
 | 	 * any inadvertent prints don't screw things up. | 
 | 	 */ | 
 | 	dup(0, 3); | 
 | 	dup(1, 4); | 
 | 	close(0); | 
 | 	close(1); | 
 | 	open("/dev/null", OREAD); | 
 | 	open("/dev/null", OWRITE); | 
 |  | 
 | 	/* reopens stdout if debugging */ | 
 | 	runxevent(0); | 
 |  | 
 | 	fmtinstall('W', drawfcallfmt); | 
 |  | 
 | 	ARGBEGIN{ | 
 | 	case 'D': | 
 | 		chatty++; | 
 | 		break; | 
 | 	default: | 
 | 		usage(); | 
 | 	}ARGEND | 
 |  | 
 | 	/* | 
 | 	 * Ignore arguments.  They're only for good ps -a listings. | 
 | 	 */ | 
 | 	 | 
 | 	notify(bell); | 
 |  | 
 | 	fdin.rp = fdin.wp = fdin.buf; | 
 | 	fdin.ep = fdin.buf+sizeof fdin.buf; | 
 | 	 | 
 | 	fdout.rp = fdout.wp = fdout.buf; | 
 | 	fdout.ep = fdout.buf+sizeof fdout.buf; | 
 |  | 
 | 	fdnoblock(3); | 
 | 	fdnoblock(4); | 
 |  | 
 | 	firstx = 1; | 
 | 	_x.fd = -1; | 
 | 	for(;;){ | 
 | 		/* set up file descriptors */ | 
 | 		FD_ZERO(&rd); | 
 | 		FD_ZERO(&wr); | 
 | 		FD_ZERO(&xx); | 
 | 		/* | 
 | 		 * Don't read unless there's room *and* we haven't | 
 | 		 * already filled the output buffer too much. | 
 | 		 */ | 
 | 		if(fdout.wp < fdout.buf+MAXWMSG && fdin.wp < fdin.ep) | 
 | 			FD_SET(3, &rd); | 
 | 		if(fdout.wp > fdout.rp) | 
 | 			FD_SET(4, &wr); | 
 | 		FD_SET(3, &xx); | 
 | 		FD_SET(4, &xx); | 
 | 		top = 4; | 
 | 		if(_x.fd >= 0){ | 
 | 			if(firstx){ | 
 | 				firstx = 0; | 
 | 				XSelectInput(_x.display, _x.drawable, Mask); | 
 | 			} | 
 | 			FD_SET(_x.fd, &rd); | 
 | 			FD_SET(_x.fd, &xx); | 
 | 			XFlush(_x.display); | 
 | 			if(_x.fd > top) | 
 | 				top = _x.fd; | 
 | 		} | 
 |  | 
 | 		if(chatty) | 
 | 			fprint(2, "select %d...\n", top+1); | 
 | 		/* wait for something to happen */ | 
 | 	    again: | 
 | 		if(select(top+1, &rd, &wr, &xx, NULL) < 0){ | 
 | 			if(errno == EINTR) | 
 | 				goto again; | 
 | 			if(chatty) | 
 | 				fprint(2, "select failure\n"); | 
 | 			exits(0); | 
 | 		} | 
 | 		if(chatty) | 
 | 			fprint(2, "got select...\n"); | 
 |  | 
 | 		{ | 
 | 			/* read what we can */ | 
 | 			n = 1; | 
 | 			while(fdin.wp < fdin.ep && (n = read(3, fdin.wp, fdin.ep-fdin.wp)) > 0) | 
 | 				fdin.wp += n; | 
 | 			if(n == 0){ | 
 | 				if(chatty) | 
 | 					fprint(2, "eof\n"); | 
 | 				exits(0); | 
 | 			} | 
 | 			if(n < 0 && errno != EAGAIN) | 
 | 				sysfatal("reading wsys msg: %r"); | 
 |  | 
 | 			/* pick off messages one by one */ | 
 | 			while((n = convM2W(fdin.rp, fdin.wp-fdin.rp, &m)) > 0){ | 
 | 				/* fprint(2, "<- %W\n", &m); */ | 
 | 				runmsg(&m); | 
 | 				fdin.rp += n; | 
 | 			} | 
 | 			 | 
 | 			/* slide data to beginning of buf */ | 
 | 			fdslide(&fdin); | 
 | 		} | 
 | 		{ | 
 | 			/* write what we can */ | 
 | 			n = 1; | 
 | 			while(fdout.rp < fdout.wp && (n = write(4, fdout.rp, fdout.wp-fdout.rp)) > 0) | 
 | 				fdout.rp += n; | 
 | 			if(n == 0) | 
 | 				sysfatal("short write writing wsys"); | 
 | 			if(n < 0 && errno != EAGAIN) | 
 | 				sysfatal("writing wsys msg: %r"); | 
 |  | 
 | 			/* slide data to beginning of buf */ | 
 | 			fdslide(&fdout); | 
 | 		} | 
 | 		{ | 
 | 			/* | 
 | 			 * Read an X message if we can. | 
 | 			 * (XPending actually calls select to make sure | 
 | 			 * the display's fd is readable and then reads | 
 | 			 * in any waiting data before declaring whether | 
 | 			 * there are events on the queue.) | 
 | 			 */ | 
 | 			while(XPending(_x.display)){ | 
 | 				XNextEvent(_x.display, &event); | 
 | 				runxevent(&event); | 
 | 			} | 
 | 		} | 
 | 	} | 
 | } | 
 |  | 
 | int | 
 | fdnoblock(int fd) | 
 | { | 
 | 	return fcntl(fd, F_SETFL, fcntl(fd, F_GETFL)|O_NONBLOCK); | 
 | } | 
 |  | 
 | void | 
 | fdslide(Fdbuf *fb) | 
 | { | 
 | 	int n; | 
 |  | 
 | 	n = fb->wp - fb->rp; | 
 | 	if(n > 0) | 
 | 		memmove(fb->buf, fb->rp, n); | 
 | 	fb->rp = fb->buf; | 
 | 	fb->wp = fb->rp+n; | 
 | } | 
 |  | 
 | void | 
 | replyerror(Wsysmsg *m) | 
 | { | 
 | 	char err[256]; | 
 | 	 | 
 | 	rerrstr(err, sizeof err); | 
 | 	m->type = Rerror; | 
 | 	m->error = err; | 
 | 	replymsg(m); | 
 | } | 
 |  | 
 |  | 
 |  | 
 | /*  | 
 |  * Handle a single wsysmsg.  | 
 |  * Might queue for later (kbd, mouse read) | 
 |  */ | 
 | void | 
 | runmsg(Wsysmsg *m) | 
 | { | 
 | 	uchar buf[65536]; | 
 | 	int n; | 
 | 	Memimage *i; | 
 | 	 | 
 | 	switch(m->type){ | 
 | 	case Tinit: | 
 | 		memimageinit(); | 
 | 		i = _xattach(m->label, m->winsize); | 
 | 		_initdisplaymemimage(i); | 
 | 		replymsg(m); | 
 | 		break; | 
 |  | 
 | 	case Trdmouse: | 
 | 		mousetags.t[mousetags.wi++] = m->tag; | 
 | 		if(mousetags.wi == nelem(mousetags.t)) | 
 | 			mousetags.wi = 0; | 
 | 		if(mousetags.wi == mousetags.ri) | 
 | 			sysfatal("too many queued mouse reads"); | 
 | 		/* fprint(2, "mouse unstall\n"); */ | 
 | 		mouse.stall = 0; | 
 | 		matchmouse(); | 
 | 		break; | 
 |  | 
 | 	case Trdkbd: | 
 | 		kbdtags.t[kbdtags.wi++] = m->tag; | 
 | 		if(kbdtags.wi == nelem(kbdtags.t)) | 
 | 			kbdtags.wi = 0; | 
 | 		if(kbdtags.wi == kbdtags.ri) | 
 | 			sysfatal("too many queued keyboard reads"); | 
 | 		kbd.stall = 0; | 
 | 		matchkbd(); | 
 | 		break; | 
 |  | 
 | 	case Tmoveto: | 
 | 		_xmoveto(m->mouse.xy); | 
 | 		replymsg(m); | 
 | 		break; | 
 |  | 
 | 	case Tcursor: | 
 | 		if(m->arrowcursor) | 
 | 			_xsetcursor(nil); | 
 | 		else | 
 | 			_xsetcursor(&m->cursor); | 
 | 		replymsg(m); | 
 | 		break; | 
 | 			 | 
 | 	case Tbouncemouse: | 
 | 		_xbouncemouse(&m->mouse); | 
 | 		replymsg(m); | 
 | 		break; | 
 |  | 
 | 	case Tlabel: | 
 | 		_xsetlabel(m->label); | 
 | 		replymsg(m); | 
 | 		break; | 
 |  | 
 | 	case Trdsnarf: | 
 | 		m->snarf = _xgetsnarf(); | 
 | 		replymsg(m); | 
 | 		free(m->snarf); | 
 | 		break; | 
 |  | 
 | 	case Twrsnarf: | 
 | 		_xputsnarf(m->snarf); | 
 | 		replymsg(m); | 
 | 		break; | 
 |  | 
 | 	case Trddraw: | 
 | 		n = m->count; | 
 | 		if(n > sizeof buf) | 
 | 			n = sizeof buf; | 
 | 		n = _drawmsgread(buf, n); | 
 | 		if(n < 0) | 
 | 			replyerror(m); | 
 | 		else{ | 
 | 			m->count = n; | 
 | 			m->data = buf; | 
 | 			replymsg(m); | 
 | 		} | 
 | 		break; | 
 |  | 
 | 	case Twrdraw: | 
 | 		if(_drawmsgwrite(m->data, m->count) < 0) | 
 | 			replyerror(m); | 
 | 		else | 
 | 			replymsg(m); | 
 | 		break; | 
 | 	 | 
 | 	case Ttop: | 
 | 		_xtopwindow(); | 
 | 		replymsg(m); | 
 | 		break; | 
 | 	 | 
 | 	case Tresize: | 
 | 		_xresizewindow(m->rect); | 
 | 		replymsg(m); | 
 | 		break; | 
 | 	} | 
 | } | 
 |  | 
 | /* | 
 |  * Reply to m. | 
 |  */ | 
 | void | 
 | replymsg(Wsysmsg *m) | 
 | { | 
 | 	int n; | 
 |  | 
 | 	/* T -> R msg */ | 
 | 	if(m->type%2 == 0) | 
 | 		m->type++; | 
 | 		 | 
 | 	/* fprint(2, "-> %W\n", m); */ | 
 | 	/* copy to output buffer */ | 
 | 	n = sizeW2M(m); | 
 | 	if(fdout.wp+n > fdout.ep) | 
 | 		sysfatal("out of space for reply message"); | 
 | 	convW2M(m, fdout.wp, n); | 
 | 	fdout.wp += n; | 
 | } | 
 |  | 
 | /* | 
 |  * Match queued kbd reads with queued kbd characters. | 
 |  */ | 
 | void | 
 | matchkbd(void) | 
 | { | 
 | 	Wsysmsg m; | 
 | 	 | 
 | 	if(kbd.stall) | 
 | 		return; | 
 | 	while(kbd.ri != kbd.wi && kbdtags.ri != kbdtags.wi){ | 
 | 		m.type = Rrdkbd; | 
 | 		m.tag = kbdtags.t[kbdtags.ri++]; | 
 | 		if(kbdtags.ri == nelem(kbdtags.t)) | 
 | 			kbdtags.ri = 0; | 
 | 		m.rune = kbd.r[kbd.ri++]; | 
 | 		if(kbd.ri == nelem(kbd.r)) | 
 | 			kbd.ri = 0; | 
 | 		replymsg(&m); | 
 | 	} | 
 | } | 
 |  | 
 | /* | 
 |  * Match queued mouse reads with queued mouse events. | 
 |  */ | 
 | void | 
 | matchmouse(void) | 
 | { | 
 | 	Wsysmsg m; | 
 | 	 | 
 | 	while(mouse.ri != mouse.wi && mousetags.ri != mousetags.wi){ | 
 | 		m.type = Rrdmouse; | 
 | 		m.tag = mousetags.t[mousetags.ri++]; | 
 | 		if(mousetags.ri == nelem(mousetags.t)) | 
 | 			mousetags.ri = 0; | 
 | 		m.mouse = mouse.m[mouse.ri]; | 
 | 		m.resized = mouse.resized; | 
 | 		/* | 
 | 		if(m.resized) | 
 | 			fprint(2, "sending resize\n"); | 
 | 		*/ | 
 | 		mouse.resized = 0; | 
 | 		mouse.ri++; | 
 | 		if(mouse.ri == nelem(mouse.m)) | 
 | 			mouse.ri = 0; | 
 | 		replymsg(&m); | 
 | 	} | 
 | } | 
 |  | 
 | static int kbuttons; | 
 | static int altdown; | 
 | static int kstate; | 
 |  | 
 | static void | 
 | sendmouse(Mouse m) | 
 | { | 
 | 	m.buttons |= kbuttons; | 
 | 	mouse.m[mouse.wi] = m; | 
 | 	mouse.wi++; | 
 | 	if(mouse.wi == nelem(mouse.m)) | 
 | 		mouse.wi = 0; | 
 | 	if(mouse.wi == mouse.ri){ | 
 | 		mouse.stall = 1; | 
 | 		mouse.ri = 0; | 
 | 		mouse.wi = 1; | 
 | 		mouse.m[0] = m; | 
 | 		/* fprint(2, "mouse stall\n"); */ | 
 | 	} | 
 | 	matchmouse(); | 
 | } | 
 |  | 
 | /* | 
 |  * Handle an incoming X event. | 
 |  */ | 
 | void | 
 | runxevent(XEvent *xev) | 
 | { | 
 | 	int c; | 
 | 	KeySym k; | 
 | 	static Mouse m; | 
 | 	XButtonEvent *be; | 
 | 	XKeyEvent *ke; | 
 |  | 
 | #ifdef SHOWEVENT | 
 | 	static int first = 1; | 
 | 	if(first){ | 
 | 		dup(create("/tmp/devdraw.out", OWRITE, 0666), 1); | 
 | 		setbuf(stdout, 0); | 
 | 		first = 0; | 
 | 	} | 
 | #endif | 
 |  | 
 | 	if(xev == 0) | 
 | 		return; | 
 |  | 
 | #ifdef SHOWEVENT | 
 | 	print("\n"); | 
 | 	ShowEvent(xev); | 
 | #endif | 
 |  | 
 | 	switch(xev->type){ | 
 | 	case Expose: | 
 | 		_xexpose(xev); | 
 | 		break; | 
 | 	 | 
 | 	case DestroyNotify: | 
 | 		if(_xdestroy(xev)) | 
 | 			exits(0); | 
 | 		break; | 
 |  | 
 | 	case ConfigureNotify: | 
 | 		if(_xconfigure(xev)){ | 
 | 			mouse.resized = 1; | 
 | 			_xreplacescreenimage(); | 
 | 			sendmouse(m); | 
 | 		} | 
 | 		break; | 
 |  | 
 | 	case ButtonPress: | 
 | 		be = (XButtonEvent*)xev; | 
 | 		if(be->button == 1) { | 
 | 			if(kstate & ControlMask) | 
 | 				be->button = 2; | 
 | 			else if(kstate & Mod1Mask) | 
 | 				be->button = 3; | 
 | 		} | 
 | 		// fall through | 
 | 	case ButtonRelease: | 
 | 		altdown = 0; | 
 | 		// fall through | 
 | 	case MotionNotify: | 
 | 		if(mouse.stall) | 
 | 			return; | 
 | 		if(_xtoplan9mouse(xev, &m) < 0) | 
 | 			return; | 
 | 		sendmouse(m); | 
 | 		break; | 
 | 	 | 
 | 	case KeyRelease: | 
 | 	case KeyPress: | 
 | 		ke = (XKeyEvent*)xev; | 
 | 		XLookupString(ke, NULL, 0, &k, NULL); | 
 | 		c = ke->state; | 
 | 		switch(k) { | 
 | 		case XK_Alt_L: | 
 | 		case XK_Meta_L:	/* Shift Alt on PCs */ | 
 | 		case XK_Alt_R: | 
 | 		case XK_Meta_R:	/* Shift Alt on PCs */ | 
 | 		case XK_Multi_key: | 
 | 			if(xev->type == KeyPress) | 
 | 				altdown = 1; | 
 | 			else if(altdown) { | 
 | 				altdown = 0; | 
 | 				sendalt(); | 
 | 			} | 
 | 			break; | 
 | 		} | 
 |  | 
 | 		switch(k) { | 
 | 		case XK_Control_L: | 
 | 			if(xev->type == KeyPress) | 
 | 				c |= ControlMask; | 
 | 			else | 
 | 				c &= ~ControlMask; | 
 | 			goto kbutton; | 
 | 		case XK_Alt_L: | 
 | 		case XK_Shift_L: | 
 | 			if(xev->type == KeyPress) | 
 | 				c |= Mod1Mask; | 
 | 			else | 
 | 				c &= ~Mod1Mask; | 
 | 		kbutton: | 
 | 			kstate = c; | 
 | 			if(m.buttons || kbuttons) { | 
 | 				altdown = 0; // used alt | 
 | 				kbuttons = 0; | 
 | 				if(c & ControlMask) | 
 | 					kbuttons |= 2; | 
 | 				if(c & Mod1Mask) | 
 | 					kbuttons |= 4; | 
 | 				sendmouse(m); | 
 | 				break; | 
 | 			} | 
 | 		} | 
 |  | 
 | 		if(xev->type != KeyPress) | 
 | 			break; | 
 | 		if(k == XK_F11){ | 
 | 			fullscreen = !fullscreen; | 
 | 			_xmovewindow(fullscreen ? screenrect : windowrect); | 
 | 			return; | 
 | 		} | 
 | 		if(kbd.stall) | 
 | 			return; | 
 | 		if((c = _xtoplan9kbd(xev)) < 0) | 
 | 			return; | 
 | 		kbd.r[kbd.wi++] = c; | 
 | 		if(kbd.wi == nelem(kbd.r)) | 
 | 			kbd.wi = 0; | 
 | 		if(kbd.ri == kbd.wi) | 
 | 			kbd.stall = 1; | 
 | 		matchkbd(); | 
 | 		break; | 
 | 	 | 
 | 	case FocusOut: | 
 | 		/* | 
 | 		 * Some key combinations (e.g. Alt-Tab) can cause us | 
 | 		 * to see the key down event without the key up event, | 
 | 		 * so clear out the keyboard state when we lose the focus. | 
 | 		 */ | 
 | 		kstate = 0; | 
 | 		altdown = 0; | 
 | 		abortcompose(); | 
 | 		break; | 
 | 	 | 
 | 	case SelectionRequest: | 
 | 		_xselect(xev); | 
 | 		break; | 
 | 	} | 
 | } | 
 |  |