Commit Diff


commit - 843e5af1986a2e8f1c4f6177dc4509501711a22c
commit + b1a086dee9bf5846b31323ba2c438f8853a9c87f
blob - e83f6f07bc4733dea0559faa404cc0b7f135895c
blob + 77a3f44a2d401940ea3f6bef12c41284acffa6a0
--- src/cmd/devdraw/devdraw.c
+++ src/cmd/devdraw/devdraw.c
@@ -14,8 +14,6 @@
 #include <drawfcall.h>
 #include "devdraw.h"
 
-extern void _flushmemscreen(Rectangle);
-
 static	Draw		sdraw;
 Client		*client0;
 static	int		drawuninstall(Client*, int);
@@ -32,6 +30,8 @@ _initdisplaymemimage(Client *c, Memimage *m)
 	c->op = SoverD;
 }
 
+// _drawreplacescreen 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)
 {
@@ -141,7 +141,7 @@ addflush(Client *c, Rectangle r)
 	}
 	/* emit current state */
 	if(c->flushrect.min.x < c->flushrect.max.x)
-		_flushmemscreen(c->flushrect);
+		rpc_flushmemscreen(c, c->flushrect);
 	c->flushrect = r;
 	c->waste = 0;
 }
@@ -178,7 +178,7 @@ void
 drawflush(Client *c)
 {
 	if(c->flushrect.min.x < c->flushrect.max.x)
-		_flushmemscreen(c->flushrect);
+		rpc_flushmemscreen(c, c->flushrect);
 	c->flushrect = Rect(10000, 10000, -10000, -10000);
 }
 
blob - fe9532b3a45bc2273538606e5c539d0240476a51
blob + 30586228a15294d002c2b0473ce539ce7c7e8b53
--- src/cmd/devdraw/devdraw.h
+++ src/cmd/devdraw/devdraw.h
@@ -28,6 +28,8 @@ struct Kbdbuf
 	int wi;
 	int stall;
 	int alting;
+	Rune k[10];
+	int nk;
 };
 
 struct Mousebuf
@@ -75,7 +77,7 @@ struct Client
 
 	int		rfd;
 	int		wfd;
-	void*		view;
+	const void*		view;
 	
 	QLock inputlk;
 	Kbdbuf kbd;
@@ -163,6 +165,22 @@ void	_drawreplacescreenimage(Client*, Memimage*);
 int _latin1(Rune*, int);
 int parsewinsize(char*, Rectangle*, int*);
 int mouseswap(int);
-void abortcompose(Client*);
 
+void	gfx_abortcompose(Client*);
+void	gfx_keystroke(Client*, int);
+void	gfx_mousetrack(Client*, int, int, int, uint);
+
+void	rpc_setmouse(Client*, Point);
+void	rpc_setcursor(Client*, Cursor*, Cursor2*);
+void	rpc_setlabel(Client*, char*);
+void	rpc_resizeimg(Client*);
+void	rpc_resizewindow(Client*, Rectangle);
+void	rpc_topwin(Client*);
+char*	rpc_getsnarf(void);
+void	rpc_putsnarf(char*);
+Memimage *rpc_attachscreen(Client*, char*, char*);
+void	rpc_flushmemscreen(Client*, Rectangle);
+
 extern Client *client0;
+
+void	servep9p(Client*);
blob - 7bc0920d8a8df1bfc7ce0191021a42fec1a7243d (mode 644)
blob + /dev/null
--- src/cmd/devdraw/mac-screen.h
+++ /dev/null
@@ -1,19 +0,0 @@
-#define setcursor dsetcursor
-
-Memimage *attachscreen(Client*, char*, char*);
-void	setmouse(Point);
-void	setcursor(Cursor*, Cursor2*);
-void	setlabel(char*);
-char*	getsnarf(void);
-void	putsnarf(char*);
-void	topwin(void);
-
-void	mousetrack(Client*, int, int, int, uint);
-void	keystroke(Client*, int);
-void	kicklabel(char*);
-
-void	servep9p(Client*);
-
-void resizeimg(Client*);
-
-void resizewindow(Rectangle);
blob - 8ce8a0cdbf2dde7a4a47bd95309ba46173ff2688
blob + d756d3d750069a17b029ae73a10f36022f0a196d
--- src/cmd/devdraw/mac-screen.m
+++ src/cmd/devdraw/mac-screen.m
@@ -21,7 +21,6 @@
 #include <keyboard.h>
 #include <drawfcall.h>
 #include "devdraw.h"
-#include "mac-screen.h"
 #include "bigarrow.h"
 #include "glendapng.h"
 
@@ -34,7 +33,6 @@ AUTOFRAMEWORK(QuartzCore)
 static void setprocname(const char*);
 static uint keycvt(uint);
 static uint msec(void);
-static Memimage* initimg(Client*);
 
 void
 usage(void)
@@ -43,34 +41,14 @@ usage(void)
 	threadexitsall("usage");
 }
 
-@interface DrawLayer : CAMetalLayer
-@end
+@class DrawView;
+@class DrawLayer;
+
 @interface AppDelegate : NSObject<NSApplicationDelegate>
-+ (void)makewin:(NSValue *)v;
-+ (void)callkicklabel:(NSString *)v;
-+ (void)callsetNeedsDisplayInRect:(NSValue *)v;
-+ (void)callsetcursor:(NSValue *)v;
 @end
 
-@interface DevDrawView : NSView<NSTextInputClient,NSWindowDelegate>
-@property (nonatomic, assign) Client *client;
-@property (nonatomic, assign) DrawLayer *dlayer;
-@property (nonatomic, assign) NSWindow *win;
-@property (nonatomic, assign) NSCursor *currentCursor;
-@property (nonatomic, assign) Memimage *img;
-
-- (void)clearInput;
-- (void)getmouse:(NSEvent *)e;
-- (void)sendmouse:(NSUInteger)b;
-- (void)resetLastInputRect;
-- (void)enlargeLastInputRect:(NSRect)r;
-@end
-
 static AppDelegate *myApp = NULL;
 
-static id<MTLDevice> device;
-static id<MTLCommandQueue> commandQueue;
-static id<MTLTexture> texture;
 
 static QLock snarfl;
 
@@ -100,11 +78,13 @@ threadmain(int argc, char **argv)
 	}ARGEND
 
 	client0 = mallocz(sizeof(Client), 1);
-	client0->displaydpi = 100;
 	if(client0 == nil){
 		fprint(2, "initdraw: allocating client0: out of memory");
 		abort();
 	}
+	client0->displaydpi = 100;
+	client0->rfd = 3;
+	client0->wfd = 4;
 
 	setprocname(argv0);
 
@@ -117,22 +97,168 @@ threadmain(int argc, char **argv)
 	}
 }
 
-
 void
 callservep9p(void *v)
 {
 	USED(v);
 
-	client0->rfd = 3;
-	client0->wfd = 4;
 	servep9p(client0);
 	[NSApp terminate:myApp];
 }
 
 @implementation AppDelegate
