Commit Diff


commit - b1a086dee9bf5846b31323ba2c438f8853a9c87f
commit + 41547af3f614061dd2c94bb52ae118f146925743
blob - 77a3f44a2d401940ea3f6bef12c41284acffa6a0
blob + e3040779d1370e6550e1ffb98186f3da8b14023a
--- src/cmd/devdraw/devdraw.c
+++ src/cmd/devdraw/devdraw.c
@@ -14,14 +14,12 @@
 #include <drawfcall.h>
 #include "devdraw.h"
 
-static	Draw		sdraw;
-Client		*client0;
 static	int		drawuninstall(Client*, int);
 static	Memimage*	drawinstall(Client*, int, Memimage*, DScreen*);
 static	void		drawfreedimage(Client*, DImage*);
 
 void
-_initdisplaymemimage(Client *c, Memimage *m)
+draw_initdisplaymemimage(Client *c, Memimage *m)
 {
 	c->screenimage = m;
 	m->screenref = 1;
@@ -30,10 +28,10 @@ _initdisplaymemimage(Client *c, Memimage *m)
 	c->op = SoverD;
 }
 
-// _drawreplacescreen replaces c's screen image with m.
+// gfx_replacescreenimage replaces c's screen image with m.
 // It is called by the host driver on the main host thread.
 void
-_drawreplacescreenimage(Client *c, Memimage *m)
+gfx_replacescreenimage(Client *c, Memimage *m)
 {
 	/*
 	 * Replace the screen image because the screen
@@ -49,21 +47,21 @@ _drawreplacescreenimage(Client *c, Memimage *m)
 	 */
 	Memimage *om;
 
-	qlock(&c->inputlk);
-	qlock(&sdraw.lk);
+	qlock(&c->drawlk);
 	om = c->screenimage;
 	c->screenimage = m;
 	m->screenref = 1;
-	c->mouse.resized = 1;
 	if(om && --om->screenref == 0){
 		_freememimage(om);
 	}
-	qunlock(&sdraw.lk);
-	qunlock(&c->inputlk);
+	qunlock(&c->drawlk);
+
+	qlock(&c->eventlk);
+	c->mouse.resized = 1;
+	qunlock(&c->eventlk);
 }
 
-static
-void
+static void
 drawrefreshscreen(DImage *l, Client *client)
 {
 	while(l != nil && l->dscreen == nil)
@@ -72,8 +70,7 @@ drawrefreshscreen(DImage *l, Client *client)
 		l->dscreen->owner->refreshme = 1;
 }
 
-static
-void
+static void
 drawrefresh(Memimage *m, Rectangle r, void *v)
 {
 	Refx *x;
@@ -106,7 +103,7 @@ static void
 addflush(Client *c, Rectangle r)
 {
 	int abb, ar, anbb;
-	Rectangle nbb;
+	Rectangle nbb, fr;
 
 	if(/*sdraw.softscreen==0 ||*/ !rectclip(&r, c->screenimage->r))
 		return;
@@ -140,14 +137,20 @@ addflush(Client *c, Rectangle r)
 		return;
 	}
 	/* emit current state */
-	if(c->flushrect.min.x < c->flushrect.max.x)
-		rpc_flushmemscreen(c, c->flushrect);
+	fr = c->flushrect;
 	c->flushrect = r;
 	c->waste = 0;
+	if(fr.min.x < fr.max.x) {
+		// Unlock drawlk because rpc_flush may want to run on gfx thread,
+		// and gfx thread might be blocked on drawlk trying to install a new screen
+		// during a resize.
+		qunlock(&c->drawlk);
+		rpc_flush(c, fr);
+		qlock(&c->drawlk);
+	}
 }
 
