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