URI: 
       tdevdraw: refactor, clean up mac screen - plan9port - [fork] Plan 9 from user space
  HTML git clone git://src.adamsgaard.dk/plan9port
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
   DIR commit b1a086dee9bf5846b31323ba2c438f8853a9c87f
   DIR parent 843e5af1986a2e8f1c4f6177dc4509501711a22c
  HTML Author: Russ Cox <rsc@swtch.com>
       Date:   Thu,  9 Jan 2020 21:47:14 -0500
       
       devdraw: refactor, clean up mac screen
       
       Also turn mac-srv.c into a generic srv.c,
       so we can remove the duplication with x11-srv.c.
       
       Diffstat:
         M src/cmd/devdraw/devdraw.c           |       8 ++++----
         M src/cmd/devdraw/devdraw.h           |      22 ++++++++++++++++++++--
         D src/cmd/devdraw/mac-screen.h        |      19 -------------------
         M src/cmd/devdraw/mac-screen.m        |     827 +++++++++++++++----------------
         D src/cmd/devdraw/mac-srv.c           |     378 -------------------------------
         M src/cmd/devdraw/mkfile              |       3 ++-
         M src/cmd/devdraw/mkwsysrules.sh      |       4 ++--
         A src/cmd/devdraw/srv.c               |     402 ++++++++++++++++++++++++++++++
       
       8 files changed, 825 insertions(+), 838 deletions(-)
       ---
   DIR diff --git a/src/cmd/devdraw/devdraw.c b/src/cmd/devdraw/devdraw.c
       t@@ -14,8 +14,6 @@
        #include <drawfcall.h>
        #include "devdraw.h"
        
       -extern void _flushmemscreen(Rectangle);
       -
        static        Draw                sdraw;
        Client                *client0;
        static        int                drawuninstall(Client*, int);
       t@@ -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)
        {
       t@@ -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;
        }
       t@@ -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);
        }
        
   DIR diff --git a/src/cmd/devdraw/devdraw.h b/src/cmd/devdraw/devdraw.h
       t@@ -28,6 +28,8 @@ struct Kbdbuf
                int wi;
                int stall;
                int alting;
       +        Rune k[10];
       +        int nk;
        };
        
        struct Mousebuf
       t@@ -75,7 +77,7 @@ struct Client
        
                int                rfd;
                int                wfd;
       -        void*                view;
       +        const void*                view;
                
                QLock inputlk;
                Kbdbuf kbd;
       t@@ -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*);
   DIR diff --git a/src/cmd/devdraw/mac-screen.h b/src/cmd/devdraw/mac-screen.h
       t@@ -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);
   DIR diff --git a/src/cmd/devdraw/mac-screen.m b/src/cmd/devdraw/mac-screen.m
       t@@ -21,7 +21,6 @@
        #include <keyboard.h>
        #include <drawfcall.h>
        #include "devdraw.h"
       -#include "mac-screen.h"
        #include "bigarrow.h"
        #include "glendapng.h"
        
       t@@ -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)
       t@@ -43,34 +41,14 @@ usage(void)
                threadexitsall("usage");
        }
        
       -@interface DrawLayer : CAMetalLayer
       -@end
       -@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;
       +@class DrawView;
       +@class DrawLayer;
        
       -- (void)clearInput;
       -- (void)getmouse:(NSEvent *)e;
       -- (void)sendmouse:(NSUInteger)b;
       -- (void)resetLastInputRect;
       -- (void)enlargeLastInputRect:(NSRect)r;
       +@interface AppDelegate : NSObject<NSApplicationDelegate>
        @end
        
        static AppDelegate *myApp = NULL;
        
       -static id<MTLDevice> device;
       -static id<MTLCommandQueue> commandQueue;
       -static id<MTLTexture> texture;
        
        static QLock snarfl;
        
       t@@ -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);
        
       t@@ -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;
       t@@ -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)
       t@@ -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]) {
       t@@ -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];
       t@@ -218,64 +344,76 @@ 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;
       -
       -        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];
       -
       -        time = dispatch_time(DISPATCH_TIME_NOW, 16 * NSEC_PER_MSEC);
       -        dispatch_after(time, dispatch_get_main_queue(), ^(void){
       -                [view.dlayer setNeedsDisplayInRect:r];
       +        DrawView *view = (__bridge DrawView*)client->view;
       +        dispatch_sync(dispatch_get_main_queue(), ^(void){
       +                [view setlabel:label];
                });
       -
       -        [view enlargeLastInputRect:r];
        }
        
       -typedef struct Cursors Cursors;
       -struct Cursors {
       -        Cursor *c;
       -        Cursor2 *c2;
       -};
       +- (void)setlabel:(char*)label {
       +        LOG(@"setlabel(%s)", label);
       +        if(label == nil)
       +                return;
       +
       +        @autoreleasepool{
       +                NSString *s = [[NSString alloc] initWithUTF8String:label];
       +                [self.win setTitle:s];
       +                [[NSApp dockTile] setBadgeLabel:s]; // TODO: Not with multiple windows
       +        }
       +}
        
       -+ (void)callsetcursor:(NSValue *)v
       +// 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)
        {
       -        Cursors *cs;
       -        Cursor *c;
       -        Cursor2 *c2;
       +        DrawView *view = (__bridge DrawView*)client->view;
       +        dispatch_sync(dispatch_get_main_queue(), ^(void){
       +                [view setcursor:c cursor2:c2];
       +        });
       +}
       +
       +- (void)setcursor:(Cursor*)c cursor2:(Cursor2*)c2 {
       +        if(!c) {
       +                c = &bigarrow;
       +                c2 = &bigarrow2;
       +        }
       +
                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
       t@@ -311,125 +449,164 @@ 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;
       +        }
       +
       +        @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];
                }
        }
        
       -- (void)windowDidBecomeKey:(id)arg
       +// 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)
        {
       -        [self sendmouse:0];
       +        DrawView *view = (__bridge DrawView*)c->view;
       +        dispatch_sync(dispatch_get_main_queue(), ^(void){
       +                [view resizeimg];
       +        });
        }
        
       -- (CALayer *)makeBackingLayer
       -{
       -        LOG(@"makeBackingLayer");
       -        return [DrawLayer layer];
       +- (void)resizeimg {
       +        [self initimg];
       +        _drawreplacescreenimage(self.client, self.img);
       +        [self sendmouse:0];
        }
        
       -- (BOOL)wantsUpdateLayer
       +- (void)windowDidResize:(NSNotification *)notification {
       +        if(![self inLiveResize] && self.img) {
       +                [self resizeimg];
       +        }
       +}
       +- (void)viewDidEndLiveResize
        {
       -        return YES;
       +        [super viewDidEndLiveResize];
       +        if(self.img)
       +                [self resizeimg];
        }
        
       -- (BOOL)isOpaque
       +- (void)viewDidChangeBackingProperties
        {
       -        return YES;
       +        [super viewDidChangeBackingProperties];
       +        if(self.img)
       +                [self resizeimg];
        }
        
       -- (BOOL)isFlipped
       +// 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];
       +        });
        }
        
       -- (BOOL)acceptsFirstResponder
       -{
       -        return YES;
       +
       +- (void)windowDidBecomeKey:(id)arg {
       +        [self sendmouse:0];
        }
        
        - (void)mouseMoved:(NSEvent*)e{ [self getmouse:e];}
       t@@ -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;
        }
       t@@ -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)
       t@@ -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
       t@@ -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,
       t@@ -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);
       t@@ -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);
       t@@ -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
       t@@ -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);
       -        }
       -}
       -
       -@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;
       +                        gfx_keystroke(self.client, Kbs);
                }
       -
       -        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
       t@@ -935,122 +1079,9 @@ keycvt(uint 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;
       t@@ -1069,8 +1100,9 @@ getsnarf(void)
                }
        }
        
       +// TODO
        void
       -putsnarf(char *s)
       +rpc_putsnarf(char *s)
        {
                NSArray *t;
                NSPasteboard *pb;
       t@@ -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)
        {
   DIR diff --git a/src/cmd/devdraw/mac-srv.c b/src/cmd/devdraw/mac-srv.c
       t@@ -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;
       -}
   DIR diff --git a/src/cmd/devdraw/mkfile b/src/cmd/devdraw/mkfile
       t@@ -9,6 +9,7 @@ WSYSOFILES=\
                devdraw.$O\
                latin1.$O\
                mouseswap.$O\
       +        srv.$O\
                winsize.$O\
        
        <|sh ./mkwsysrules.sh
       t@@ -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
        
   DIR diff --git a/src/cmd/devdraw/mkwsysrules.sh b/src/cmd/devdraw/mkwsysrules.sh
       t@@ -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'
   DIR diff --git a/src/cmd/devdraw/srv.c b/src/cmd/devdraw/srv.c
       t@@ -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;
       +}