-static
-void
+static void
 dstflush(Client *c, int dstid, Memimage *dst, Rectangle r)
 {
 	Memlayer *l;
@@ -173,17 +176,24 @@ dstflush(Client *c, int dstid, Memimage *dst, Rectangl
 	addflush(c, r);
 }
 
-static
-void
+static void
 drawflush(Client *c)
 {
-	if(c->flushrect.min.x < c->flushrect.max.x)
-		rpc_flushmemscreen(c, c->flushrect);
+	Rectangle r;
+
+	r = c->flushrect;
 	c->flushrect = Rect(10000, 10000, -10000, -10000);
+	if(r.min.x < r.max.x) {
+		// Unlock drawlk because rpc_flush may want to run on gfx thread,
+		// and gfx thread might be blocked on drawlk trying to install a new screen
+		// during a resize.
+		qunlock(&c->drawlk);
+		rpc_flush(c, r);
+		qlock(&c->drawlk);
+	}
 }
 
-static
-int
+static int
 drawcmp(char *a, char *b, int n)
 {
 	if(strlen(a) != n)
@@ -191,8 +201,7 @@ drawcmp(char *a, char *b, int n)
 	return memcmp(a, b, n);
 }
 
-static
-DName*
+static DName*
 drawlookupname(Client *client, int n, char *str)
 {
 	DName *name, *ename;
@@ -205,8 +214,7 @@ drawlookupname(Client *client, int n, char *str)
 	return 0;
 }
 
-static
-int
+static int
 drawgoodname(Client *client, DImage *d)
 {
 	DName *n;
@@ -224,8 +232,7 @@ drawgoodname(Client *client, DImage *d)
 	return 1;
 }
 
-static
-DImage*
+static DImage*
 drawlookup(Client *client, int id, int checkname)
 {
 	DImage *d;
@@ -246,8 +253,7 @@ drawlookup(Client *client, int id, int checkname)
 	return 0;
 }
 
-static
-DScreen*
+static DScreen*
 drawlookupdscreen(Client *c, int id)
 {
 	DScreen *s;
@@ -261,8 +267,7 @@ drawlookupdscreen(Client *c, int id)
 	return 0;
 }
 
-static
-DScreen*
+static DScreen*
 drawlookupscreen(Client *client, int id, CScreen **cs)
 {
 	CScreen *s;
@@ -279,8 +284,7 @@ drawlookupscreen(Client *client, int id, CScreen **cs)
 	return 0;
 }
 
-static
-Memimage*
+static Memimage*
 drawinstall(Client *client, int id, Memimage *i, DScreen *dscreen)
 {
 	DImage *d;
@@ -304,8 +308,7 @@ drawinstall(Client *client, int id, Memimage *i, DScre
 	return i;
 }
 
-static
-Memscreen*
+static Memscreen*
 drawinstallscreen(Client *client, DScreen *d, int id, DImage *dimage, DImage *dfill, int public)
 {
 	Memscreen *s;
@@ -358,8 +361,7 @@ drawinstallscreen(Client *client, DScreen *d, int id, 
 	return d->screen;
 }
 
-static
-void
+static void
 drawdelname(Client *client, DName *name)
 {
 	int i;
@@ -369,8 +371,7 @@ drawdelname(Client *client, DName *name)
 	client->nname--;
 }
 
-static
-void
+static void
 drawfreedscreen(Client *client, DScreen *this)
 {
 	DScreen *ds, *next;
@@ -406,8 +407,7 @@ drawfreedscreen(Client *client, DScreen *this)
 	free(this);
 }
 
-static
-void
+static void
 drawfreedimage(Client *client, DImage *dimage)
 {
 	int i;
@@ -456,8 +456,7 @@ drawfreedimage(Client *client, DImage *dimage)
 	free(dimage);
 }
 
-static
-void
+static void
 drawuninstallscreen(Client *client, CScreen *this)
 {
 	CScreen *cs, *next;
@@ -480,8 +479,7 @@ drawuninstallscreen(Client *client, CScreen *this)
 	}
 }
 
-static
-int
+static int
 drawuninstall(Client *client, int id)
 {
 	DImage *d, **l;
@@ -496,8 +494,7 @@ drawuninstall(Client *client, int id)
 	return -1;
 }
 
-static
-int
+static int
 drawaddname(Client *client, DImage *di, int n, char *str, char **err)
 {
 	DName *name, *ename, *new, *t;
@@ -541,8 +538,7 @@ drawclientop(Client *cl)
 	return op;
 }
 
-static
-Memimage*
+static Memimage*
 drawimage(Client *client, uchar *a)
 {
 	DImage *d;
@@ -553,8 +549,7 @@ drawimage(Client *client, uchar *a)
 	return d->image;
 }
 
-static
-void
+static void
 drawrectangle(Rectangle *r, uchar *a)
 {
 	r->min.x = BGLONG(a+0*4);
@@ -563,16 +558,14 @@ drawrectangle(Rectangle *r, uchar *a)
 	r->max.y = BGLONG(a+3*4);
 }
 
-static
-void
+static void
 drawpoint(Point *p, uchar *a)
 {
 	p->x = BGLONG(a+0*4);
 	p->y = BGLONG(a+1*4);
 }
 
-static
-Point
+static Point
 drawchar(Memimage *dst, Point p, Memimage *src, Point *sp, DImage *font, int index, int op)
 {
 	FChar *fc;
@@ -592,8 +585,7 @@ drawchar(Memimage *dst, Point p, Memimage *src, Point 
 	return p;
 }
 
-static
-uchar*
+static uchar*
 drawcoord(uchar *p, uchar *maxp, int oldx, int *newx)
 {
 	int b, x;
@@ -619,9 +611,9 @@ drawcoord(uchar *p, uchar *maxp, int oldx, int *newx)
 }
 
 int
-_drawmsgread(Client *cl, void *a, int n)
+draw_dataread(Client *cl, void *a, int n)
 {
-	qlock(&sdraw.lk);
+	qlock(&cl->drawlk);
 	if(cl->readdata == nil){
 		werrstr("no draw data");
 		goto err;
@@ -634,16 +626,16 @@ _drawmsgread(Client *cl, void *a, int n)
 	memmove(a, cl->readdata, cl->nreaddata);
 	free(cl->readdata);
 	cl->readdata = nil;
-	qunlock(&sdraw.lk);
+	qunlock(&cl->drawlk);
 	return n;
 
 err:
-	qunlock(&sdraw.lk);
+	qunlock(&cl->drawlk);
 	return -1;
 }
 
 int
-_drawmsgwrite(Client *client, void *v, int n)
+draw_datawrite(Client *client, void *v, int n)
 {
 	char cbuf[40], *err, ibuf[12*12+1], *s;
 	int c, ci, doflush, dstid, e0, e1, esize, j, m;
@@ -663,7 +655,7 @@ _drawmsgwrite(Client *client, void *v, int n)
 	Refreshfn reffn;
 	Refx *refx;
 
-	qlock(&sdraw.lk);
+	qlock(&client->drawlk);
 	a = v;
 	m = 0;
 	oldn = n;
@@ -1436,7 +1428,7 @@ _drawmsgwrite(Client *client, void *v, int n)
 			continue;
 		}
 	}
-	qunlock(&sdraw.lk);
+	qunlock(&client->drawlk);
 	return oldn - n;
 
 Enodrawimage:
@@ -1506,6 +1498,6 @@ Ebadarg:
 
 error:
 	werrstr("%s", err);
-	qunlock(&sdraw.lk);
+	qunlock(&client->drawlk);
 	return -1;
 }
blob - 30586228a15294d002c2b0473ce539ce7c7e8b53
blob + dd7fc8ba1998ca5097d3215a67663977b0dc486b
--- src/cmd/devdraw/devdraw.h
+++ src/cmd/devdraw/devdraw.h
@@ -7,7 +7,6 @@ typedef struct Mousebuf Mousebuf;
 typedef struct Tagbuf Tagbuf;
 
 typedef struct Client Client;
-typedef struct Draw Draw;
 typedef struct DImage DImage;
 typedef struct DScreen DScreen;
 typedef struct CScreen CScreen;
@@ -16,11 +15,6 @@ typedef struct Refresh Refresh;
 typedef struct Refx Refx;
 typedef struct DName DName;
 
-struct Draw
-{
-	QLock		lk;
-};
-
 struct Kbdbuf
 {
 	Rune r[256];
@@ -51,6 +45,19 @@ struct Tagbuf
 
 struct Client
 {
+	int		rfd;
+
+	// wfdlk protects writes to wfd, which can be issued from either
+	// the RPC thread or the graphics thread.
+	QLock	wfdlk;
+	int		wfd;
+	uchar*	mbuf;
+	int		nmbuf;
+
+	// drawlk protects the draw data structures.
+	// It can be acquired by an RPC thread or a graphics thread
+	// but must not be held on one thread while waiting for the other.
+	QLock	drawlk;
 	/*Ref		r;*/
 	DImage*		dimage[NHASH];
 	CScreen*	cscreen;
@@ -64,7 +71,6 @@ struct Client
 	int		refreshme;
 	int		infoid;
 	int		op;
-
 	int		displaydpi;
 	int		forcedpi;
 	int		waste;
@@ -75,11 +81,11 @@ struct Client
 	DName*		name;
 	int		namevers;
 
-	int		rfd;
-	int		wfd;
+	// Only accessed/modified by the graphics thread.
 	const void*		view;
 	
-	QLock inputlk;
+	// eventlk protects the keyboard and mouse events.
+	QLock eventlk;
 	Kbdbuf kbd;
 	Mousebuf mouse;
 	Tagbuf kbdtags;
@@ -157,30 +163,59 @@ struct DScreen
 	DScreen*	next;
 };
 
-int _drawmsgread(Client*, void*, int);
-int _drawmsgwrite(Client*, void*, int);
-void _initdisplaymemimage(Client*, Memimage*);
-void	_drawreplacescreenimage(Client*, Memimage*);
+// For the most part, the graphics driver-specific code in files
+// like mac-screen.m runs in the graphics library's main thread,
+// while the RPC service code in srv.c runs on the RPC service thread.
+// The exceptions in each file, which are called by the other,
+// are marked with special prefixes: gfx_* indicates code that
+// is in srv.c but nonetheless runs on the main graphics thread,
+// while rpc_* indicates code that is in, say, mac-screen.m but
+// nonetheless runs on the RPC service thread.
+//
+// The gfx_* and rpc_* calls typically synchronize with the other
+// code in the file by acquiring a lock (or running a callback on the
+// target thread, which amounts to the same thing).
+// To avoid deadlock, callers of those routines must not hold any locks.
 
-int _latin1(Rune*, int);
-int parsewinsize(char*, Rectangle*, int*);
-int mouseswap(int);
-
+// gfx_* routines are called on the graphics thread,
+// invoked from graphics driver callbacks to do RPC work.
+// No locks are held on entry.
 void	gfx_abortcompose(Client*);
 void	gfx_keystroke(Client*, int);
+void	gfx_main(void);
 void	gfx_mousetrack(Client*, int, int, int, uint);
+void	gfx_replacescreenimage(Client*, Memimage*);
+void	gfx_started(void);
 
-void	rpc_setmouse(Client*, Point);
-void	rpc_setcursor(Client*, Cursor*, Cursor2*);
-void	rpc_setlabel(Client*, char*);
+// rpc_* routines are called on the RPC thread,
+// invoked by the RPC server code to do graphics work.
+// No locks are held on entry.
+Memimage *rpc_attach(Client*, char*, char*);
+char*	rpc_getsnarf(void);
+void	rpc_putsnarf(char*);
 void	rpc_resizeimg(Client*);
 void	rpc_resizewindow(Client*, Rectangle);
+void	rpc_serve(Client*);
+void	rpc_setcursor(Client*, Cursor*, Cursor2*);
+void	rpc_setlabel(Client*, char*);
+void	rpc_setmouse(Client*, Point);
+void	rpc_shutdown(void);
 void	rpc_topwin(Client*);
-char*	rpc_getsnarf(void);
-void	rpc_putsnarf(char*);
-Memimage *rpc_attachscreen(Client*, char*, char*);
-void	rpc_flushmemscreen(Client*, Rectangle);
+void	rpc_main(void);
 
-extern Client *client0;
+// TODO: rpc_flush is called from draw_datawrite,
+// which holds c->drawlk. Is this OK?
+void	rpc_flush(Client*, Rectangle);
 
-void	servep9p(Client*);
+// draw* routines are called on the RPC thread,
+// invoked by the RPC server to do pixel pushing.
+// c->drawlk is held on entry.
+int draw_dataread(Client*, void*, int);
+int draw_datawrite(Client*, void*, int);
+void draw_initdisplaymemimage(Client*, Memimage*);
+
+// utility routines
+int latin1(Rune*, int);
+int mouseswap(int);
+int parsewinsize(char*, Rectangle*, int*);
+
blob - 2fa9e29d26cf44f6868e112bb1a73bf2cfdaddb8
blob + a3d13a088614807c4c742261dff5aa58a7b94319
--- src/cmd/devdraw/latin1.c
+++ src/cmd/devdraw/latin1.c
@@ -46,7 +46,7 @@ unicode(Rune *k)
  * is minus the required n.
  */
 int
-_latin1(Rune *k, int n)
+latin1(Rune *k, int n)
 {
 	struct cvlist *l;
 	int c;
blob - d756d3d750069a17b029ae73a10f36022f0a196d
blob + 2ce6bb34b42338bcd54366e42757755007339ca8
--- src/cmd/devdraw/mac-screen.m
+++ src/cmd/devdraw/mac-screen.m
@@ -34,13 +34,6 @@ static void setprocname(const char*);
 static uint keycvt(uint);
 static uint msec(void);
 
-void
-usage(void)
-{
-	fprint(2, "usage: devdraw (don't run directly)\n");
-	threadexitsall("usage");
-}
-
 @class DrawView;
 @class DrawLayer;
 
@@ -49,43 +42,9 @@ usage(void)
 
 static AppDelegate *myApp = NULL;
 
-
-static QLock snarfl;
-
 void
-threadmain(int argc, char **argv)
+gfx_main(void)
 {
-	/*
-	 * 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);
-
-	ARGBEGIN{
-	case 'D':		/* for good ps -a listings */
-		break;
-	case 'f':		/* fall through for backward compatibility */
-	case 'g':
-	case 'b':
-		break;
-	default:
-		usage();
-	}ARGEND
-
-	client0 = mallocz(sizeof(Client), 1);
-	if(client0 == nil){
-		fprint(2, "initdraw: allocating client0: out of memory");
-		abort();
-	}
-	client0->displaydpi = 100;
-	client0->rfd = 3;
-	client0->wfd = 4;
-
 	setprocname(argv0);
 
 	@autoreleasepool{
@@ -97,12 +56,10 @@ threadmain(int argc, char **argv)
 	}
 }
 
+
 void
-callservep9p(void *v)
+rpc_shutdown(void)
 {
-	USED(v);
-
-	servep9p(client0);
 	[NSApp terminate:myApp];
 }
 
@@ -128,8 +85,8 @@ callservep9p(void *v)
 	i = [[NSImage alloc] initWithData:d];
 	[NSApp setApplicationIconImage:i];
 	[[NSApp dockTile] display];
-
-	proccreate(callservep9p, nil, 0);
+	
+	gfx_started();
 }
 
 - (NSApplicationPresentationOptions)window:(id)arg
@@ -242,10 +199,10 @@ callservep9p(void *v)
 - (BOOL)isFlipped { return YES; }
 - (BOOL)acceptsFirstResponder { return YES; }
 
-// rpc_attachscreen allocates a new screen window with the given label and size
+// rpc_attach allocates a new screen window with the given label and size
 // and attaches it to client c (by setting c->view).
 Memimage*
-rpc_attachscreen(Client *c, char *label, char *winsize)
+rpc_attach(Client *c, char *label, char *winsize)
 {
 	LOG(@"attachscreen(%s, %s)", label, winsize);
 	
@@ -468,71 +425,73 @@ rpc_setcursor(Client *client, Cursor *c, Cursor2 *c2)
 }
 
 - (void)initimg {
-@autoreleasepool{
-	CGFloat scale;
-	NSSize size;
-	MTLTextureDescriptor *textureDesc;
-
-	size = [self convertSizeToBacking:[self bounds].size];
-	self.client->mouserect = Rect(0, 0, size.width, size.height);
-
-	LOG(@"initimg %.0f %.0f", size.width, size.height);
-
-	self.img = allocmemimage(self.client->mouserect, XRGB32);
-	if(self.img == nil)
-		panic("allocmemimage: %r");
-	if(self.img->data == nil)
-		panic("img->data == nil");
-
-	textureDesc = [MTLTextureDescriptor
-		texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm
-		width:size.width
-		height:size.height
-		mipmapped:NO];
-	textureDesc.allowGPUOptimizedContents = YES;
-	textureDesc.usage = MTLTextureUsageShaderRead;
-	textureDesc.cpuCacheMode = MTLCPUCacheModeWriteCombined;
-	self.dlayer.texture = [self.dlayer.device newTextureWithDescriptor:textureDesc];
-
-	scale = [self.win backingScaleFactor];
-	[self.dlayer setDrawableSize:size];
-	[self.dlayer setContentsScale:scale];
-
-	// NOTE: This is not really the display DPI.
-	// On retina, scale is 2; otherwise it is 1.
-	// This formula gives us 220 for retina, 110 otherwise.
-	// That's not quite right but it's close to correct.
-	// https://en.wikipedia.org/wiki/Retina_display#Models
-	self.client->displaydpi = scale * 110;
+	@autoreleasepool {
+		CGFloat scale;
+		NSSize size;
+		MTLTextureDescriptor *textureDesc;
+	
+		size = [self convertSizeToBacking:[self bounds].size];
+		self.client->mouserect = Rect(0, 0, size.width, size.height);
+	
+		LOG(@"initimg %.0f %.0f", size.width, size.height);
+	
+		self.img = allocmemimage(self.client->mouserect, XRGB32);
+		if(self.img == nil)
+			panic("allocmemimage: %r");
+		if(self.img->data == nil)
+			panic("img->data == nil");
+	
+		textureDesc = [MTLTextureDescriptor
+			texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm
+			width:size.width
+			height:size.height
+			mipmapped:NO];
+		textureDesc.allowGPUOptimizedContents = YES;
+		textureDesc.usage = MTLTextureUsageShaderRead;
+		textureDesc.cpuCacheMode = MTLCPUCacheModeWriteCombined;
+		self.dlayer.texture = [self.dlayer.device newTextureWithDescriptor:textureDesc];
+	
+		scale = [self.win backingScaleFactor];
+		[self.dlayer setDrawableSize:size];
+		[self.dlayer setContentsScale:scale];
+	
+		// NOTE: This is not really the display DPI.
+		// On retina, scale is 2; otherwise it is 1.
+		// This formula gives us 220 for retina, 110 otherwise.
+		// That's not quite right but it's close to correct.
+		// https://en.wikipedia.org/wiki/Retina_display#Models
+		self.client->displaydpi = scale * 110;
+	}
 }
-	LOG(@"initimg return");
-}
 
-// rpc_flushmemscreen flushes changes to view.img's rectangle r
+// rpc_flush flushes changes to view.img's rectangle r
 // to the on-screen window, making them visible.
 // Called from an RPC thread with no client lock held.
 void
-rpc_flushmemscreen(Client *client, Rectangle r)
+rpc_flush(Client *client, Rectangle r)
 {
 	DrawView *view = (__bridge DrawView*)client->view;
 	dispatch_async(dispatch_get_main_queue(), ^(void){
-		[view flushmemscreen:r];
+		[view flush:r];
 	});
 }
 
-- (void)flushmemscreen:(Rectangle)r {
-	LOG(@"flushmemscreen(%d,%d,%d,%d)", r.min.x, r.min.y, Dx(r), Dy(r));
-	if(!rectinrect(r, Rect(0, 0, self.dlayer.texture.width, self.dlayer.texture.height))){
-		LOG(@"Rectangle is out of bounds, return.");
-		return;
-	}
-
+- (void)flush:(Rectangle)r {
 	@autoreleasepool{
+		if(!rectclip(&r, Rect(0, 0, self.dlayer.texture.width, self.dlayer.texture.height)) || !rectclip(&r, self.img->r))
+			return;
+		
+		// self.client->drawlk protects the pixel data in self.img.
+		// In addition to avoiding a technical data race,
+		// the lock avoids drawing partial updates, which makes
+		// animations like sweeping windows much less flickery.
+		qlock(&self.client->drawlk);
 		[self.dlayer.texture
 			replaceRegion:MTLRegionMake2D(r.min.x, r.min.y, Dx(r), Dy(r))
 			mipmapLevel:0
 			withBytes:byteaddr(self.img, Pt(r.min.x, r.min.y))
 			bytesPerRow:self.img->width*sizeof(u32int)];
+		qunlock(&self.client->drawlk);
 
 		NSRect nr = NSMakeRect(r.min.x, r.min.y, Dx(r), Dy(r));
 		dispatch_time_t time;
@@ -565,7 +524,7 @@ rpc_resizeimg(Client *c)
 
 - (void)resizeimg {
 	[self initimg];
-	_drawreplacescreenimage(self.client, self.img);
+	gfx_replacescreenimage(self.client, self.img);
 	[self sendmouse:0];
 }
 
@@ -750,7 +709,7 @@ rpc_setmouse(Client *c, Point p)
 	});
 }
 
--(void)setmouse:(Point)p {
+- (void)setmouse:(Point)p {
 	@autoreleasepool{
 		NSPoint q;
 
@@ -782,21 +741,10 @@ rpc_setmouse(Client *c, Point p)
 }
 
 // conforms to protocol NSTextInputClient
-- (BOOL)hasMarkedText
-{
-	LOG(@"hasMarkedText");
-	return _markedRange.location != NSNotFound;
-}
-- (NSRange)markedRange
-{
-	LOG(@"markedRange");
-	return _markedRange;
-}
-- (NSRange)selectedRange
-{
-	LOG(@"selectedRange");
-	return _selectedRange;
-}
+- (BOOL)hasMarkedText { return _markedRange.location != NSNotFound; }
+- (NSRange)markedRange { return _markedRange; }
+- (NSRange)selectedRange { return _selectedRange; }
+
 - (void)setMarkedText:(id)string
 	selectedRange:(NSRange)sRange
 	replacementRange:(NSRange)rRange
@@ -861,8 +809,8 @@ rpc_setmouse(Client *c, Point p)
 		_markedRange.location, _markedRange.length,
 		_selectedRange.location, _selectedRange.length);
 }
-- (void)unmarkText
-{
+
+- (void)unmarkText {
 	//NSUInteger i;
 	NSUInteger len;
 
@@ -874,12 +822,13 @@ rpc_setmouse(Client *c, Point p)
 	_markedRange = NSMakeRange(NSNotFound, 0);
 	_selectedRange = NSMakeRange(0, 0);
 }
-- (NSArray<NSAttributedStringKey> *)validAttributesForMarkedText
-{
+
+- (NSArray<NSAttributedStringKey>*)validAttributesForMarkedText {
 	LOG(@"validAttributesForMarkedText");
 	return @[];
 }
-- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)r
+
+- (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)r
 	actualRange:(NSRangePointer)actualRange
 {
 	NSRange sr;
@@ -899,9 +848,8 @@ rpc_setmouse(Client *c, Point p)
 	LOG(@"	return %@", s);
 	return s;
 }
-- (void)insertText:(id)s
-	replacementRange:(NSRange)r
-{
+
+- (void)insertText:(id)s replacementRange:(NSRange)r {
 	NSUInteger i;
 	NSUInteger len;
 
@@ -916,22 +864,22 @@ rpc_setmouse(Client *c, Point p)
 	_markedRange = NSMakeRange(NSNotFound, 0);
 	_selectedRange = NSMakeRange(0, 0);
 }
+
 - (NSUInteger)characterIndexForPoint:(NSPoint)point
 {
 	LOG(@"characterIndexForPoint: %g, %g", point.x, point.y);
 	return 0;
 }
-- (NSRect)firstRectForCharacterRange:(NSRange)r
-	actualRange:(NSRangePointer)actualRange
-{
+
+- (NSRect)firstRectForCharacterRange:(NSRange)r actualRange:(NSRangePointer)actualRange {
 	LOG(@"firstRectForCharacterRange: (%ld, %ld) (%ld, %ld)",
 		r.location, r.length, actualRange->location, actualRange->length);
 	if(actualRange)
 		*actualRange = r;
 	return [[self window] convertRectToScreen:_lastInputRect];
 }
-- (void)doCommandBySelector:(SEL)s
-{
+
+- (void)doCommandBySelector:(SEL)s {
 	NSEvent *e;
 	NSEventModifierFlags m;
 	uint c, k;
@@ -955,8 +903,7 @@ rpc_setmouse(Client *c, Point p)
 }
 
 // Helper for managing input rect approximately
-- (void)resetLastInputRect
-{
+- (void)resetLastInputRect {
 	LOG(@"resetLastInputRect");
 	_lastInputRect.origin.x = 0.0;
 	_lastInputRect.origin.y = 0.0;
@@ -964,8 +911,7 @@ rpc_setmouse(Client *c, Point p)
 	_lastInputRect.size.height = 0.0;
 }
 
-- (void)enlargeLastInputRect:(NSRect)r
-{
+- (void)enlargeLastInputRect:(NSRect)r {
 	r.origin.y = [self bounds].size.height - r.origin.y - r.size.height;
 	_lastInputRect = NSUnionRect(_lastInputRect, r);
 	LOG(@"update last input rect (%g, %g, %g, %g)",
@@ -973,8 +919,7 @@ rpc_setmouse(Client *c, Point p)
 		_lastInputRect.size.width, _lastInputRect.size.height);
 }
 
-- (void)clearInput
-{
+- (void)clearInput {
 	if(_tmpText.length){
 		uint i;
 		int l;
@@ -1079,48 +1024,42 @@ keycvt(uint code)
 	}
 }
 
-// TODO
+// rpc_getsnarf reads the current pasteboard as a plain text string.
+// Called from an RPC thread with no client lock held.
 char*
 rpc_getsnarf(void)
 {
-	NSPasteboard *pb;
-	NSString *s;
-
-	@autoreleasepool{
-		pb = [NSPasteboard generalPasteboard];
-
-		qlock(&snarfl);
-		s = [pb stringForType:NSPasteboardTypeString];
-		qunlock(&snarfl);
-
-		if(s)
-			return strdup((char *)[s UTF8String]);
-		else
-			return nil;
-	}
+	char __block *ret;
+	
+	ret = nil;
+	dispatch_sync(dispatch_get_main_queue(), ^(void) {
+		@autoreleasepool {
+			NSPasteboard *pb = [NSPasteboard generalPasteboard];
+			NSString *s = [pb stringForType:NSPasteboardTypeString];
+			if(s)
+				ret = strdup((char*)[s UTF8String]);
+		}
+	});
+	return ret;
 }
 
-// TODO
+// rpc_putsnarf writes the given text to the pasteboard.
+// Called from an RPC thread with no client lock held.
 void
 rpc_putsnarf(char *s)
 {
-	NSArray *t;
-	NSPasteboard *pb;
-	NSString *str;
-
-	if(strlen(s) >= SnarfSize)
+	if(s == nil || strlen(s) >= SnarfSize)
 		return;
 
-	@autoreleasepool{
-		t = [NSArray arrayWithObject:NSPasteboardTypeString];
-		pb = [NSPasteboard generalPasteboard];
-		str = [[NSString alloc] initWithUTF8String:s];
-
-		qlock(&snarfl);
-		[pb declareTypes:t owner:nil];
-		[pb setString:str forType:NSPasteboardTypeString];
-		qunlock(&snarfl);
-	}
+	dispatch_sync(dispatch_get_main_queue(), ^(void) {
+		@autoreleasepool{
+			NSArray *t = [NSArray arrayWithObject:NSPasteboardTypeString];
+			NSPasteboard *pb = [NSPasteboard generalPasteboard];
+			NSString *str = [[NSString alloc] initWithUTF8String:s];
+			[pb declareTypes:t owner:nil];
+			[pb setString:str forType:NSPasteboardTypeString];
+		}
+	});
 }
 
 static void
blob - 0e7540be9b94a460a62c3b07b4c6a412b7ee3861
blob + 5169c11312729a6f7a1ff97b0a3204101176323c
--- src/cmd/devdraw/srv.c
+++ src/cmd/devdraw/srv.c
@@ -18,18 +18,72 @@ static void runmsg(Client*, Wsysmsg*);
 static void replymsg(Client*, Wsysmsg*);
 static void matchkbd(Client*);
 static void matchmouse(Client*);
+static void serve(void*);
+static Client *client0;
 
 int trace = 0;
 
+static void
+usage(void)
+{
+	fprint(2, "usage: devdraw (don't run directly)\n");
+	threadexitsall("usage");
+}
+
 void
-servep9p(Client *c)
+threadmain(int argc, char **argv)
 {
+	/*
+	 * 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);
+
+	ARGBEGIN{
+	case 'D':		/* for good ps -a listings */
+		break;
+	case 'f':		/* fall through for backward compatibility */
+	case 'g':
+	case 'b':
+		break;
+	default:
+		usage();
+	}ARGEND
+
+	fmtinstall('W', drawfcallfmt);
+
+	client0 = mallocz(sizeof(Client), 1);
+	if(client0 == nil){
+		fprint(2, "initdraw: allocating client0: out of memory");
+		abort();
+	}
+	client0->displaydpi = 100;
+	client0->rfd = 3;
+	client0->wfd = 4;
+
+	gfx_main();
+}
+
+void
+gfx_started(void)
+{
+	proccreate(serve, client0, 0);
+}
+
+static void
+serve(void *v)
+{
+	Client *c;
 	uchar buf[4], *mbuf;
 	int nmbuf, n, nn;
 	Wsysmsg m;
 
-	fmtinstall('W', drawfcallfmt);
-
+	c = v;
 	mbuf = nil;
 	nmbuf = 0;
 	while((n = read(c->rfd, buf, 4)) == 4){
@@ -52,6 +106,9 @@ servep9p(Client *c)
 		if(trace) fprint(2, "%ud [%d] <- %W\n", nsec()/1000000, threadid(), &m);
 		runmsg(c, &m);
 	}
+
+	rpc_shutdown();
+	threadexitsall(nil);
 }
 
 static void
@@ -79,13 +136,13 @@ runmsg(Client *c, Wsysmsg *m)
 	switch(m->type){
 	case Tinit:
 		memimageinit();
-		i = rpc_attachscreen(c, m->label, m->winsize);
-		_initdisplaymemimage(c, i);
+		i = rpc_attach(c, m->label, m->winsize);
+		draw_initdisplaymemimage(c, i);
 		replymsg(c, m);
 		break;
 
 	case Trdmouse:
-		qlock(&c->inputlk);
+		qlock(&c->eventlk);
 		c->mousetags.t[c->mousetags.wi++] = m->tag;
 		if(c->mousetags.wi == nelem(c->mousetags.t))
 			c->mousetags.wi = 0;
@@ -93,11 +150,11 @@ runmsg(Client *c, Wsysmsg *m)
 			sysfatal("too many queued mouse reads");
 		c->mouse.stall = 0;
 		matchmouse(c);
-		qunlock(&c->inputlk);
+		qunlock(&c->eventlk);
 		break;
 
 	case Trdkbd:
-		qlock(&c->inputlk);
+		qlock(&c->eventlk);
 		c->kbdtags.t[c->kbdtags.wi++] = m->tag;
 		if(c->kbdtags.wi == nelem(c->kbdtags.t))
 			c->kbdtags.wi = 0;
@@ -105,7 +162,7 @@ runmsg(Client *c, Wsysmsg *m)
 			sysfatal("too many queued keyboard reads");
 		c->kbd.stall = 0;
 		matchkbd(c);
-		qunlock(&c->inputlk);
+		qunlock(&c->eventlk);
 		break;
 
 	case Tmoveto:
@@ -148,16 +205,15 @@ runmsg(Client *c, Wsysmsg *m)
 		break;
 
 	case Twrsnarf:
-		putsnarf(m->snarf);
+		rpc_putsnarf(m->snarf);
 		replymsg(c, m);
 		break;
 
 	case Trddraw:
-		qlock(&c->inputlk);
 		n = m->count;
 		if(n > sizeof buf)
 			n = sizeof buf;
-		n = _drawmsgread(c, buf, n);
+		n = draw_dataread(c, buf, n);
 		if(n < 0)
 			replyerror(c, m);
 		else{
@@ -165,16 +221,13 @@ runmsg(Client *c, Wsysmsg *m)
 			m->data = buf;
 			replymsg(c, m);
 		}
-		qunlock(&c->inputlk);
 		break;
 
 	case Twrdraw:
-		qlock(&c->inputlk);
-		if(_drawmsgwrite(c, m->data, m->count) < 0)
+		if(draw_datawrite(c, m->data, m->count) < 0)
 			replyerror(c, m);
 		else
 			replymsg(c, m);
-		qunlock(&c->inputlk);
 		break;
 
 	case Ttop:
@@ -192,13 +245,10 @@ runmsg(Client *c, Wsysmsg *m)
 /*
  * Reply to m.
  */
-QLock replylock;
 static void
 replymsg(Client *c, Wsysmsg *m)
 {
 	int n;
-	static uchar *mbuf;
-	static int nmbuf;
 
 	/* T -> R msg */
 	if(m->type%2 == 0)
@@ -208,18 +258,18 @@ replymsg(Client *c, Wsysmsg *m)
 	/* copy to output buffer */
 	n = sizeW2M(m);
 
-	qlock(&replylock);
-	if(n > nmbuf){
-		free(mbuf);
-		mbuf = malloc(n);
-		if(mbuf == nil)
+	qlock(&c->wfdlk);
+	if(n > c->nmbuf){
+		free(c->mbuf);
+		c->mbuf = malloc(n);
+		if(c->mbuf == nil)
 			sysfatal("out of memory");
-		nmbuf = n;
+		c->nmbuf = n;
 	}
-	convW2M(m, mbuf, n);
-	if(write(c->wfd, mbuf, n) != n)
+	convW2M(m, c->mbuf, n);
+	if(write(c->wfd, c->mbuf, n) != n)
 		sysfatal("write: %r");
-	qunlock(&replylock);
+	qunlock(&c->wfdlk);
 }
 
 /*
@@ -245,13 +295,13 @@ matchkbd(Client *c)
 }
 
 // matchmouse matches queued mouse reads with queued mouse events.
-// It must be called with c->inputlk held.
+// It must be called with c->eventlk held.
 static void
 matchmouse(Client *c)
 {
 	Wsysmsg m;
 
-	if(canqlock(&c->inputlk)) {
+	if(canqlock(&c->eventlk)) {
 		fprint(2, "misuse of matchmouse\n");
 		abort();
 	}
@@ -280,7 +330,7 @@ gfx_mousetrack(Client *c, int x, int y, int b, uint ms
 {
 	Mouse *m;
 
-	qlock(&c->inputlk);
+	qlock(&c->eventlk);
 	if(x < c->mouserect.min.x)
 		x = c->mouserect.min.x;
 	if(x > c->mouserect.max.x)
@@ -312,15 +362,15 @@ gfx_mousetrack(Client *c, int x, int y, int b, uint ms
 		}
 		matchmouse(c);
 	}
-	qunlock(&c->inputlk);
+	qunlock(&c->eventlk);
 }
 
 // kputc adds ch to the keyboard buffer.
-// It must be called with c->inputlk held.
+// It must be called with c->eventlk held.
 static void
 kputc(Client *c, int ch)
 {
-	if(canqlock(&c->inputlk)) {
+	if(canqlock(&c->eventlk)) {
 		fprint(2, "misuse of kputc\n");
 		abort();
 	}
@@ -339,12 +389,12 @@ kputc(Client *c, int ch)
 void
 gfx_abortcompose(Client *c)
 {
-	qlock(&c->inputlk);
+	qlock(&c->eventlk);
 	if(c->kbd.alting) {
 		c->kbd.alting = 0;
 		c->kbd.nk = 0;
 	}
-	qunlock(&c->inputlk);
+	qunlock(&c->eventlk);
 }
 
 // gfx_keystroke records a single-rune keystroke.
@@ -354,11 +404,11 @@ gfx_keystroke(Client *c, int ch)
 {
 	int i;
 
-	qlock(&c->inputlk);
+	qlock(&c->eventlk);
 	if(ch == Kalt){
 		c->kbd.alting = !c->kbd.alting;
 		c->kbd.nk = 0;
-		qunlock(&c->inputlk);
+		qunlock(&c->eventlk);
 		return;
 	}
 	if(ch == Kcmd+'r') {
@@ -368,24 +418,24 @@ gfx_keystroke(Client *c, int ch)
 			c->forcedpi = 100;
 		else
 			c->forcedpi = 225;
-		qunlock(&c->inputlk);
+		qunlock(&c->eventlk);
 		rpc_resizeimg(c);
 		return;
 	}
 	if(!c->kbd.alting){
 		kputc(c, ch);
-		qunlock(&c->inputlk);
+		qunlock(&c->eventlk);
 		return;
 	}
 	if(c->kbd.nk >= nelem(c->kbd.k))      // should not happen
 		c->kbd.nk = 0;
 	c->kbd.k[c->kbd.nk++] = ch;
-	ch = _latin1(c->kbd.k, c->kbd.nk);
+	ch = latin1(c->kbd.k, c->kbd.nk);
 	if(ch > 0){
 		c->kbd.alting = 0;
 		kputc(c, ch);
 		c->kbd.nk = 0;
-		qunlock(&c->inputlk);
+		qunlock(&c->eventlk);
 		return;
 	}
 	if(ch == -1){
@@ -393,10 +443,10 @@ gfx_keystroke(Client *c, int ch)
 		for(i=0; i<c->kbd.nk; i++)
 			kputc(c, c->kbd.k[i]);
 		c->kbd.nk = 0;
-		qunlock(&c->inputlk);
+		qunlock(&c->eventlk);
 		return;
 	}
 	// need more input
-	qunlock(&c->inputlk);
+	qunlock(&c->eventlk);
 	return;
 }
blob - 4b5b570d240b272f51caa10e0ed88a78a2913a3c
blob + f9cf0868dce46b69a9b04a7fc295184f4b40d6a4
--- src/cmd/devdraw/x11-init.c
+++ src/cmd/devdraw/x11-init.c
@@ -733,6 +733,6 @@ _xreplacescreenimage(void)
 		XFreePixmap(_x.display, _x.nextscreenpm);
 	_x.nextscreenpm = pixmap;
 	_x.screenr = r;
-	_drawreplacescreenimage(m);
+	gfx_replacescreenimage(m);
 	return 1;
 }
blob - bdf7d2b2e44465fc3f19cbc9a39cfade337a2084
blob + 12034d1b4f312caa163c9bfbe06a734a58d97571
--- src/cmd/devdraw/x11-itrans.c
+++ src/cmd/devdraw/x11-itrans.c
@@ -146,7 +146,7 @@ abortcompose(void)
 
 static Rune* sendrune(Rune);
 
-extern int _latin1(Rune*, int);
+extern int latin1(Rune*, int);
 static Rune*
 xtoplan9latin1(XEvent *e)
 {
@@ -182,7 +182,7 @@ sendrune(Rune r)
 			return nil;
 		}
 		k[nk++] = r;
-		n = _latin1(k, nk);
+		n = latin1(k, nk);
 		if(n > 0){
 			alting = 0;
 			k[0] = n;
blob - 4d72415b18dd29d1978c44483a06aa3d495f1630
blob + cfede6f5936b216f14af04786b559c40d01193e2
--- src/cmd/devdraw/x11-srv.c
+++ src/cmd/devdraw/x11-srv.c
@@ -365,7 +365,7 @@ runmsg(Wsysmsg *m)
 		n = m->count;
 		if(n > sizeof buf)
 			n = sizeof buf;
-		n = _drawmsgread(buf, n);
+		n = draw_dataread(buf, n);
 		if(n < 0)
 			replyerror(m);
 		else{
@@ -376,7 +376,7 @@ runmsg(Wsysmsg *m)
 		break;
 
 	case Twrdraw:
-		if(_drawmsgwrite(m->data, m->count) < 0)
+		if(draw_datawrite(m->data, m->count) < 0)
 			replyerror(m);
 		else
 			replymsg(m);