+- (void)applicationDidFinishLaunching:(id)arg
+{
+	NSMenu *m, *sm;
+	NSData *d;
+	NSImage *i;
 
-+ (void)makewin:(NSValue *)v
+	LOG(@"applicationDidFinishLaunching");
+
+	sm = [NSMenu new];
+	[sm addItemWithTitle:@"Toggle Full Screen" action:@selector(toggleFullScreen:) keyEquivalent:@"f"];
+	[sm addItemWithTitle:@"Hide" action:@selector(hide:) keyEquivalent:@"h"];
+	[sm addItemWithTitle:@"Quit" action:@selector(terminate:) keyEquivalent:@"q"];
+	m = [NSMenu new];
+	[m addItemWithTitle:@"DEVDRAW" action:NULL keyEquivalent:@""];
+	[m setSubmenu:sm forItem:[m itemWithTitle:@"DEVDRAW"]];
+	[NSApp setMainMenu:m];
+
+	d = [[NSData alloc] initWithBytes:glenda_png length:(sizeof glenda_png)];
+	i = [[NSImage alloc] initWithData:d];
+	[NSApp setApplicationIconImage:i];
+	[[NSApp dockTile] display];
+
+	proccreate(callservep9p, nil, 0);
+}
+
+- (NSApplicationPresentationOptions)window:(id)arg
+		willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)proposedOptions {
+	NSApplicationPresentationOptions o;
+	o = proposedOptions;
+	o &= ~(NSApplicationPresentationAutoHideDock | NSApplicationPresentationAutoHideMenuBar);
+	o |= NSApplicationPresentationHideDock | NSApplicationPresentationHideMenuBar;
+	return o;
+}
+
+- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication {
+	return YES;
+}
+@end
+
+@interface DrawLayer : CAMetalLayer
+@property (nonatomic, retain) id<MTLCommandQueue> cmd;
+@property (nonatomic, retain) id<MTLTexture> texture;
+@end
+
+@implementation DrawLayer
+- (void)display
+{
+	LOG(@"display");
+	LOG(@"display query drawable");
+
+	@autoreleasepool{
+		id<CAMetalDrawable> drawable = [self nextDrawable];
+		if(!drawable){
+			LOG(@"display couldn't get drawable");
+			[self setNeedsDisplay];
+			return;
+		}
+	
+		LOG(@"display got drawable");
+	
+		id<MTLCommandBuffer> cbuf = [self.cmd commandBuffer];
+		id<MTLBlitCommandEncoder> blit = [cbuf blitCommandEncoder];
+		[blit copyFromTexture:self.texture
+			sourceSlice:0
+			sourceLevel:0
+			sourceOrigin:MTLOriginMake(0, 0, 0)
+			sourceSize:MTLSizeMake(self.texture.width, self.texture.height, self.texture.depth)
+			toTexture:drawable.texture
+			destinationSlice:0
+			destinationLevel:0
+			destinationOrigin:MTLOriginMake(0, 0, 0)];
+		[blit endEncoding];
+	
+		[cbuf presentDrawable:drawable];
+		drawable = nil;
+		[cbuf addCompletedHandler:^(id<MTLCommandBuffer> cmdBuff){
+			if(cmdBuff.error){
+				NSLog(@"command buffer finished with error: %@",
+					cmdBuff.error.localizedDescription);
+			}else
+				LOG(@"command buffer finishes present drawable");
+		}];
+		[cbuf commit];
+	}
+	LOG(@"display commit");
+}
+@end
+
+@interface DrawView : NSView<NSTextInputClient,NSWindowDelegate>
+@property (nonatomic, assign) Client *client;
+@property (nonatomic, retain) DrawLayer *dlayer;
+@property (nonatomic, retain) NSWindow *win;
+@property (nonatomic, retain) NSCursor *currentCursor;
+@property (nonatomic, assign) Memimage *img;
+
+- (id)attach:(Client*)client winsize:(char*)winsize label:(char*)label;
+- (void)topwin;
+- (void)setlabel:(char*)label;
+- (void)setcursor:(Cursor*)c cursor2:(Cursor2*)c2;
+- (void)setmouse:(Point)p;
+- (void)clearInput;
+- (void)getmouse:(NSEvent*)e;
+- (void)sendmouse:(NSUInteger)b;
+- (void)resetLastInputRect;
+- (void)enlargeLastInputRect:(NSRect)r;
+@end
+
+@implementation DrawView
 {
+	NSMutableString *_tmpText;
+	NSRange _markedRange;
+	NSRange _selectedRange;
+	NSRect _lastInputRect;	// The view is flipped, this is not.
+	BOOL _tapping;
+	NSUInteger _tapFingers;
+	NSUInteger _tapTime;
+}
+
+- (id)init
+{
+	LOG(@"View init");
+	self = [super init];
+	[self setAllowedTouchTypes:NSTouchTypeMaskDirect|NSTouchTypeMaskIndirect];
+	_tmpText = [[NSMutableString alloc] initWithCapacity:2];
+	_markedRange = NSMakeRange(NSNotFound, 0);
+	_selectedRange = NSMakeRange(0, 0);
+	return self;
+}
+
+- (CALayer*)makeBackingLayer { return [DrawLayer layer]; }
+- (BOOL)wantsUpdateLayer { return YES; }
+- (BOOL)isOpaque { return YES; }
+- (BOOL)isFlipped { return YES; }
+- (BOOL)acceptsFirstResponder { return YES; }
+
+// rpc_attachscreen 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)
+{
+	LOG(@"attachscreen(%s, %s)", label, winsize);
+	
+	dispatch_sync(dispatch_get_main_queue(), ^(void) {
+		@autoreleasepool {
+			DrawView *view = [[DrawView new] attach:c winsize:winsize label:label];
+			[view initimg];
+		}
+	});
+	return ((__bridge DrawView*)c->view).img;
+}
+
+- (id)attach:(Client*)client winsize:(char*)winsize label:(char*)label {
 	NSRect r, sr;
 	Rectangle wr;
 	int set;
@@ -144,10 +270,10 @@ callservep9p(void *v)
 		| NSWindowStyleMaskMiniaturizable
 		| NSWindowStyleMaskResizable;
 
+	s = winsize;
 	sr = [[NSScreen mainScreen] frame];
 	r = [[NSScreen mainScreen] visibleFrame];
 
-	s = [v pointerValue];
 	LOG(@"makewin(%s)", s);
 	if(s && *s){
 		if(parsewinsize(s, &wr, &set) < 0)
@@ -177,17 +303,16 @@ callservep9p(void *v)
 	[win setRestorable:NO];
 	[win setAcceptsMouseMovedEvents:YES];
 
-	DevDrawView *view = [DevDrawView new];
-	client0->view = view;
-	view.client = client0;
-	view.win = win;
-	view.currentCursor = nil;
-	[win setContentView:view];
-	[win setDelegate:view];
-	[view setWantsLayer:YES];
-	[view setLayerContentsRedrawPolicy:NSViewLayerContentsRedrawOnSetNeedsDisplay];
+	client->view = CFBridgingRetain(self);
+	self.client = client;
+	self.win = win;
+	self.currentCursor = nil;
+	[win setContentView:self];
+	[win setDelegate:self];
+	[self setWantsLayer:YES];
+	[self setLayerContentsRedrawPolicy:NSViewLayerContentsRedrawOnSetNeedsDisplay];
 	
-	device = nil;
+	id<MTLDevice> device = nil;
 	allDevices = MTLCopyAllDevices();
 	for(id mtlDevice in allDevices) {
 		if ([mtlDevice isLowPower] && ![mtlDevice isRemovable]) {
@@ -198,18 +323,19 @@ callservep9p(void *v)
 	if(!device)
 		device = MTLCreateSystemDefaultDevice();
 
-	commandQueue = [device newCommandQueue];
-
-	DrawLayer *layer = (DrawLayer *)[view layer];
-	view.dlayer = layer;
+	DrawLayer *layer = (DrawLayer*)[self layer];
+	self.dlayer = layer;
 	layer.device = device;
+	layer.cmd = [device newCommandQueue];
 	layer.pixelFormat = MTLPixelFormatBGRA8Unorm;
 	layer.framebufferOnly = YES;
 	layer.opaque = YES;
 
 	// We use a default transparent layer on top of the CAMetalLayer.
 	// This seems to make fullscreen applications behave.
-	{
+	// Specifically, without this code if you enter full screen with Cmd-F,
+	// the screen goes black until the first mouse click.
+	if(1) {
 		CALayer *stub = [CALayer layer];
 		stub.frame = CGRectMake(0, 0, 1, 1);
 		[stub setNeedsDisplay];
@@ -218,65 +344,77 @@ callservep9p(void *v)
 
 	[NSEvent setMouseCoalescingEnabled:NO];
 
-	topwin();
+	[self topwin];
+	[self setlabel:label];
+	[self setcursor:nil cursor2:nil];
+	
+	return self;
 }
 
-+ (void)callkicklabel:(NSString *)s
+// rpc_topwin moves the window to the top of the desktop.
+// Called from an RPC thread with no client lock held.
+void
+rpc_topwin(Client *c)
 {
-	DevDrawView *view = client0->view;
-	
-	LOG(@"callkicklabel(%@)", s);
-	[view.win setTitle:s];
-	[[NSApp dockTile] setBadgeLabel:s];
+	DrawView *view = (__bridge DrawView*)c->view;
+	dispatch_sync(dispatch_get_main_queue(), ^(void) {
+		[view topwin];
+	});
 }
 
+- (void)topwin {
+	[self.win makeKeyAndOrderFront:nil];
+	[NSApp activateIgnoringOtherApps:YES];
+}
 
-+ (void)callsetNeedsDisplayInRect:(NSValue *)v
+// rpc_setlabel updates the client window's label.
+// If label == nil, the call is a no-op.
+// Called from an RPC thread with no client lock held.
+void
+rpc_setlabel(Client *client, char *label)
 {
-	NSRect r;
-	dispatch_time_t time;
-	DevDrawView *view = client0->view;
+	DrawView *view = (__bridge DrawView*)client->view;
+	dispatch_sync(dispatch_get_main_queue(), ^(void){
+		[view setlabel:label];
+	});
+}
 
-	r = [v rectValue];
-	LOG(@"callsetNeedsDisplayInRect(%g, %g, %g, %g)", r.origin.x, r.origin.y, r.size.width, r.size.height);
-	r = [view.win convertRectFromBacking:r];
-	LOG(@"setNeedsDisplayInRect(%g, %g, %g, %g)", r.origin.x, r.origin.y, r.size.width, r.size.height);
-	[view.dlayer setNeedsDisplayInRect:r];
+- (void)setlabel:(char*)label {
+	LOG(@"setlabel(%s)", label);
+	if(label == nil)
+		return;
 
-	time = dispatch_time(DISPATCH_TIME_NOW, 16 * NSEC_PER_MSEC);
-	dispatch_after(time, dispatch_get_main_queue(), ^(void){
-		[view.dlayer setNeedsDisplayInRect:r];
-	});
+	@autoreleasepool{
+		NSString *s = [[NSString alloc] initWithUTF8String:label];
+		[self.win setTitle:s];
+		[[NSApp dockTile] setBadgeLabel:s]; // TODO: Not with multiple windows
+	}
+}
 
-	[view enlargeLastInputRect:r];
+// rpc_setcursor updates the client window's cursor image.
+// Either c and c2 are both non-nil, or they are both nil to use the default arrow.
+// Called from an RPC thread with no client lock held.
+void
+rpc_setcursor(Client *client, Cursor *c, Cursor2 *c2)
+{
+	DrawView *view = (__bridge DrawView*)client->view;
+	dispatch_sync(dispatch_get_main_queue(), ^(void){
+		[view setcursor:c cursor2:c2];
+	});
 }
 
-typedef struct Cursors Cursors;
-struct Cursors {
-	Cursor *c;
-	Cursor2 *c2;
-};
+- (void)setcursor:(Cursor*)c cursor2:(Cursor2*)c2 {
+	if(!c) {
+		c = &bigarrow;
+		c2 = &bigarrow2;
+	}
 
-+ (void)callsetcursor:(NSValue *)v
-{
-	Cursors *cs;
-	Cursor *c;
-	Cursor2 *c2;
 	NSBitmapImageRep *r, *r2;
 	NSImage *i;
 	NSPoint p;
 	uchar *plane[5], *plane2[5];
 	uint b;
-	DevDrawView *view = client0->view;
 
-	cs = [v pointerValue];
-	c = cs->c;
-	if(!c)
-		c = &bigarrow;
-	c2 = cs->c2;
-	if(!c2)
-		c2 = &bigarrow2;
-
 	r = [[NSBitmapImageRep alloc]
 		initWithBitmapDataPlanes:nil
 		pixelsWide:16
@@ -311,127 +449,166 @@ struct Cursors {
 		plane2[1][b] = c2->set[b] | c2->clr[b];
 	}
 
-	// For checking out the cursor bitmap image
-/*
-	static BOOL saveimg = YES;
-	if(saveimg){
+	static BOOL debug = NO;
+	if(debug){
 		NSData *data = [r representationUsingType: NSBitmapImageFileTypeBMP properties: @{}];
 		[data writeToFile: @"/tmp/r.bmp" atomically: NO];
 		data = [r2 representationUsingType: NSBitmapImageFileTypeBMP properties: @{}];
 		[data writeToFile: @"/tmp/r2.bmp" atomically: NO];
-		saveimg = NO;
+		debug = NO;
 	}
-*/
 
 	i = [[NSImage alloc] initWithSize:NSMakeSize(16, 16)];
 	[i addRepresentation:r2];
 	[i addRepresentation:r];
 
 	p = NSMakePoint(-c->offset.x, -c->offset.y);
-	view.currentCursor = [[NSCursor alloc] initWithImage:i hotSpot:p];
-
-	[view.win invalidateCursorRectsForView:view];
+	self.currentCursor = [[NSCursor alloc] initWithImage:i hotSpot:p];
+	[self.win invalidateCursorRectsForView:self];
 }
 
-- (void)applicationDidFinishLaunching:(id)arg
-{
-	NSMenu *m, *sm;
-	NSData *d;
-	NSImage *i;
+- (void)initimg {
+@autoreleasepool{
+	CGFloat scale;
+	NSSize size;
+	MTLTextureDescriptor *textureDesc;
 
-	LOG(@"applicationDidFinishLaunching");
+	size = [self convertSizeToBacking:[self bounds].size];
+	self.client->mouserect = Rect(0, 0, size.width, size.height);
 
-	sm = [NSMenu new];
-	[sm addItemWithTitle:@"Toggle Full Screen" action:@selector(toggleFullScreen:) keyEquivalent:@"f"];
-	[sm addItemWithTitle:@"Hide" action:@selector(hide:) keyEquivalent:@"h"];
-	[sm addItemWithTitle:@"Quit" action:@selector(terminate:) keyEquivalent:@"q"];
-	m = [NSMenu new];
-	[m addItemWithTitle:@"DEVDRAW" action:NULL keyEquivalent:@""];
-	[m setSubmenu:sm forItem:[m itemWithTitle:@"DEVDRAW"]];
-	[NSApp setMainMenu:m];
+	LOG(@"initimg %.0f %.0f", size.width, size.height);
 
-	d = [[NSData alloc] initWithBytes:glenda_png length:(sizeof glenda_png)];
-	i = [[NSImage alloc] initWithData:d];
-	[NSApp setApplicationIconImage:i];
-	[[NSApp dockTile] display];
+	self.img = allocmemimage(self.client->mouserect, XRGB32);
+	if(self.img == nil)
+		panic("allocmemimage: %r");
+	if(self.img->data == nil)
+		panic("img->data == nil");
 
-	proccreate(callservep9p, nil, 0);
-}
+	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];
 
-- (NSApplicationPresentationOptions)window:(id)arg
-		willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)proposedOptions {
-	NSApplicationPresentationOptions o;
-	o = proposedOptions;
-	o &= ~(NSApplicationPresentationAutoHideDock | NSApplicationPresentationAutoHideMenuBar);
-	o |= NSApplicationPresentationHideDock | NSApplicationPresentationHideMenuBar;
-	return o;
-}
+	scale = [self.win backingScaleFactor];
+	[self.dlayer setDrawableSize:size];
+	[self.dlayer setContentsScale:scale];
 
-- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication {
-	return YES;
+	// 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;
 }
-@end
-
-@implementation DevDrawView
-{
-	NSMutableString *_tmpText;
-	NSRange _markedRange;
-	NSRange _selectedRange;
-	NSRect _lastInputRect;	// The view is flipped, this is not.
-	BOOL _tapping;
-	NSUInteger _tapFingers;
-	NSUInteger _tapTime;
+	LOG(@"initimg return");
 }
 
-- (id)init
+// rpc_flushmemscreen 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)
 {
-	LOG(@"View init");
-	self = [super init];
-	[self setAllowedTouchTypes:NSTouchTypeMaskDirect|NSTouchTypeMaskIndirect];
-	_tmpText = [[NSMutableString alloc] initWithCapacity:2];
-	_markedRange = NSMakeRange(NSNotFound, 0);
-	_selectedRange = NSMakeRange(0, 0);
-	return self;
+	DrawView *view = (__bridge DrawView*)client->view;
+	dispatch_async(dispatch_get_main_queue(), ^(void){
+		[view flushmemscreen:r];
+	});
 }
 
-- (void)windowDidResize:(NSNotification *)notification
-{
-	if(![self inLiveResize] && self.img) {
-		resizeimg(self.client);
+- (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)windowDidBecomeKey:(id)arg
-{
-        [self sendmouse:0];
+	@autoreleasepool{
+		[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)];
+
+		NSRect nr = NSMakeRect(r.min.x, r.min.y, Dx(r), Dy(r));
+		dispatch_time_t time;
+
+		LOG(@"callsetNeedsDisplayInRect(%g, %g, %g, %g)", nr.origin.x, nr.origin.y, nr.size.width, nr.size.height);
+		nr = [self.win convertRectFromBacking:nr];
+		LOG(@"setNeedsDisplayInRect(%g, %g, %g, %g)", nr.origin.x, nr.origin.y, nr.size.width, nr.size.height);
+		[self.dlayer setNeedsDisplayInRect:nr];
+	
+		time = dispatch_time(DISPATCH_TIME_NOW, 16 * NSEC_PER_MSEC);
+		dispatch_after(time, dispatch_get_main_queue(), ^(void){
+			[self.dlayer setNeedsDisplayInRect:nr];
+		});
+	
+		[self enlargeLastInputRect:nr];
+	}
 }
 
-- (CALayer *)makeBackingLayer
+// rpc_resizeimg forces the client window to discard its current window and make a new one.
+// It is called when the user types Cmd-R to toggle whether retina mode is forced.
+// Called from an RPC thread with no client lock held.
+void
+rpc_resizeimg(Client *c)
 {
-	LOG(@"makeBackingLayer");
-	return [DrawLayer layer];
+	DrawView *view = (__bridge DrawView*)c->view;
+	dispatch_sync(dispatch_get_main_queue(), ^(void){
+		[view resizeimg];
+	});
 }
 
-- (BOOL)wantsUpdateLayer
-{
-	return YES;
+- (void)resizeimg {
+	[self initimg];
+	_drawreplacescreenimage(self.client, self.img);
+	[self sendmouse:0];
 }
 
-- (BOOL)isOpaque
+- (void)windowDidResize:(NSNotification *)notification {
+	if(![self inLiveResize] && self.img) {
+		[self resizeimg];
+	}
+}
+- (void)viewDidEndLiveResize
 {
-	return YES;
+	[super viewDidEndLiveResize];
+	if(self.img)
+		[self resizeimg];
 }
 
-- (BOOL)isFlipped
+- (void)viewDidChangeBackingProperties
 {
-	return YES;
+	[super viewDidChangeBackingProperties];
+	if(self.img)
+		[self resizeimg];
 }
 
-- (BOOL)acceptsFirstResponder
+// rpc_resizewindow asks for the client window to be resized to size r.
+// Called from an RPC thread with no client lock held.
+void
+rpc_resizewindow(Client *c, Rectangle r)
 {
-	return YES;
+	DrawView *view = (__bridge DrawView*)c->view;
+
+	LOG(@"resizewindow %d %d %d %d", r.min.x, r.min.y, Dx(r), Dy(r));
+	dispatch_async(dispatch_get_main_queue(), ^(void){
+		NSSize s;
+
+		s = [view convertSizeFromBacking:NSMakeSize(Dx(r), Dy(r))];
+		[view.win setContentSize:s];
+	});
 }
 
+
+- (void)windowDidBecomeKey:(id)arg {
+        [self sendmouse:0];
+}
+
 - (void)mouseMoved:(NSEvent*)e{ [self getmouse:e];}
 - (void)mouseDown:(NSEvent*)e{ [self getmouse:e];}
 - (void)mouseDragged:(NSEvent*)e{ [self getmouse:e];}
@@ -483,7 +660,7 @@ struct Cursors {
 			b |= 4;
 		[self sendmouse:b];
 	}else if(m & ~omod & NSEventModifierFlagOption)
-		keystroke(self.client, Kalt);
+		gfx_keystroke(self.client, Kalt);
 
 	omod = m;
 }
@@ -540,7 +717,7 @@ struct Cursors {
 	if(b == 1){
 		m = [e modifierFlags];
 		if(m & NSEventModifierFlagOption){
-			abortcompose(self.client);
+			gfx_abortcompose(self.client);
 			b = 2;
 		}else
 		if(m & NSEventModifierFlagCommand)
@@ -557,28 +734,51 @@ struct Cursors {
 		[self.window mouseLocationOutsideOfEventStream]];
 	p.y = Dy(self.client->mouserect) - p.y;
 	// LOG(@"(%g, %g) <- sendmouse(%d)", p.x, p.y, (uint)b);
-	mousetrack(self.client, p.x, p.y, b, msec());
+	gfx_mousetrack(self.client, p.x, p.y, b, msec());
 	if(b && _lastInputRect.size.width && _lastInputRect.size.height)
 		[self resetLastInputRect];
 }
 
-- (void)resetCursorRects {
-	[super resetCursorRects];
-	[self addCursorRect:self.bounds cursor:self.currentCursor];
+// rpc_setmouse moves the mouse cursor.
+// Called from an RPC thread with no client lock held.
+void
+rpc_setmouse(Client *c, Point p)
+{
+	DrawView *view = (__bridge DrawView*)c->view;
+	dispatch_async(dispatch_get_main_queue(), ^(void){
+		[view setmouse:p];
+	});
 }
 
-- (void)viewDidEndLiveResize
-{
-	[super viewDidEndLiveResize];
-	if(self.img)
-		resizeimg(self.client);
+-(void)setmouse:(Point)p {
+	@autoreleasepool{
+		NSPoint q;
+
+		LOG(@"setmouse(%d,%d)", p.x, p.y);
+		q = [self.win convertPointFromBacking:NSMakePoint(p.x, p.y)];
+		LOG(@"(%g, %g) <- fromBacking", q.x, q.y);
+		q = [self convertPoint:q toView:nil];
+		LOG(@"(%g, %g) <- toWindow", q.x, q.y);
+		q = [self.win convertPointToScreen:q];
+		LOG(@"(%g, %g) <- toScreen", q.x, q.y);
+		// Quartz has the origin of the "global display
+		// coordinate space" at the top left of the primary
+		// screen with y increasing downward, while Cocoa has
+		// the origin at the bottom left of the primary screen
+		// with y increasing upward.  We flip the coordinate
+		// with a negative sign and shift upward by the height
+		// of the primary screen.
+		q.y = NSScreen.screens[0].frame.size.height - q.y;
+		LOG(@"(%g, %g) <- setmouse", q.x, q.y);
+		CGWarpMouseCursorPosition(NSPointToCGPoint(q));
+		CGAssociateMouseAndMouseCursorPosition(true);
+	}
 }
 
-- (void)viewDidChangeBackingProperties
-{
-	[super viewDidChangeBackingProperties];
-	if(self.img)
-		resizeimg(self.client);
+
+- (void)resetCursorRects {
+	[super resetCursorRects];
+	[self addCursorRect:self.bounds cursor:self.currentCursor];
 }
 
 // conforms to protocol NSTextInputClient
@@ -637,24 +837,24 @@ struct Cursors {
 		LOG(@"text length %ld", _tmpText.length);
 		for(i = 0; i <= _tmpText.length; ++i){
 			if(i == _markedRange.location)
-				keystroke(self.client, '[');
+				gfx_keystroke(self.client, '[');
 			if(_selectedRange.length){
 				if(i == _selectedRange.location)
-					keystroke(self.client, '{');
+					gfx_keystroke(self.client, '{');
 				if(i == NSMaxRange(_selectedRange))
-					keystroke(self.client, '}');
+					gfx_keystroke(self.client, '}');
 				}
 			if(i == NSMaxRange(_markedRange))
-				keystroke(self.client, ']');
+				gfx_keystroke(self.client, ']');
 			if(i < _tmpText.length)
-				keystroke(self.client, [_tmpText characterAtIndex:i]);
+				gfx_keystroke(self.client, [_tmpText characterAtIndex:i]);
 		}
 		int l;
 		l = 1 + _tmpText.length - NSMaxRange(_selectedRange)
 			+ (_selectedRange.length > 0);
 		LOG(@"move left %d", l);
 		for(i = 0; i < l; ++i)
-			keystroke(self.client, Kleft);
+			gfx_keystroke(self.client, Kleft);
 	}
 
 	LOG(@"text: \"%@\"  (%ld,%ld)  (%ld,%ld)", _tmpText,
@@ -669,7 +869,7 @@ struct Cursors {
 	LOG(@"unmarkText");
 	len = [_tmpText length];
 	//for(i = 0; i < len; ++i)
-	//	keystroke(self.client, [_tmpText characterAtIndex:i]);
+	//	gfx_keystroke(self.client, [_tmpText characterAtIndex:i]);
 	[_tmpText deleteCharactersInRange:NSMakeRange(0, len)];
 	_markedRange = NSMakeRange(NSNotFound, 0);
 	_selectedRange = NSMakeRange(0, 0);
@@ -711,7 +911,7 @@ struct Cursors {
 
 	len = [s length];
 	for(i = 0; i < len; ++i)
-		keystroke(self.client, [s characterAtIndex:i]);
+		gfx_keystroke(self.client, [s characterAtIndex:i]);
 	[_tmpText deleteCharactersInRange:NSMakeRange(0, _tmpText.length)];
 	_markedRange = NSMakeRange(NSNotFound, 0);
 	_selectedRange = NSMakeRange(0, 0);
@@ -751,7 +951,7 @@ struct Cursors {
 			k += Kcmd;
 	}
 	if(k>0)
-		keystroke(self.client, k);
+		gfx_keystroke(self.client, k);
 }
 
 // Helper for managing input rect approximately
@@ -782,69 +982,13 @@ struct Cursors {
 			+ (_selectedRange.length > 0);
 		LOG(@"move right %d", l);
 		for(i = 0; i < l; ++i)
-			keystroke(self.client, Kright);
+			gfx_keystroke(self.client, Kright);
 		l = _tmpText.length+2+2*(_selectedRange.length > 0);
 		LOG(@"backspace %d", l);
 		for(uint i = 0; i < l; ++i)
-			keystroke(self.client, Kbs);
+			gfx_keystroke(self.client, Kbs);
 	}
-}
-
-@end
-
-@implementation DrawLayer
-
-- (void)display
-{
-	id<MTLCommandBuffer> cbuf;
-	id<MTLBlitCommandEncoder> blit;
-
-	LOG(@"display");
-
-	cbuf = [commandQueue commandBuffer];
-
-	LOG(@"display query drawable");
-
-@autoreleasepool{
-	id<CAMetalDrawable> drawable;
-	DevDrawView *view = client0->view;
-
-	drawable = [view.dlayer nextDrawable];
-	if(!drawable){
-		LOG(@"display couldn't get drawable");
-		[self setNeedsDisplay];
-		return;
-	}
-
-	LOG(@"display got drawable");
-
-	blit = [cbuf blitCommandEncoder];
-	[blit copyFromTexture:texture
-		sourceSlice:0
-		sourceLevel:0
-		sourceOrigin:MTLOriginMake(0, 0, 0)
-		sourceSize:MTLSizeMake(texture.width, texture.height, texture.depth)
-		toTexture:drawable.texture
-		destinationSlice:0
-		destinationLevel:0
-		destinationOrigin:MTLOriginMake(0, 0, 0)];
-	[blit endEncoding];
-
-	[cbuf presentDrawable:drawable];
-	drawable = nil;
-}
-	[cbuf addCompletedHandler:^(id<MTLCommandBuffer> cmdBuff){
-		if(cmdBuff.error){
-			NSLog(@"command buffer finished with error: %@",
-				cmdBuff.error.localizedDescription);
-		}else
-			LOG(@"command buffer finishes present drawable");
-	}];
-	[cbuf commit];
-
-	LOG(@"display commit");
 }
-
 @end
 
 static uint
@@ -932,125 +1076,12 @@ keycvt(uint code)
 	case NSHelpFunctionKey:
 	case NSModeSwitchFunctionKey: return 0;
 	default: return code;
-	}
-}
-
-Memimage*
-attachscreen(Client *c, char *label, char *winsize)
-{
-	LOG(@"attachscreen(%s, %s)", label, winsize);
-	[AppDelegate
-		performSelectorOnMainThread:@selector(makewin:)
-		withObject:[NSValue valueWithPointer:winsize]
-		waitUntilDone:YES];
-	kicklabel(label);
-	setcursor(nil, nil);
-	c->mouse.resized = 0;
-	return initimg(c);
-}
-
-static Memimage*
-initimg(Client *c)
-{
-	DevDrawView *view = c->view;
-
-@autoreleasepool{
-	CGFloat scale;
-	NSSize size;
-	MTLTextureDescriptor *textureDesc;
-
-	size = [view convertSizeToBacking:[view bounds].size];
-	c->mouserect = Rect(0, 0, size.width, size.height);
-
-	LOG(@"initimg %.0f %.0f", size.width, size.height);
-
-	view.img = allocmemimage(c->mouserect, XRGB32);
-	if(view.img == nil)
-		panic("allocmemimage: %r");
-	if(view.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;
-	texture = [device newTextureWithDescriptor:textureDesc];
-
-	scale = [view.win backingScaleFactor];
-	[view.dlayer setDrawableSize:size];
-	[view.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
-	c->displaydpi = scale * 110;
-}
-	LOG(@"initimg return");
-
-	return view.img;
-}
-
-void
-_flushmemscreen(Rectangle r)
-{
-	DevDrawView *view = client0->view;
-
-	LOG(@"_flushmemscreen(%d,%d,%d,%d)", r.min.x, r.min.y, Dx(r), Dy(r));
-	if(!rectinrect(r, Rect(0, 0, texture.width, texture.height))){
-		LOG(@"Rectangle is out of bounds, return.");
-		return;
-	}
-
-	@autoreleasepool{
-		[texture
-			replaceRegion:MTLRegionMake2D(r.min.x, r.min.y, Dx(r), Dy(r))
-			mipmapLevel:0
-			withBytes:byteaddr(view.img, Pt(r.min.x, r.min.y))
-			bytesPerRow:view.img->width*sizeof(u32int)];
-		[AppDelegate
-			performSelectorOnMainThread:@selector(callsetNeedsDisplayInRect:)
-			withObject:[NSValue valueWithRect:NSMakeRect(r.min.x, r.min.y, Dx(r), Dy(r))]
-			waitUntilDone:NO];
-	}
-}
-
-void
-setmouse(Point p)
-{
-	DevDrawView *view = client0->view;
-
-	@autoreleasepool{
-		NSPoint q;
-
-		LOG(@"setmouse(%d,%d)", p.x, p.y);
-		q = [view.win convertPointFromBacking:NSMakePoint(p.x, p.y)];
-		LOG(@"(%g, %g) <- fromBacking", q.x, q.y);
-		q = [view convertPoint:q toView:nil];
-		LOG(@"(%g, %g) <- toWindow", q.x, q.y);
-		q = [view.win convertPointToScreen:q];
-		LOG(@"(%g, %g) <- toScreen", q.x, q.y);
-		// Quartz has the origin of the "global display
-		// coordinate space" at the top left of the primary
-		// screen with y increasing downward, while Cocoa has
-		// the origin at the bottom left of the primary screen
-		// with y increasing upward.  We flip the coordinate
-		// with a negative sign and shift upward by the height
-		// of the primary screen.
-		q.y = NSScreen.screens[0].frame.size.height - q.y;
-		LOG(@"(%g, %g) <- setmouse", q.x, q.y);
-		CGWarpMouseCursorPosition(NSPointToCGPoint(q));
-		CGAssociateMouseAndMouseCursorPosition(true);
 	}
 }
 
+// TODO
 char*
-getsnarf(void)
+rpc_getsnarf(void)
 {
 	NSPasteboard *pb;
 	NSString *s;
@@ -1069,8 +1100,9 @@ getsnarf(void)
 	}
 }
 
+// TODO
 void
-putsnarf(char *s)
+rpc_putsnarf(char *s)
 {
 	NSArray *t;
 	NSPasteboard *pb;
@@ -1091,75 +1123,6 @@ putsnarf(char *s)
 	}
 }
 
-void
-kicklabel(char *label)
-{
-	NSString *s;
-
-	LOG(@"kicklabel(%s)", label);
-	if(label == nil)
-		return;
-
-	@autoreleasepool{
-		s = [[NSString alloc] initWithUTF8String:label];
-		[AppDelegate
-			performSelectorOnMainThread:@selector(callkicklabel:)
-			withObject:s
-			waitUntilDone:NO];
-	}
-}
-
-void
-setcursor(Cursor *c, Cursor2 *c2)
-{
-	Cursors cs;
-	
-	cs.c = c;
-	cs.c2 = c2;
-
-	[AppDelegate
-		performSelectorOnMainThread:@selector(callsetcursor:)
-		withObject:[NSValue valueWithPointer:&cs]
-		waitUntilDone:YES];
-}
-
-void
-topwin(void)
-{
-	DevDrawView *view = client0->view;
-
-	[view.win
-		performSelectorOnMainThread:
-		@selector(makeKeyAndOrderFront:)
-		withObject:nil
-		waitUntilDone:YES];
-
-	[NSApp activateIgnoringOtherApps:YES];
-}
-
-void
-resizeimg(Client *c)
-{
-	DevDrawView *view = c->view;
-
-	_drawreplacescreenimage(c, initimg(c));
-	[view sendmouse:0];
-}
-
-void
-resizewindow(Rectangle r)
-{
-	DevDrawView *view = client0->view;
-
-	LOG(@"resizewindow %d %d %d %d", r.min.x, r.min.y, Dx(r), Dy(r));
-	dispatch_async(dispatch_get_main_queue(), ^(void){
-		NSSize s;
-
-		s = [view convertSizeFromBacking:NSMakeSize(Dx(r), Dy(r))];
-		[view.win setContentSize:s];
-	});
-}
-
 static void
 setprocname(const char *s)
 {
blob - 6727ef6318909fd8b1f28fcb801198665c24c086 (mode 644)
blob + /dev/null
--- src/cmd/devdraw/mac-srv.c
+++ /dev/null
@@ -1,378 +0,0 @@
-/*
- * Window system protocol server.
- */
-
-#include <u.h>
-#include <libc.h>
-#include <thread.h>
-#include <draw.h>
-#include <memdraw.h>
-#include <memlayer.h>
-#include <keyboard.h>
-#include <mouse.h>
-#include <cursor.h>
-#include <drawfcall.h>
-#include "devdraw.h"
-#include "mac-screen.h"
-
-void runmsg(Client*, Wsysmsg*);
-void replymsg(Client*, Wsysmsg*);
-void matchkbd(Client*);
-void matchmouse(Client*);
-
-int trace = 0;
-
-void
-servep9p(Client *c)
-{
-	uchar buf[4], *mbuf;
-	int nmbuf, n, nn;
-	Wsysmsg m;
-
-	fmtinstall('W', drawfcallfmt);
-
-	mbuf = nil;
-	nmbuf = 0;
-	while((n = read(c->rfd, buf, 4)) == 4){
-		GET(buf, n);
-		if(n > nmbuf){
-			free(mbuf);
-			mbuf = malloc(4+n);
-			if(mbuf == nil)
-				sysfatal("malloc: %r");
-			nmbuf = n;
-		}
-		memmove(mbuf, buf, 4);
-		nn = readn(c->rfd, mbuf+4, n-4);
-		if(nn != n-4)
-			sysfatal("eof during message");
-
-		/* pick off messages one by one */
-		if(convM2W(mbuf, nn+4, &m) <= 0)
-			sysfatal("cannot convert message");
-		if(trace) fprint(2, "%ud [%d] <- %W\n", nsec()/1000000, threadid(), &m);
-		runmsg(c, &m);
-	}
-}
-
-void
-replyerror(Client *c, Wsysmsg *m)
-{
-	char err[256];
-
-	rerrstr(err, sizeof err);
-	m->type = Rerror;
-	m->error = err;
-	replymsg(c, m);
-}
-
-/*
- * Handle a single wsysmsg.
- * Might queue for later (kbd, mouse read)
- */
-void
-runmsg(Client *c, Wsysmsg *m)
-{
-	static uchar buf[65536];
-	int n;
-	Memimage *i;
-
-	switch(m->type){
-	case Tinit:
-		memimageinit();
-		i = attachscreen(c, m->label, m->winsize);
-		_initdisplaymemimage(c, i);
-		replymsg(c, m);
-		break;
-
-	case Trdmouse:
-		qlock(&c->inputlk);
-		c->mousetags.t[c->mousetags.wi++] = m->tag;
-		if(c->mousetags.wi == nelem(c->mousetags.t))
-			c->mousetags.wi = 0;
-		if(c->mousetags.wi == c->mousetags.ri)
-			sysfatal("too many queued mouse reads");
-		c->mouse.stall = 0;
-		matchmouse(c);
-		qunlock(&c->inputlk);
-		break;
-
-	case Trdkbd:
-		qlock(&c->inputlk);
-		c->kbdtags.t[c->kbdtags.wi++] = m->tag;
-		if(c->kbdtags.wi == nelem(c->kbdtags.t))
-			c->kbdtags.wi = 0;
-		if(c->kbdtags.wi == c->kbdtags.ri)
-			sysfatal("too many queued keyboard reads");
-		c->kbd.stall = 0;
-		matchkbd(c);
-		qunlock(&c->inputlk);
-		break;
-
-	case Tmoveto:
-		setmouse(m->mouse.xy);
-		replymsg(c, m);
-		break;
-
-	case Tcursor:
-		if(m->arrowcursor)
-			setcursor(nil, nil);
-		else
-			setcursor(&m->cursor, nil);
-		replymsg(c, m);
-		break;
-
-	case Tcursor2:
-		if(m->arrowcursor)
-			setcursor(nil, nil);
-		else
-			setcursor(&m->cursor, &m->cursor2);
-		replymsg(c, m);
-		break;
-
-	case Tbouncemouse:
-	//	_xbouncemouse(&m->mouse);
-		replymsg(c, m);
-		break;
-
-	case Tlabel:
-		kicklabel(m->label);
-		replymsg(c, m);
-		break;
-
-	case Trdsnarf:
-		m->snarf = getsnarf();
-		replymsg(c, m);
-		free(m->snarf);
-		break;
-
-	case Twrsnarf:
-		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);
-		if(n < 0)
-			replyerror(c, m);
-		else{
-			m->count = n;
-			m->data = buf;
-			replymsg(c, m);
-		}
-		qunlock(&c->inputlk);
-		break;
-
-	case Twrdraw:
-		qlock(&c->inputlk);
-		if(_drawmsgwrite(c, m->data, m->count) < 0)
-			replyerror(c, m);
-		else
-			replymsg(c, m);
-		qunlock(&c->inputlk);
-		break;
-
-	case Ttop:
-		topwin();
-		replymsg(c, m);
-		break;
-
-	case Tresize:
-		resizewindow(m->rect);
-		replymsg(c, m);
-		break;
-	}
-}
-
-/*
- * Reply to m.
- */
-QLock replylock;
-void
-replymsg(Client *c, Wsysmsg *m)
-{
-	int n;
-	static uchar *mbuf;
-	static int nmbuf;
-
-	/* T -> R msg */
-	if(m->type%2 == 0)
-		m->type++;
-
-	if(trace) fprint(2, "%ud [%d] -> %W\n", nsec()/1000000, threadid(), m);
-	/* copy to output buffer */
-	n = sizeW2M(m);
-
-	qlock(&replylock);
-	if(n > nmbuf){
-		free(mbuf);
-		mbuf = malloc(n);
-		if(mbuf == nil)
-			sysfatal("out of memory");
-		nmbuf = n;
-	}
-	convW2M(m, mbuf, n);
-	if(write(c->wfd, mbuf, n) != n)
-		sysfatal("write: %r");
-	qunlock(&replylock);
-}
-
-/*
- * Match queued kbd reads with queued kbd characters.
- */
-void
-matchkbd(Client *c)
-{
-	Wsysmsg m;
-
-	if(c->kbd.stall)
-		return;
-	while(c->kbd.ri != c->kbd.wi && c->kbdtags.ri != c->kbdtags.wi){
-		m.type = Rrdkbd;
-		m.tag = c->kbdtags.t[c->kbdtags.ri++];
-		if(c->kbdtags.ri == nelem(c->kbdtags.t))
-			c->kbdtags.ri = 0;
-		m.rune = c->kbd.r[c->kbd.ri++];
-		if(c->kbd.ri == nelem(c->kbd.r))
-			c->kbd.ri = 0;
-		replymsg(c, &m);
-	}
-}
-
-/*
- * Match queued mouse reads with queued mouse events.
- */
-void
-matchmouse(Client *c)
-{
-	Wsysmsg m;
-
-	while(c->mouse.ri != c->mouse.wi && c->mousetags.ri != c->mousetags.wi){
-		m.type = Rrdmouse;
-		m.tag = c->mousetags.t[c->mousetags.ri++];
-		if(c->mousetags.ri == nelem(c->mousetags.t))
-			c->mousetags.ri = 0;
-		m.mouse = c->mouse.m[c->mouse.ri];
-		m.resized = c->mouse.resized;
-		c->mouse.resized = 0;
-		/*
-		if(m.resized)
-			fprint(2, "sending resize\n");
-		*/
-		c->mouse.ri++;
-		if(c->mouse.ri == nelem(c->mouse.m))
-			c->mouse.ri = 0;
-		replymsg(c, &m);
-	}
-}
-
-void
-mousetrack(Client *c, int x, int y, int b, uint ms)
-{
-	Mouse *m;
-
-	if(x < c->mouserect.min.x)
-		x = c->mouserect.min.x;
-	if(x > c->mouserect.max.x)
-		x = c->mouserect.max.x;
-	if(y < c->mouserect.min.y)
-		y = c->mouserect.min.y;
-	if(y > c->mouserect.max.y)
-		y = c->mouserect.max.y;
-
-	qlock(&c->inputlk);
-	// If reader has stopped reading, don't bother.
-	// If reader is completely caught up, definitely queue.
-	// Otherwise, queue only button change events.
-	if(!c->mouse.stall)
-	if(c->mouse.wi == c->mouse.ri || c->mouse.last.buttons != b){
-		m = &c->mouse.last;
-		m->xy.x = x;
-		m->xy.y = y;
-		m->buttons = b;
-		m->msec = ms;
-
-		c->mouse.m[c->mouse.wi] = *m;
-		if(++c->mouse.wi == nelem(c->mouse.m))
-			c->mouse.wi = 0;
-		if(c->mouse.wi == c->mouse.ri){
-			c->mouse.stall = 1;
-			c->mouse.ri = 0;
-			c->mouse.wi = 1;
-			c->mouse.m[0] = *m;
-		}
-		matchmouse(c);
-	}
-	qunlock(&c->inputlk);
-}
-
-void
-kputc(Client *c, int ch)
-{
-	qlock(&c->inputlk);
-	c->kbd.r[c->kbd.wi++] = ch;
-	if(c->kbd.wi == nelem(c->kbd.r))
-		c->kbd.wi = 0;
-	if(c->kbd.ri == c->kbd.wi)
-		c->kbd.stall = 1;
-	matchkbd(c);
-	qunlock(&c->inputlk);
-}
-
-void
-abortcompose(Client *c)
-{
-	if(c->kbd.alting)
-		keystroke(c, Kalt);
-}
-
-void
-keystroke(Client *c, int ch)
-{
-	static Rune k[10];
-	static int nk;
-	int i;
-
-	if(ch == Kalt){
-		c->kbd.alting = !c->kbd.alting;
-		nk = 0;
-		return;
-	}
-	if(ch == Kcmd+'r') {
-		if(c->forcedpi)
-			c->forcedpi = 0;
-		else if(c->displaydpi >= 200)
-			c->forcedpi = 100;
-		else
-			c->forcedpi = 225;
-		resizeimg(c);
-		return;
-	}
-	if(!c->kbd.alting){
-		kputc(c, ch);
-		return;
-	}
-	if(nk >= nelem(k))      // should not happen
-		nk = 0;
-	k[nk++] = ch;
-	ch = _latin1(k, nk);
-	if(ch > 0){
-		c->kbd.alting = 0;
-		kputc(c, ch);
-		nk = 0;
-		return;
-	}
-	if(ch == -1){
-		c->kbd.alting = 0;
-		for(i=0; i<nk; i++)
-			kputc(c, k[i]);
-		nk = 0;
-		return;
-	}
-	// need more input
-	return;
-}
blob - 6546b5903c3e332367d2a5c530097efd529d3487
blob + 7ecf7dc1735a96fda8cf5e3b2b4b9efb8d8bcf4e
--- src/cmd/devdraw/mkfile
+++ src/cmd/devdraw/mkfile
@@ -9,6 +9,7 @@ WSYSOFILES=\
 	devdraw.$O\
 	latin1.$O\
 	mouseswap.$O\
+	srv.$O\
 	winsize.$O\
 
 <|sh ./mkwsysrules.sh
@@ -42,7 +43,7 @@ $O.macargv: $MACARGV
 	$LD -o $target $prereq
 
 %.$O: %.m
-	$CC $CFLAGS $OBJCFLAGS -o $target $stem.m
+	$CC $CFLAGS $OBJCFLAGS -fobjc-arc -o $target $stem.m
 
 CLEANFILES=$O.devdraw $O.macargv $O.drawclient $O.mklatinkbd latin1.h
 
blob - cd72120f1081025311cbe2a7202e06df35644184
blob + 122e9123d2d5df6b58d4c0565622e101dbdd6997
--- src/cmd/devdraw/mkwsysrules.sh
+++ src/cmd/devdraw/mkwsysrules.sh
@@ -53,8 +53,8 @@ if [ $WSYSTYPE = x11 ]; then
 	echo 'WSYSOFILES=$WSYSOFILES '$XO
 	echo 'WSYSHFILES=x11-inc.h x11-keysym2ucs.h x11-memdraw.h'
 elif [ $WSYSTYPE = mac ]; then
-	echo 'WSYSOFILES=$WSYSOFILES mac-draw.o mac-screen.o mac-srv.o'
-	echo 'WSYSHFILES=mac-screen.h'
+	echo 'WSYSOFILES=$WSYSOFILES mac-draw.o mac-screen.o'
+	echo 'WSYSHFILES='
 	echo 'MACARGV=macargv.o'
 elif [ $WSYSTYPE = nowsys ]; then
 	echo 'WSYSOFILES=nowsys.o'
blob - /dev/null
blob + 0e7540be9b94a460a62c3b07b4c6a412b7ee3861 (mode 644)
--- /dev/null
+++ src/cmd/devdraw/srv.c
@@ -0,0 +1,402 @@
+/*
+ * Window system protocol server.
+ */
+
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <memlayer.h>
+#include <keyboard.h>
+#include <mouse.h>
+#include <cursor.h>
+#include <drawfcall.h>
+#include "devdraw.h"
+
+static void runmsg(Client*, Wsysmsg*);
+static void replymsg(Client*, Wsysmsg*);
+static void matchkbd(Client*);
+static void matchmouse(Client*);
+
+int trace = 0;
+
+void
+servep9p(Client *c)
+{
+	uchar buf[4], *mbuf;
+	int nmbuf, n, nn;
+	Wsysmsg m;
+
+	fmtinstall('W', drawfcallfmt);
+
+	mbuf = nil;
+	nmbuf = 0;
+	while((n = read(c->rfd, buf, 4)) == 4){
+		GET(buf, n);
+		if(n > nmbuf){
+			free(mbuf);
+			mbuf = malloc(4+n);
+			if(mbuf == nil)
+				sysfatal("malloc: %r");
+			nmbuf = n;
+		}
+		memmove(mbuf, buf, 4);
+		nn = readn(c->rfd, mbuf+4, n-4);
+		if(nn != n-4)
+			sysfatal("eof during message");
+
+		/* pick off messages one by one */
+		if(convM2W(mbuf, nn+4, &m) <= 0)
+			sysfatal("cannot convert message");
+		if(trace) fprint(2, "%ud [%d] <- %W\n", nsec()/1000000, threadid(), &m);
+		runmsg(c, &m);
+	}
+}
+
+static void
+replyerror(Client *c, Wsysmsg *m)
+{
+	char err[256];
+
+	rerrstr(err, sizeof err);
+	m->type = Rerror;
+	m->error = err;
+	replymsg(c, m);
+}
+
+/*
+ * Handle a single wsysmsg.
+ * Might queue for later (kbd, mouse read)
+ */
+static void
+runmsg(Client *c, Wsysmsg *m)
+{
+	static uchar buf[65536];
+	int n;
+	Memimage *i;
+
+	switch(m->type){
+	case Tinit:
+		memimageinit();
+		i = rpc_attachscreen(c, m->label, m->winsize);
+		_initdisplaymemimage(c, i);
+		replymsg(c, m);
+		break;
+
+	case Trdmouse:
+		qlock(&c->inputlk);
+		c->mousetags.t[c->mousetags.wi++] = m->tag;
+		if(c->mousetags.wi == nelem(c->mousetags.t))
+			c->mousetags.wi = 0;
+		if(c->mousetags.wi == c->mousetags.ri)
+			sysfatal("too many queued mouse reads");
+		c->mouse.stall = 0;
+		matchmouse(c);
+		qunlock(&c->inputlk);
+		break;
+
+	case Trdkbd:
+		qlock(&c->inputlk);
+		c->kbdtags.t[c->kbdtags.wi++] = m->tag;
+		if(c->kbdtags.wi == nelem(c->kbdtags.t))
+			c->kbdtags.wi = 0;
+		if(c->kbdtags.wi == c->kbdtags.ri)
+			sysfatal("too many queued keyboard reads");
+		c->kbd.stall = 0;
+		matchkbd(c);
+		qunlock(&c->inputlk);
+		break;
+
+	case Tmoveto:
+		rpc_setmouse(c, m->mouse.xy);
+		replymsg(c, m);
+		break;
+
+	case Tcursor:
+		if(m->arrowcursor)
+			rpc_setcursor(c, nil, nil);
+		else {
+			scalecursor(&m->cursor2, &m->cursor);
+			rpc_setcursor(c, &m->cursor, &m->cursor2);
+		}
+		replymsg(c, m);
+		break;
+
+	case Tcursor2:
+		if(m->arrowcursor)
+			rpc_setcursor(c, nil, nil);
+		else
+			rpc_setcursor(c, &m->cursor, &m->cursor2);
+		replymsg(c, m);
+		break;
+
+	case Tbouncemouse:
+	//	_xbouncemouse(&m->mouse);
+		replymsg(c, m);
+		break;
+
+	case Tlabel:
+		rpc_setlabel(c, m->label);
+		replymsg(c, m);
+		break;
+
+	case Trdsnarf:
+		m->snarf = rpc_getsnarf();
+		replymsg(c, m);
+		free(m->snarf);
+		break;
+
+	case Twrsnarf:
+		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);
+		if(n < 0)
+			replyerror(c, m);
+		else{
+			m->count = n;
+			m->data = buf;
+			replymsg(c, m);
+		}
+		qunlock(&c->inputlk);
+		break;
+
+	case Twrdraw:
+		qlock(&c->inputlk);
+		if(_drawmsgwrite(c, m->data, m->count) < 0)
+			replyerror(c, m);
+		else
+			replymsg(c, m);
+		qunlock(&c->inputlk);
+		break;
+
+	case Ttop:
+		rpc_topwin(c);
+		replymsg(c, m);
+		break;
+
+	case Tresize:
+		rpc_resizewindow(c, m->rect);
+		replymsg(c, m);
+		break;
+	}
+}
+
+/*
+ * 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)
+		m->type++;
+
+	if(trace) fprint(2, "%ud [%d] -> %W\n", nsec()/1000000, threadid(), m);
+	/* copy to output buffer */
+	n = sizeW2M(m);
+
+	qlock(&replylock);
+	if(n > nmbuf){
+		free(mbuf);
+		mbuf = malloc(n);
+		if(mbuf == nil)
+			sysfatal("out of memory");
+		nmbuf = n;
+	}
+	convW2M(m, mbuf, n);
+	if(write(c->wfd, mbuf, n) != n)
+		sysfatal("write: %r");
+	qunlock(&replylock);
+}
+
+/*
+ * Match queued kbd reads with queued kbd characters.
+ */
+static void
+matchkbd(Client *c)
+{
+	Wsysmsg m;
+
+	if(c->kbd.stall)
+		return;
+	while(c->kbd.ri != c->kbd.wi && c->kbdtags.ri != c->kbdtags.wi){
+		m.type = Rrdkbd;
+		m.tag = c->kbdtags.t[c->kbdtags.ri++];
+		if(c->kbdtags.ri == nelem(c->kbdtags.t))
+			c->kbdtags.ri = 0;
+		m.rune = c->kbd.r[c->kbd.ri++];
+		if(c->kbd.ri == nelem(c->kbd.r))
+			c->kbd.ri = 0;
+		replymsg(c, &m);
+	}
+}
+
+// matchmouse matches queued mouse reads with queued mouse events.
+// It must be called with c->inputlk held.
+static void
+matchmouse(Client *c)
+{
+	Wsysmsg m;
+
+	if(canqlock(&c->inputlk)) {
+		fprint(2, "misuse of matchmouse\n");
+		abort();
+	}
+
+	while(c->mouse.ri != c->mouse.wi && c->mousetags.ri != c->mousetags.wi){
+		m.type = Rrdmouse;
+		m.tag = c->mousetags.t[c->mousetags.ri++];
+		if(c->mousetags.ri == nelem(c->mousetags.t))
+			c->mousetags.ri = 0;
+		m.mouse = c->mouse.m[c->mouse.ri];
+		m.resized = c->mouse.resized;
+		c->mouse.resized = 0;
+		/*
+		if(m.resized)
+			fprint(2, "sending resize\n");
+		*/
+		c->mouse.ri++;
+		if(c->mouse.ri == nelem(c->mouse.m))
+			c->mouse.ri = 0;
+		replymsg(c, &m);
+	}
+}
+
+void
+gfx_mousetrack(Client *c, int x, int y, int b, uint ms)
+{
+	Mouse *m;
+
+	qlock(&c->inputlk);
+	if(x < c->mouserect.min.x)
+		x = c->mouserect.min.x;
+	if(x > c->mouserect.max.x)
+		x = c->mouserect.max.x;
+	if(y < c->mouserect.min.y)
+		y = c->mouserect.min.y;
+	if(y > c->mouserect.max.y)
+		y = c->mouserect.max.y;
+
+	// If reader has stopped reading, don't bother.
+	// If reader is completely caught up, definitely queue.
+	// Otherwise, queue only button change events.
+	if(!c->mouse.stall)
+	if(c->mouse.wi == c->mouse.ri || c->mouse.last.buttons != b){
+		m = &c->mouse.last;
+		m->xy.x = x;
+		m->xy.y = y;
+		m->buttons = b;
+		m->msec = ms;
+
+		c->mouse.m[c->mouse.wi] = *m;
+		if(++c->mouse.wi == nelem(c->mouse.m))
+			c->mouse.wi = 0;
+		if(c->mouse.wi == c->mouse.ri){
+			c->mouse.stall = 1;
+			c->mouse.ri = 0;
+			c->mouse.wi = 1;
+			c->mouse.m[0] = *m;
+		}
+		matchmouse(c);
+	}
+	qunlock(&c->inputlk);
+}
+
+// kputc adds ch to the keyboard buffer.
+// It must be called with c->inputlk held.
+static void
+kputc(Client *c, int ch)
+{
+	if(canqlock(&c->inputlk)) {
+		fprint(2, "misuse of kputc\n");
+		abort();
+	}
+
+	c->kbd.r[c->kbd.wi++] = ch;
+	if(c->kbd.wi == nelem(c->kbd.r))
+		c->kbd.wi = 0;
+	if(c->kbd.ri == c->kbd.wi)
+		c->kbd.stall = 1;
+	matchkbd(c);
+}
+
+// gfx_abortcompose stops any pending compose sequence,
+// because a mouse button has been clicked.
+// It is called from the graphics thread with no locks held.
+void
+gfx_abortcompose(Client *c)
+{
+	qlock(&c->inputlk);
+	if(c->kbd.alting) {
+		c->kbd.alting = 0;
+		c->kbd.nk = 0;
+	}
+	qunlock(&c->inputlk);
+}
+
+// gfx_keystroke records a single-rune keystroke.
+// It is called from the graphics thread with no locks held.
+void
+gfx_keystroke(Client *c, int ch)
+{
+	int i;
+
+	qlock(&c->inputlk);
+	if(ch == Kalt){
+		c->kbd.alting = !c->kbd.alting;
+		c->kbd.nk = 0;
+		qunlock(&c->inputlk);
+		return;
+	}
+	if(ch == Kcmd+'r') {
+		if(c->forcedpi)
+			c->forcedpi = 0;
+		else if(c->displaydpi >= 200)
+			c->forcedpi = 100;
+		else
+			c->forcedpi = 225;
+		qunlock(&c->inputlk);
+		rpc_resizeimg(c);
+		return;
+	}
+	if(!c->kbd.alting){
+		kputc(c, ch);
+		qunlock(&c->inputlk);
+		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);
+	if(ch > 0){
+		c->kbd.alting = 0;
+		kputc(c, ch);
+		c->kbd.nk = 0;
+		qunlock(&c->inputlk);
+		return;
+	}
+	if(ch == -1){
+		c->kbd.alting = 0;
+		for(i=0; i<c->kbd.nk; i++)
+			kputc(c, c->kbd.k[i]);
+		c->kbd.nk = 0;
+		qunlock(&c->inputlk);
+		return;
+	}
+	// need more input
+	qunlock(&c->inputlk);
+	return;
+}