Commit Diff


commit - de43b1629d008aa6cdf4f6beb2b06e3859616a3e
commit + 9af9ceca26596d562a3ae89fda70bad9f8822ab0
blob - a46f19a09f3f8b562dae58ee9ca2101467e025fb
blob + 3b66c954e9645b1b65e4a83e8b8c6a51b143649a
--- src/cmd/devdraw/bigarrow.h
+++ src/cmd/devdraw/bigarrow.h
@@ -1,11 +1,13 @@
-Cursor bigarrow = {
-	{0, 0},
-	{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFC, 
-	 0xFF, 0xF0, 0xFF, 0xF0, 0xFF, 0xF8, 0xFF, 0xFC, 
-	 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFC, 
-	 0xF3, 0xF8, 0xF1, 0xF0, 0xE0, 0xE0, 0xC0, 0x40},
-	{0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFC, 0x7F, 0xF0, 
-	 0x7F, 0xE0, 0x7F, 0xE0, 0x7F, 0xF0, 0x7F, 0xF8, 
-	 0x7F, 0xFC, 0x7F, 0xFE, 0x7F, 0xFC, 0x73, 0xF8, 
-	 0x61, 0xF0, 0x60, 0xE0, 0x40, 0x40, 0x00, 0x00},
+Cursor	bigarrow = {
+	{ -1, -1 },
+	{ 0xFF, 0xFF, 0x80, 0x01, 0x80, 0x02, 0x80, 0x0C,
+	  0x80, 0x10, 0x80, 0x10, 0x80, 0x08, 0x80, 0x04,
+	  0x80, 0x02, 0x80, 0x01, 0x80, 0x02, 0x8C, 0x04,
+	  0x92, 0x08, 0x91, 0x10, 0xA0, 0xA0, 0xC0, 0x40,
+	},
+	{ 0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFC, 0x7F, 0xF0,
+	  0x7F, 0xE0, 0x7F, 0xE0, 0x7F, 0xF0, 0x7F, 0xF8,
+	  0x7F, 0xFC, 0x7F, 0xFE, 0x7F, 0xFC, 0x73, 0xF8,
+	  0x61, 0xF0, 0x60, 0xE0, 0x40, 0x40, 0x00, 0x00,
+	},
 };
blob - 3c4c94c5cce8b5f9ac16cd09c12af36d6282715d
blob + 7b41c34b49fc04b49bbec3701697f76a5e3da251
--- src/cmd/devdraw/cocoa-screen.h
+++ src/cmd/devdraw/cocoa-screen.h
@@ -16,5 +16,9 @@ void	servep9p(void);
 void	zlock(void);
 void	zunlock(void);
 
+void resizeimg(void);
+
 Rectangle mouserect;
-int	mouseresized;
+
+int mouseresized;
+void resizewindow(Rectangle);
blob - /dev/null
blob + c82ce0bc2690ecd3a2071c102e49520a7b804561 (mode 644)
--- /dev/null
+++ src/cmd/devdraw/cocoa-screen-metal.m
@@ -0,0 +1,1260 @@
+#define Cursor OSXCursor
+#define Point OSXPoint
+#define Rect OSXRect
+
+#import <Cocoa/Cocoa.h>
+#import <Metal/Metal.h>
+#import <QuartzCore/CAMetalLayer.h>
+
+#undef Cursor
+#undef Point
+#undef Rect
+
+#include <u.h>
+#include <libc.h>
+#include  "cocoa-thread.h"
+#include <draw.h>
+#include <memdraw.h>
+#include <keyboard.h>
+#include <cursor.h>
+#include "cocoa-screen.h"
+#include "osx-keycodes.h"
+#include "devdraw.h"
+#include "bigarrow.h"
+#include "glendapng.h"
+
+AUTOFRAMEWORK(Cocoa)
+AUTOFRAMEWORK(Metal)
+AUTOFRAMEWORK(QuartzCore)
+
+#define LOG	if(0)NSLog
+
+static void setprocname(const char*);
+static uint keycvt(uint);
+static uint msec(void);
+static Memimage* initimg(void);
+
+void
+usage(void)
+{
+	fprint(2, "usage: devdraw (don't run directly)\n");
+	threadexitsall("usage");
+}
+
+@interface AppDelegate : NSObject<NSApplicationDelegate,NSWindowDelegate>
++ (void)callservep9p:(id)arg;
++ (void)makewin:(NSValue *)v;
++ (void)callkicklabel:(NSString *)v;
++ (void)callsetNeedsDisplayInRect:(NSValue *)v;
++ (void)callsetcursor:(NSValue *)v;
+@end
+@interface DevDrawView : NSView<NSTextInputClient>
+- (void)clearInput;
+- (void)getmouse:(NSEvent *)e;
+- (void)sendmouse:(NSUInteger)b;
+- (void)resetLastInputRect;
+- (void)enlargeLastInputRect:(NSRect)r;
+@end
+@interface DrawLayer : CAMetalLayer
+@end
+
+static AppDelegate *myApp = NULL;
+static DevDrawView *myContent = NULL;
+static NSWindow *win = NULL;
+static NSCursor *currentCursor = NULL;
+
+static DrawLayer *layer;
+static MTLRenderPassDescriptor *renderPass;
+static id<MTLDevice> device;
+static id<MTLRenderPipelineState> pipelineState;
+static id<MTLCommandQueue> commandQueue;
+static id<MTLTexture> texture;
+
+static Memimage *img = NULL;
+
+static QLock snarfl;
+
+static NSString *const metal =
+@"#include<metal_stdlib>\n"
+"using namespace metal;\n"
+"typedef struct {\n"
+"	float4 rCoord [[position]];\n"
+"	float2 tCoord;\n"
+"} VertexOut;\n"
+"vertex VertexOut\n"
+"renderVertex(unsigned int vid [[ vertex_id ]])\n"
+"{\n"
+"	const VertexOut fixedV[] = {\n"
+"		{{ -1.0f, -1.0f, 0.0f, 1.0f }, { 0.0f, 1.0f }},\n"
+"		{{  1.0f, -1.0f, 0.0f, 1.0f }, { 1.0f, 1.0f }},\n"
+"		{{ -1.0f,  1.0f, 0.0f, 1.0f }, { 0.0f, 0.0f }},\n"
+"		{{  1.0f,  1.0f, 0.0f, 1.0f }, { 1.0f, 0.0f }},\n"
+"	};\n"
+"	return fixedV[vid];\n"
+"}\n"
+"fragment half4\n"
+"renderFragment(VertexOut in [[ stage_in ]],\n"
+"		texture2d<half> texture [[ texture(0) ]]) {\n"
+"	constexpr sampler s;\n"
+"	return texture.sample(s, in.tCoord);\n"
+"}";
+
+
+void
+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
+
+	setprocname(argv0);
+
+	@autoreleasepool{
+		[NSApplication sharedApplication];
+		[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
+		myApp = [AppDelegate new];
+		[NSApp setDelegate:myApp];
+		[NSApp run];
+	}
+}
+
+@implementation AppDelegate
+
++ (void)callservep9p:(id)arg
+{
+	servep9p();
+	[NSApp terminate:self];
+}
+
++ (void)makewin:(NSValue *)v
+{
+	NSRect r, sr;
+	Rectangle wr;
+	int set;
+	char *s;
+	id<MTLLibrary> library;
+	MTLRenderPipelineDescriptor *pipelineDesc;
+	NSError *error;
+
+	const NSWindowStyleMask Winstyle = NSWindowStyleMaskTitled
+		| NSWindowStyleMaskClosable
+		| NSWindowStyleMaskMiniaturizable
+		| NSWindowStyleMaskResizable;
+
+	sr = [[NSScreen mainScreen] frame];
+	r = [[NSScreen mainScreen] visibleFrame];
+
+	s = [v pointerValue];
+	LOG(@"makewin(%s)", s);
+	if(s && *s){
+		if(parsewinsize(s, &wr, &set) < 0)
+			sysfatal("%r");
+	}else{
+		wr = Rect(0, 0, sr.size.width*2/3, sr.size.height*2/3);
+		set = 0;
+	}
+
+	r.origin.x = wr.min.x;
+	r.origin.y = sr.size.height-wr.max.y;	/* winsize is top-left-based */
+	r.size.width = fmin(Dx(wr), r.size.width);
+	r.size.height = fmin(Dy(wr), r.size.height);
+	r = [NSWindow contentRectForFrameRect:r styleMask:Winstyle];
+
+	win = [[NSWindow alloc]
+		initWithContentRect:r
+		styleMask:Winstyle
+		backing:NSBackingStoreBuffered defer:NO];
+	[win setTitle:@"devdraw"];
+
+	if(!set)
+		[win center];
+	[win setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
+	[win setContentMinSize:NSMakeSize(64,64)];
+
+	[win setRestorable:NO];
+	[win setAcceptsMouseMovedEvents:YES];
+	[win setDelegate:myApp];
+
+	myContent = [DevDrawView new];
+	[win setContentView:myContent];
+	[myContent setWantsLayer:YES];
+	[myContent setLayerContentsRedrawPolicy:NSViewLayerContentsRedrawOnSetNeedsDisplay];
+
+	device = MTLCreateSystemDefaultDevice();
+	commandQueue = [device newCommandQueue];
+
+	layer = (DrawLayer *)[myContent layer];
+	layer.device = device;
+	layer.pixelFormat = MTLPixelFormatBGRA8Unorm;
+	layer.framebufferOnly = YES;
+	layer.opaque = YES;
+
+	renderPass = [MTLRenderPassDescriptor renderPassDescriptor];
+	renderPass.colorAttachments[0].loadAction = MTLLoadActionDontCare;
+	renderPass.colorAttachments[0].storeAction = MTLStoreActionDontCare;
+
+	library = [device newLibraryWithSource:metal options:nil error:&error];
+	if(!library)
+		sysfatal((char *)[[error localizedDescription] UTF8String]);
+
+	pipelineDesc = [MTLRenderPipelineDescriptor new];
+	pipelineDesc.alphaToOneEnabled = YES;
+	pipelineDesc.inputPrimitiveTopology = MTLPrimitiveTopologyClassTriangle;
+	pipelineDesc.rasterSampleCount = 1;
+	pipelineDesc.colorAttachments[0].pixelFormat = MTLPixelFormatBGRA8Unorm;
+	[pipelineDesc setVertexFunction: [library newFunctionWithName: @"renderVertex"]];
+	[pipelineDesc setFragmentFunction: [library newFunctionWithName: @"renderFragment"]];
+
+	pipelineState = [device newRenderPipelineStateWithDescriptor:pipelineDesc error:&error];
+	if(!pipelineState)
+		sysfatal((char *)[[error localizedDescription] UTF8String]);
+
+	[NSEvent setMouseCoalescingEnabled:NO];
+
+	topwin();
+}
+
++ (void)callkicklabel:(NSString *)s
+{
+	LOG(@"callkicklabel(%@)", s);
+	[win setTitle:s];
+	[[NSApp dockTile] setBadgeLabel:s];
+}
+
+
++ (void)callsetNeedsDisplayInRect:(NSValue *)v
+{
+	NSRect r;
+
+	r = [v rectValue];
+	LOG(@"callsetNeedsDisplayInRect(%g, %g, %g, %g)", r.origin.x, r.origin.y, r.size.width, r.size.height);
+	r = [win convertRectFromBacking:r];
+	LOG(@"setNeedsDisplayInRect(%g, %g, %g, %g)", r.origin.x, r.origin.y, r.size.width, r.size.height);
+	[layer setNeedsDisplayInRect:r];
+	[myContent enlargeLastInputRect:r];
+}
+
++ (void)callsetcursor:(NSValue *)v
+{
+	Cursor *c;
+	NSBitmapImageRep *r, *r2;
+	NSImage *i;
+	NSPoint p;
+	uint b, x, y, a;
+	uchar *plane[5], *plane2[5];
+	uchar pu, pb, pl, pr, pc;  // upper, bottom, left, right, center
+	uchar pul, pur, pbl, pbr;
+	uchar ful, fur, fbl, fbr;
+
+	c = [v pointerValue];
+	if(!c)
+		c = &bigarrow;
+
+	r = [[NSBitmapImageRep alloc]
+		initWithBitmapDataPlanes:nil
+		pixelsWide:16
+		pixelsHigh:16
+		bitsPerSample:1
+		samplesPerPixel:2
+		hasAlpha:YES
+		isPlanar:YES
+		colorSpaceName:NSDeviceWhiteColorSpace
+		bytesPerRow:2
+		bitsPerPixel:0];
+	[r getBitmapDataPlanes:plane];
+	for(b=0; b<2*16; b++){
+		plane[0][b] = ~c->set[b] & c->clr[b];
+		plane[1][b] = c->set[b] | c->clr[b];
+	}
+
+	r2 = [[NSBitmapImageRep alloc]
+		initWithBitmapDataPlanes:nil
+		pixelsWide:32
+		pixelsHigh:32
+		bitsPerSample:1
+		samplesPerPixel:2
+		hasAlpha:YES
+		isPlanar:YES
+		colorSpaceName:NSDeviceWhiteColorSpace
+		bytesPerRow:4
+		bitsPerPixel:0];
+	[r2 getBitmapDataPlanes:plane2];
+	// https://en.wikipedia.org/wiki/Pixel-art_scaling_algorithms#EPX/Scale2×/AdvMAME2×
+	for(a=0; a<2; a++){
+		for(y=0; y<16; y++){
+			for(x=0; x<2; x++){
+				pc = plane[a][x+2*y];
+				pu = y==0 ? pc : plane[a][x+2*(y-1)];
+				pb = y==15 ? pc : plane[a][x+2*(y+1)];
+				pl = (pc>>1) | (x==0 ? pc&0x80 : (plane[a][x-1+2*y]&1)<<7);
+				pr = (pc<<1) | (x==1 ? pc&1 : (plane[a][x+1+2*y]&0x80)>>7);
+				ful = ~(pl^pu) & (pl^pb) & (pu^pr);
+				pul = (ful & pu) | (~ful & pc);
+				fur = ~(pu^pr) & (pu^pl) & (pr^pb);
+				pur = (fur & pr) | (~fur & pc);
+				fbl = ~(pb^pl) & (pb^pr) & (pl^pu);
+				pbl = (fbl & pl) | (~fbl & pc);
+				fbr = ~(pr^pb) & (pr^pu) & (pb^pl);
+				pbr = (fbr & pb) | (~fbr & pc);
+				plane2[a][2*x+4*2*y] = (pul&0x80) | ((pul&0x40)>>1)  | ((pul&0x20)>>2) | ((pul&0x10)>>3)
+					| ((pur&0x80)>>1) | ((pur&0x40)>>2)  | ((pur&0x20)>>3) | ((pur&0x10)>>4);
+				plane2[a][2*x+1+4*2*y] = ((pul&0x8)<<4) | ((pul&0x4)<<3)  | ((pul&0x2)<<2) | ((pul&0x1)<<1)
+					| ((pur&0x8)<<3) | ((pur&0x4)<<2)  | ((pur&0x2)<<1) | (pur&0x1);
+				plane2[a][2*x+4*(2*y+1)] =  (pbl&0x80) | ((pbl&0x40)>>1)  | ((pbl&0x20)>>2) | ((pbl&0x10)>>3)
+					| ((pbr&0x80)>>1) | ((pbr&0x40)>>2)  | ((pbr&0x20)>>3) | ((pbr&0x10)>>4);
+				plane2[a][2*x+1+4*(2*y+1)] = ((pbl&0x8)<<4) | ((pbl&0x4)<<3)  | ((pbl&0x2)<<2) | ((pbl&0x1)<<1)
+					| ((pbr&0x8)<<3) | ((pbr&0x4)<<2)  | ((pbr&0x2)<<1) | (pbr&0x1);
+			}
+		}
+	}
+
+	// For checking out the cursor bitmap image
+/*
+	static BOOL saveimg = YES;
+	if(saveimg){
+		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;
+	}
+*/
+
+	i = [[NSImage alloc] initWithSize:NSMakeSize(16, 16)];
+	[i addRepresentation:r2];
+	[i addRepresentation:r];
+
+	p = NSMakePoint(-c->offset.x, -c->offset.y);
+	currentCursor = [[NSCursor alloc] initWithImage:i hotSpot:p];
+
+	[win invalidateCursorRectsForView:myContent];
+}
+
+- (void)applicationDidFinishLaunching:(id)arg
+{
+	NSMenu *m, *sm;
+	NSData *d;
+	NSImage *i;
+
+	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];
+
+	[NSThread
+		detachNewThreadSelector:@selector(callservep9p:)
+		toTarget:[self class] withObject:nil];
+}
+
+- (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
+
+@implementation DevDrawView
+{
+	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
+{
+	LOG(@"makeBackingLayer");
+	return [DrawLayer layer];
+}
+
+- (BOOL)wantsUpdateLayer
+{
+	return YES;
+}
+
+- (BOOL)isOpaque
+{
+	return YES;
+}
+
+- (BOOL)isFlipped
+{
+	return YES;
+}
+
+- (BOOL)acceptsFirstResponder
+{
+	return YES;
+}
+
+- (void)mouseMoved:(NSEvent*)e{ [self getmouse:e];}
+- (void)mouseDown:(NSEvent*)e{ [self getmouse:e];}
+- (void)mouseDragged:(NSEvent*)e{ [self getmouse:e];}
+- (void)mouseUp:(NSEvent*)e{ [self getmouse:e];}
+- (void)otherMouseDown:(NSEvent*)e{ [self getmouse:e];}
+- (void)otherMouseDragged:(NSEvent*)e{ [self getmouse:e];}
+- (void)otherMouseUp:(NSEvent*)e{ [self getmouse:e];}
+- (void)rightMouseDown:(NSEvent*)e{ [self getmouse:e];}
+- (void)rightMouseDragged:(NSEvent*)e{ [self getmouse:e];}
+- (void)rightMouseUp:(NSEvent*)e{ [self getmouse:e];}
+
+- (void)scrollWheel:(NSEvent*)e
+{
+	NSInteger s;
+
+	s = [e scrollingDeltaY];
+	if(s > 0)
+		[self sendmouse:8];
+	else if (s < 0)
+		[self sendmouse:16];
+}
+
+- (void)keyDown:(NSEvent*)e
+{
+	LOG(@"keyDown to interpret");
+
+	[self interpretKeyEvents:[NSArray arrayWithObject:e]];
+
+	[self resetLastInputRect];
+}
+
+- (void)flagsChanged:(NSEvent*)e
+{
+	static NSEventModifierFlags omod;
+	NSEventModifierFlags m;
+	uint b;
+
+	LOG(@"flagsChanged");
+	m = [e modifierFlags];
+
+	b = [NSEvent pressedMouseButtons];
+	b = (b&~6) | (b&4)>>1 | (b&2)<<1;
+	if(b){
+		if(m & ~omod & NSEventModifierFlagControl)
+			b |= 1;
+		if(m & ~omod & NSEventModifierFlagOption)
+			b |= 2;
+		if(m & ~omod & NSEventModifierFlagCommand)
+			b |= 4;
+		[self sendmouse:b];
+	}else if(m & ~omod & NSEventModifierFlagOption)
+		keystroke(Kalt);
+
+	omod = m;
+}
+
+- (void)magnifyWithEvent:(NSEvent*)e
+{
+	if(fabs([e magnification]) > 0.02)
+		[[self window] toggleFullScreen:nil];
+}
+
+- (void)touchesBeganWithEvent:(NSEvent*)e
+{
+	_tapping = YES;
+	_tapFingers = [e touchesMatchingPhase:NSTouchPhaseTouching inView:nil].count;
+	_tapTime = msec();
+}
+- (void)touchesMovedWithEvent:(NSEvent*)e
+{
+	_tapping = NO;
+}
+- (void)touchesEndedWithEvent:(NSEvent*)e
+{
+	if(_tapping
+		&& [e touchesMatchingPhase:NSTouchPhaseTouching inView:nil].count == 0
+		&& msec() - _tapTime < 250){
+		switch(_tapFingers){
+		case 3:
+			[self sendmouse:2];
+			[self sendmouse:0];
+			break;
+		case 4:
+			[self sendmouse:2];
+			[self sendmouse:1];
+			[self sendmouse:0];
+			break;
+		}
+		_tapping = NO;
+	}
+}
+- (void)touchesCancelledWithEvent:(NSEvent*)e
+{
+	_tapping = NO;
+}
+
+- (void)getmouse:(NSEvent *)e
+{
+	NSUInteger b;
+	NSEventModifierFlags m;
+
+	b = [NSEvent pressedMouseButtons];
+	b = b&~6 | (b&4)>>1 | (b&2)<<1;
+	b = mouseswap(b);
+
+	if(b == 1){
+		m = [e modifierFlags];
+		if(m & NSEventModifierFlagOption){
+			abortcompose();
+			b = 2;
+		}else
+		if(m & NSEventModifierFlagCommand)
+			b = 4;
+	}
+	[self sendmouse:b];
+}
+
+- (void)sendmouse:(NSUInteger)b
+{
+	NSPoint p;
+
+	p = [self.window convertPointToBacking:
+		[self.window mouseLocationOutsideOfEventStream]];
+	p.y = Dy(mouserect) - p.y;
+	// LOG(@"(%g, %g) <- sendmouse(%d)", p.x, p.y, (uint)b);
+	mousetrack(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:currentCursor];
+}
+
+- (void)viewDidEndLiveResize
+{
+	[super viewDidEndLiveResize];
+	resizeimg();
+}
+
+- (void)viewDidChangeBackingProperties
+{
+	[super viewDidChangeBackingProperties];
+	resizeimg();
+}
+
+// conforms to protocol NSTextInputClient
+- (BOOL)hasMarkedText
+{
+	LOG(@"hasMarkedText");
+	return _markedRange.location != NSNotFound;
+}
+- (NSRange)markedRange
+{
+	LOG(@"markedRange");
+	return _markedRange;
+}
+- (NSRange)selectedRange
+{
+	LOG(@"selectedRange");
+	return _selectedRange;
+}
+- (void)setMarkedText:(id)string
+	selectedRange:(NSRange)sRange
+	replacementRange:(NSRange)rRange
+{
+	NSString *str;
+
+	LOG(@"setMarkedText: %@ (%ld, %ld) (%ld, %ld)", string,
+		sRange.location, sRange.length,
+		rRange.location, rRange.length);
+
+	[self clearInput];
+
+	if([string isKindOfClass:[NSAttributedString class]])
+		str = [string string];
+	else
+		str = string;
+
+	if(rRange.location == NSNotFound){
+		if(_markedRange.location != NSNotFound){
+			rRange = _markedRange;
+		}else{
+			rRange = _selectedRange;
+		}
+	}
+
+	if(str.length == 0){
+		[_tmpText deleteCharactersInRange:rRange];
+		[self unmarkText];
+	}else{
+		_markedRange = NSMakeRange(rRange.location, str.length);
+		[_tmpText replaceCharactersInRange:rRange withString:str];
+	}
+	_selectedRange.location = rRange.location + sRange.location;
+	_selectedRange.length = sRange.length;
+
+	if(_tmpText.length){
+		uint i;
+		LOG(@"text length %ld", _tmpText.length);
+		for(i = 0; i <= _tmpText.length; ++i){
+			if(i == _markedRange.location)
+				keystroke('[');
+			if(_selectedRange.length){
+				if(i == _selectedRange.location)
+					keystroke('{');
+				if(i == NSMaxRange(_selectedRange))
+					keystroke('}');
+				}
+			if(i == NSMaxRange(_markedRange))
+				keystroke(']');
+			if(i < _tmpText.length)
+				keystroke([_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(Kleft);
+	}
+
+	LOG(@"text: \"%@\"  (%ld,%ld)  (%ld,%ld)", _tmpText,
+		_markedRange.location, _markedRange.length,
+		_selectedRange.location, _selectedRange.length);
+}
+- (void)unmarkText
+{
+	//NSUInteger i;
+	NSUInteger len;
+
+	LOG(@"unmarkText");
+	len = [_tmpText length];
+	//for(i = 0; i < len; ++i)
+	//	keystroke([_tmpText characterAtIndex:i]);
+	[_tmpText deleteCharactersInRange:NSMakeRange(0, len)];
+	_markedRange = NSMakeRange(NSNotFound, 0);
+	_selectedRange = NSMakeRange(0, 0);
+}
+- (NSArray<NSAttributedStringKey> *)validAttributesForMarkedText
+{
+	LOG(@"validAttributesForMarkedText");
+	return @[];
+}
+- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)r
+	actualRange:(NSRangePointer)actualRange
+{
+	NSRange sr;
+	NSAttributedString *s;
+
+	LOG(@"attributedSubstringForProposedRange: (%ld, %ld) (%ld, %ld)",
+		r.location, r.length, actualRange->location, actualRange->length);
+	sr = NSMakeRange(0, [_tmpText length]);
+	sr = NSIntersectionRange(sr, r);
+	if(actualRange)
+		*actualRange = sr;
+	LOG(@"use range: %ld, %ld", sr.location, sr.length);
+	if(sr.length)
+		s = [[NSAttributedString alloc]
+			initWithString:[_tmpText substringWithRange:sr]];
+	LOG(@"	return %@", s);
+	return s;
+}
+- (void)insertText:(id)s
+	replacementRange:(NSRange)r
+{
+	NSUInteger i;
+	NSUInteger len;
+
+	LOG(@"insertText: %@ replacementRange: %ld, %ld", s, r.location, r.length);
+
+	[self clearInput];
+
+	len = [s length];
+	for(i = 0; i < len; ++i)
+		keystroke([s characterAtIndex:i]);
+	[_tmpText deleteCharactersInRange:NSMakeRange(0, _tmpText.length)];
+	_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
+{
+	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
+{
+	NSEvent *e;
+	NSEventModifierFlags m;
+	uint c, k;
+
+	LOG(@"doCommandBySelector (%@)", NSStringFromSelector(s));
+
+	e = [NSApp currentEvent];
+	c = [[e characters] characterAtIndex:0];
+	k = keycvt(c);
+	LOG(@"keyDown: character0: 0x%x -> 0x%x", c, k);
+	m = [e modifierFlags];
+
+	if(m & NSEventModifierFlagCommand){
+		if((m & NSEventModifierFlagShift) && 'a' <= k && k <= 'z')
+			k += 'A' - 'a';
+		if(' '<=k && k<='~')
+			k += Kcmd;
+	}
+	if(k>0)
+		keystroke(k);
+}
+
+// Helper for managing input rect approximately
+- (void)resetLastInputRect
+{
+	LOG(@"resetLastInputRect");
+	_lastInputRect.origin.x = 0.0;
+	_lastInputRect.origin.y = 0.0;
+	_lastInputRect.size.width = 0.0;
+	_lastInputRect.size.height = 0.0;
+}
+
+- (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)",
+		_lastInputRect.origin.x, _lastInputRect.origin.y,
+		_lastInputRect.size.width, _lastInputRect.size.height);
+}
+
+- (void)clearInput
+{
+	if(_tmpText.length){
+		uint i;
+		int l;
+		l = 1 + _tmpText.length - NSMaxRange(_selectedRange)
+			+ (_selectedRange.length > 0);
+		LOG(@"move right %d", l);
+		for(i = 0; i < l; ++i)
+			keystroke(Kright);
+		l = _tmpText.length+2+2*(_selectedRange.length > 0);
+		LOG(@"backspace %d", l);
+		for(uint i = 0; i < l; ++i)
+			keystroke(Kbs);
+	}
+}
+
+@end
+
+@implementation DrawLayer
+
+- (void)display
+{
+	id<MTLCommandBuffer> cbuf;
+	id<MTLRenderCommandEncoder> cmd;
+
+	LOG(@"display");
+
+	cbuf = [commandQueue commandBuffer];
+
+	LOG(@"display query drawable");
+
+@autoreleasepool{
+	id<CAMetalDrawable> drawable;
+
+	drawable = [layer nextDrawable];
+	if(!drawable){
+		LOG(@"display couldn't get drawable");
+		[self setNeedsDisplay];
+		return;
+	}
+
+	LOG(@"display got drawable");
+
+	renderPass.colorAttachments[0].texture = drawable.texture;
+
+	cmd = [cbuf renderCommandEncoderWithDescriptor:renderPass];
+	[cmd setRenderPipelineState:pipelineState];
+	[cmd setFragmentTexture:texture atIndex:0];
+	[cmd drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
+	[cmd 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
+msec(void)
+{
+	return nsec()/1000000;
+}
+
+static uint
+keycvt(uint code)
+{
+	switch(code){
+	case '\r': return '\n';
+	case '\b': return 127;
+	case 127: return '\b';
+	case NSUpArrowFunctionKey: return Kup;
+	case NSDownArrowFunctionKey: return Kdown;
+	case NSLeftArrowFunctionKey: return Kleft;
+	case NSRightArrowFunctionKey: return Kright;
+	case NSInsertFunctionKey: return Kins;
+	case NSDeleteFunctionKey: return Kdel;
+	case NSHomeFunctionKey: return Khome;
+	case NSEndFunctionKey: return Kend;
+	case NSPageUpFunctionKey: return Kpgup;
+	case NSPageDownFunctionKey: return Kpgdown;
+	case NSF1FunctionKey: return KF|1;
+	case NSF2FunctionKey: return KF|2;
+	case NSF3FunctionKey: return KF|3;
+	case NSF4FunctionKey: return KF|4;
+	case NSF5FunctionKey: return KF|5;
+	case NSF6FunctionKey: return KF|6;
+	case NSF7FunctionKey: return KF|7;
+	case NSF8FunctionKey: return KF|8;
+	case NSF9FunctionKey: return KF|9;
+	case NSF10FunctionKey: return KF|10;
+	case NSF11FunctionKey: return KF|11;
+	case NSF12FunctionKey: return KF|12;
+	case NSBeginFunctionKey:
+	case NSPrintScreenFunctionKey:
+	case NSScrollLockFunctionKey:
+	case NSF13FunctionKey:
+	case NSF14FunctionKey:
+	case NSF15FunctionKey:
+	case NSF16FunctionKey:
+	case NSF17FunctionKey:
+	case NSF18FunctionKey:
+	case NSF19FunctionKey:
+	case NSF20FunctionKey:
+	case NSF21FunctionKey:
+	case NSF22FunctionKey:
+	case NSF23FunctionKey:
+	case NSF24FunctionKey:
+	case NSF25FunctionKey:
+	case NSF26FunctionKey:
+	case NSF27FunctionKey:
+	case NSF28FunctionKey:
+	case NSF29FunctionKey:
+	case NSF30FunctionKey:
+	case NSF31FunctionKey:
+	case NSF32FunctionKey:
+	case NSF33FunctionKey:
+	case NSF34FunctionKey:
+	case NSF35FunctionKey:
+	case NSPauseFunctionKey:
+	case NSSysReqFunctionKey:
+	case NSBreakFunctionKey:
+	case NSResetFunctionKey:
+	case NSStopFunctionKey:
+	case NSMenuFunctionKey:
+	case NSUserFunctionKey:
+	case NSSystemFunctionKey:
+	case NSPrintFunctionKey:
+	case NSClearLineFunctionKey:
+	case NSClearDisplayFunctionKey:
+	case NSInsertLineFunctionKey:
+	case NSDeleteLineFunctionKey:
+	case NSInsertCharFunctionKey:
+	case NSDeleteCharFunctionKey:
+	case NSPrevFunctionKey:
+	case NSNextFunctionKey:
+	case NSSelectFunctionKey:
+	case NSExecuteFunctionKey:
+	case NSUndoFunctionKey:
+	case NSRedoFunctionKey:
+	case NSFindFunctionKey:
+	case NSHelpFunctionKey:
+	case NSModeSwitchFunctionKey: return 0;
+	default: return code;
+	}
+}
+
+Memimage*
+attachscreen(char *label, char *winsize)
+{
+	LOG(@"attachscreen(%s, %s)", label, winsize);
+	[AppDelegate
+		performSelectorOnMainThread:@selector(makewin:)
+		withObject:[NSValue valueWithPointer:winsize]
+		waitUntilDone:YES];
+	kicklabel(label);
+	setcursor(nil);
+	mouseresized = 0;
+	return initimg();
+}
+
+static Memimage*
+initimg(void)
+{
+@autoreleasepool{
+	CGFloat scale;
+	NSSize size;
+	MTLTextureDescriptor *textureDesc;
+
+	size = [myContent convertSizeToBacking:[myContent bounds].size];
+	mouserect = Rect(0, 0, size.width, size.height);
+
+	LOG(@"initimg %.0f %.0f", size.width, size.height);
+
+	img = allocmemimage(mouserect, XRGB32);
+	if(img == nil)
+		panic("allocmemimage: %r");
+	if(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 = [win backingScaleFactor];
+	[layer setDrawableSize:size];
+	[layer 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
+	displaydpi = scale * 110;
+}
+	LOG(@"initimg return");
+
+	return img;
+}
+
+void
+_flushmemscreen(Rectangle r)
+{
+	LOG(@"_flushmemscreen(%d,%d,%d,%d)", r.min.x, r.min.y, Dx(r), Dy(r));
+
+	@autoreleasepool{
+		[texture
+			replaceRegion:MTLRegionMake2D(r.min.x, r.min.y, Dx(r), Dy(r))
+			mipmapLevel:0
+			withBytes:byteaddr(img, Pt(r.min.x, r.min.y))
+			bytesPerRow: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)
+{
+	@autoreleasepool{
+		NSPoint q;
+
+		LOG(@"setmouse(%d,%d)", p.x, p.y);
+		q = [win convertPointFromBacking:NSMakePoint(p.x, p.y)];
+		LOG(@"(%g, %g) <- fromBacking", q.x, q.y);
+		q = [myContent convertPoint:q toView:nil];
+		LOG(@"(%g, %g) <- toWindow", q.x, q.y);
+		q = [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);
+	}
+}
+
+char*
+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;
+	}
+}
+
+void
+putsnarf(char *s)
+{
+	NSArray *t;
+	NSPasteboard *pb;
+	NSString *str;
+
+	if(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);
+	}
+}
+
+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)
+{
+	[AppDelegate
+		performSelectorOnMainThread:@selector(callsetcursor:)
+		withObject:[NSValue valueWithPointer:c]
+		waitUntilDone:YES];
+}
+
+void
+topwin(void)
+{
+	[win
+		performSelectorOnMainThread:
+		@selector(makeKeyAndOrderFront:)
+		withObject:nil
+		waitUntilDone:YES];
+
+	[NSApp activateIgnoringOtherApps:YES];
+}
+
+void
+resizeimg(void)
+{
+	zlock();
+	_drawreplacescreenimage(initimg());
+
+	mouseresized = 1;
+	zunlock();
+	[myContent sendmouse:0];
+}
+
+void
+resizewindow(Rectangle r)
+{
+	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 = [myContent convertSizeFromBacking:NSMakeSize(Dx(r), Dy(r))];
+		[win setContentSize:s];
+		resizeimg();
+	});
+}
+
+static void
+setprocname(const char *s)
+{
+  CFStringRef process_name;
+
+  process_name = CFStringCreateWithBytes(nil, (uchar*)s, strlen(s), kCFStringEncodingUTF8, false);
+
+  // Adapted from Chrome's mac_util.mm.
+  // http://src.chromium.org/viewvc/chrome/trunk/src/base/mac/mac_util.mm
+  //
+  // Copyright (c) 2012 The Chromium Authors. All rights reserved.
+  //
+  // Redistribution and use in source and binary forms, with or without
+  // modification, are permitted provided that the following conditions are
+  // met:
+  //
+  //    * Redistributions of source code must retain the above copyright
+  // notice, this list of conditions and the following disclaimer.
+  //    * Redistributions in binary form must reproduce the above
+  // copyright notice, this list of conditions and the following disclaimer
+  // in the documentation and/or other materials provided with the
+  // distribution.
+  //    * Neither the name of Google Inc. nor the names of its
+  // contributors may be used to endorse or promote products derived from
+  // this software without specific prior written permission.
+  //
+  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+  // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+  // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+  // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+  // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+  // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+  // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+  // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+  // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+  // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+  // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+  // Warning: here be dragons! This is SPI reverse-engineered from WebKit's
+  // plugin host, and could break at any time (although realistically it's only
+  // likely to break in a new major release).
+  // When 10.7 is available, check that this still works, and update this
+  // comment for 10.8.
+
+  // Private CFType used in these LaunchServices calls.
+  typedef CFTypeRef PrivateLSASN;
+  typedef PrivateLSASN (*LSGetCurrentApplicationASNType)();
+  typedef OSStatus (*LSSetApplicationInformationItemType)(int, PrivateLSASN,
+                                                          CFStringRef,
+                                                          CFStringRef,
+                                                          CFDictionaryRef*);
+
+  static LSGetCurrentApplicationASNType ls_get_current_application_asn_func =
+      NULL;
+  static LSSetApplicationInformationItemType
+      ls_set_application_information_item_func = NULL;
+  static CFStringRef ls_display_name_key = NULL;
+
+  static bool did_symbol_lookup = false;
+  if (!did_symbol_lookup) {
+    did_symbol_lookup = true;
+    CFBundleRef launch_services_bundle =
+        CFBundleGetBundleWithIdentifier(CFSTR("com.apple.LaunchServices"));
+    if (!launch_services_bundle) {
+      fprint(2, "Failed to look up LaunchServices bundle\n");
+      return;
+    }
+
+    ls_get_current_application_asn_func =
+        (LSGetCurrentApplicationASNType)(
+            CFBundleGetFunctionPointerForName(
+                launch_services_bundle, CFSTR("_LSGetCurrentApplicationASN")));
+    if (!ls_get_current_application_asn_func)
+      fprint(2, "Could not find _LSGetCurrentApplicationASN\n");
+
+    ls_set_application_information_item_func =
+        (LSSetApplicationInformationItemType)(
+            CFBundleGetFunctionPointerForName(
+                launch_services_bundle,
+                CFSTR("_LSSetApplicationInformationItem")));
+    if (!ls_set_application_information_item_func)
+      fprint(2, "Could not find _LSSetApplicationInformationItem\n");
+
+    CFStringRef* key_pointer = (CFStringRef*)(
+        CFBundleGetDataPointerForName(launch_services_bundle,
+                                      CFSTR("_kLSDisplayNameKey")));
+    ls_display_name_key = key_pointer ? *key_pointer : NULL;
+    if (!ls_display_name_key)
+      fprint(2, "Could not find _kLSDisplayNameKey\n");
+
+    // Internally, this call relies on the Mach ports that are started up by the
+    // Carbon Process Manager.  In debug builds this usually happens due to how
+    // the logging layers are started up; but in release, it isn't started in as
+    // much of a defined order.  So if the symbols had to be loaded, go ahead
+    // and force a call to make sure the manager has been initialized and hence
+    // the ports are opened.
+    ProcessSerialNumber psn;
+    GetCurrentProcess(&psn);
+  }
+  if (!ls_get_current_application_asn_func ||
+      !ls_set_application_information_item_func ||
+      !ls_display_name_key) {
+    return;
+  }
+
+  PrivateLSASN asn = ls_get_current_application_asn_func();
+  // Constant used by WebKit; what exactly it means is unknown.
+  const int magic_session_constant = -2;
+  OSErr err =
+      ls_set_application_information_item_func(magic_session_constant, asn,
+                                               ls_display_name_key,
+                                               process_name,
+                                               NULL /* optional out param */);
+  if(err != noErr)
+    fprint(2, "Call to set process name failed\n");
+}
blob - 197fd512a1aadbd300bf9e6074afb80dd522f9d3
blob + 6f9449adcf15172d4532c3818caf595051d59b77
--- src/cmd/devdraw/cocoa-srv.c
+++ src/cmd/devdraw/cocoa-srv.c
@@ -21,7 +21,7 @@ typedef struct Tagbuf Tagbuf;
 
 struct Kbdbuf
 {
-	Rune r[32];
+	Rune r[256];
 	int ri;
 	int wi;
 	int stall;
@@ -29,7 +29,7 @@ struct Kbdbuf
 
 struct Mousebuf
 {
-	Mouse m[32];
+	Mouse m[256];
 	Mouse last;
 	int ri;
 	int wi;
@@ -38,7 +38,7 @@ struct Mousebuf
 
 struct Tagbuf
 {
-	int t[32];
+	int t[256];
 	int ri;
 	int wi;
 };
@@ -97,7 +97,7 @@ servep9p(void)
 		/* pick off messages one by one */
 		if(convM2W(mbuf, nn+4, &m) <= 0)
 			sysfatal("cannot convert message");
-		if(trace) fprint(2, "<- %W\n", &m);
+		if(trace) fprint(2, "%ud [%d] <- %W\n", nsec()/1000000, threadid(), &m);
 		runmsg(&m);
 	}
 }
@@ -191,6 +191,7 @@ runmsg(Wsysmsg *m)
 		break;
 
 	case Trddraw:
+		zlock();
 		n = m->count;
 		if(n > sizeof buf)
 			n = sizeof buf;
@@ -202,13 +203,16 @@ runmsg(Wsysmsg *m)
 			m->data = buf;
 			replymsg(m);
 		}
+		zunlock();
 		break;
 
 	case Twrdraw:
+		zlock();
 		if(_drawmsgwrite(m->data, m->count) < 0)
 			replyerror(m);
 		else
 			replymsg(m);
+		zunlock();
 		break;
 	
 	case Ttop:
@@ -217,7 +221,9 @@ runmsg(Wsysmsg *m)
 		break;
 	
 	case Tresize:
-	//	_xresizewindow(m->rect);
+#if OSX_VERSION >= 101400
+		resizewindow(m->rect);
+#endif
 		replymsg(m);
 		break;
 	}
@@ -238,7 +244,7 @@ replymsg(Wsysmsg *m)
 	if(m->type%2 == 0)
 		m->type++;
 		
-	if(trace) fprint(2, "-> %W\n", m);
+	if(trace) fprint(2, "%ud [%d] -> %W\n", nsec()/1000000, threadid(), m);
 	/* copy to output buffer */
 	n = sizeW2M(m);
 
@@ -293,11 +299,11 @@ matchmouse(void)
 			mousetags.ri = 0;
 		m.mouse = mouse.m[mouse.ri];
 		m.resized = mouseresized;
+		mouseresized = 0;
 		/*
 		if(m.resized)
 			fprint(2, "sending resize\n");
 		*/
-		mouseresized = 0;
 		mouse.ri++;
 		if(mouse.ri == nelem(mouse.m))
 			mouse.ri = 0;
@@ -367,8 +373,6 @@ abortcompose(void)
 		keystroke(Kalt);
 }
 
-void resizeimg(void);
-
 void
 keystroke(int c)
 {
blob - c9b280f7c7420019f924f3f2befc696daf26dbc1
blob + 92b92d2cf855ea4625d82bd89f297a8cf82d7449
--- src/cmd/devdraw/cocoa-thread.c
+++ src/cmd/devdraw/cocoa-thread.c
@@ -25,4 +25,11 @@ qunlock(QLock *q)
 {
 	pthread_mutex_unlock(&q->m);
 }
+
+int
+threadid(void)
+{
+	return pthread_mach_thread_np(pthread_self());
+}
+
 #endif
blob - c2f3e9821a27f34aff1a97ae9aa3422d506551d1
blob + d5793f0a70a3e9cefd6c220217aaf53792945130
--- src/cmd/devdraw/cocoa-thread.h
+++ src/cmd/devdraw/cocoa-thread.h
@@ -30,4 +30,5 @@
 
 	void	qlock(QLock*);
 	void	qunlock(QLock*);
+	int threadid(void);
 #endif
blob - cad244ac5e6fc18e445ac1ef8bf49ea4220672aa
blob + 2e40087e0a3265892e530b69ccc0cfbbaf6a8efc
--- src/cmd/devdraw/mkfile
+++ src/cmd/devdraw/mkfile
@@ -35,6 +35,9 @@ latin1.h: $PLAN9/lib/keyboard $O.mklatinkbd
 $O.macargv: $MACARGV
 	$LD -o $target $prereq
 
+cocoa-screen-metal-objc.$O: cocoa-screen-metal.m
+	$CC $CFLAGS $OBJCFLAGS -o $target cocoa-screen-metal.m
+
 %-objc.$O: %.m
 	$CC $CFLAGS -o $target $stem.m
 
blob - 9c422261af513db67a1765bf5eeeebd448b6df6d
blob + 98d6799d9a1a6e463774a3542b7adc568791eb33
--- src/cmd/devdraw/mkwsysrules.sh
+++ src/cmd/devdraw/mkwsysrules.sh
@@ -60,7 +60,12 @@ elif [ $WSYSTYPE = osx ]; then
 	echo 'WSYSOFILES=$WSYSOFILES osx-screen-carbon-objc.o osx-draw.o osx-srv.o'
 	echo 'MACARGV=macargv.o'
 elif [ $WSYSTYPE = osx-cocoa ]; then
-	echo 'WSYSOFILES=$WSYSOFILES osx-draw.o cocoa-screen-objc.o cocoa-srv.o cocoa-thread.o'
+	if sw_vers|awk '/ProductVersion/{split($2,a,".");exit(a[2]<14)}' >/dev/null; then	# 0 is true in sh.
+		echo 'OBJCFLAGS=$OBJCFLAGS -fobjc-arc'
+		echo 'WSYSOFILES=$WSYSOFILES osx-draw.o cocoa-screen-metal-objc.o cocoa-srv.o cocoa-thread.o'
+	else
+		echo 'WSYSOFILES=$WSYSOFILES osx-draw.o cocoa-screen-objc.o cocoa-srv.o cocoa-thread.o'
+	fi
 	echo 'MACARGV=macargv-objc.o'
 elif [ $WSYSTYPE = nowsys ]; then
 	echo 'WSYSOFILES=nowsys.o'