tdevdraw: draft cocoa support - plan9port - [fork] Plan 9 from user space
 (HTM) git clone git://src.adamsgaard.dk/plan9port
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit a287dbab235c9041a32300a9e0bb60ef41864963
 (DIR) parent f0a4e8bd6cfb318e374e57f34b5676c6890fb1a2
 (HTM) Author: David Jeannot <djeannot24@gmail.com>
       Date:   Tue,  6 Sep 2011 10:10:43 -0400
       
       devdraw: draft cocoa support
       
       R=rsc
       CC=plan9port.codebot
       http://codereview.appspot.com/4974060
       
       Diffstat:
         M CONTRIBUTORS                        |       1 +
         M src/cmd/acme/text.c                 |       4 ++++
         A src/cmd/devdraw/cocoa-screen.h      |      19 +++++++++++++++++++
         A src/cmd/devdraw/cocoa-screen.m      |     835 +++++++++++++++++++++++++++++++
         A src/cmd/devdraw/cocoa-srv.c         |     397 +++++++++++++++++++++++++++++++
         A src/cmd/devdraw/cocoa-thread.c      |      54 +++++++++++++++++++++++++++++++
         A src/cmd/devdraw/cocoa-thread.h      |      27 +++++++++++++++++++++++++++
         M src/cmd/devdraw/mkfile              |       3 +++
       
       8 files changed, 1340 insertions(+), 0 deletions(-)
       ---
 (DIR) diff --git a/CONTRIBUTORS b/CONTRIBUTORS
       t@@ -10,6 +10,7 @@ Anthony Sorace <a@9srv.net>
        Arvindh Rajesh Tamilmani <art@a-30.net>
        Benjamin Huntsman <BHuntsman@mail2.cu-portland.edu>
        David du Colombier <0intro@gmail.com>
       +David Jeannot <djeannot24@gmail.com>
        David Swasey <david.swasey@gmail.com>
        Enrique Soriano <enrique.soriano@gmail.com>
        Eoghan Sherry <ejsherry@gmail.com>
 (DIR) diff --git a/src/cmd/acme/text.c b/src/cmd/acme/text.c
       t@@ -751,6 +751,10 @@ texttype(Text *t, Rune r)
                        typecommit(t);
                        cut(t, t, nil, TRUE, FALSE, nil, 0);
                        return;
       +        case Kcmd+'z':        /* %Z: undo */
       +                 typecommit(t);
       +                undo(t, nil, nil, TRUE, 0, nil, 0);
       +                return;
        
                Tagdown:
                        /* expand tag to show all text */
 (DIR) diff --git a/src/cmd/devdraw/cocoa-screen.h b/src/cmd/devdraw/cocoa-screen.h
       t@@ -0,0 +1,19 @@
       +#define setcursor dsetcursor
       +
       +Memimage *attachscreen(char*, char*);
       +void        setmouse(Point);
       +void        setcursor(Cursor*);
       +void        setlabel(char*);
       +char*        getsnarf(void);
       +void        putsnarf(char*);
       +
       +void        mousetrack(int, int, int, int);
       +void        keystroke(int);
       +void        kicklabel(char*);
       +
       +void        servep9p(void);
       +void        zlock(void);
       +void        zunlock(void);
       +
       +Rectangle mouserect;
       +int        mouseresized;
 (DIR) diff --git a/src/cmd/devdraw/cocoa-screen.m b/src/cmd/devdraw/cocoa-screen.m
       t@@ -0,0 +1,835 @@
       +/*
       + * Cocoa's event loop must be in the main thread.
       + */
       +
       +#define Point OSXPoint
       +#define Rect OSXRect
       +#define Cursor OSXCursor
       +
       +#import <Cocoa/Cocoa.h>
       +
       +#undef Rect
       +#undef Point
       +#undef Cursor
       +
       +#include <u.h>
       +#include <libc.h>
       +#include  "cocoa-thread.h"
       +#include <draw.h>
       +#include <memdraw.h>
       +#include <keyboard.h>
       +#include <cursor.h>
       +#include "cocoa-screen.h"
       +#include "osx-keycodes.h"
       +#include "devdraw.h"
       +#include "glendapng.h"
       +
       +#define DEBUG if(0)NSLog
       +
       +AUTOFRAMEWORK(Cocoa)
       +
       +#define panic sysfatal
       +
       +struct {
       +        NSWindow        *obj;
       +        NSString                *label;
       +        char                        *winsize;
       +        int                        ispositioned;
       +
       +        NSImage                *img;
       +        Memimage        *imgbuf;
       +        NSSize                imgsize;
       +
       +        QLock                lock;
       +        Rendez                meeting;
       +        NSRect                flushr;
       +        int                        osxdrawing;
       +        int                        p9pflushing;
       +        int                        isresizing;
       +} win;
       +
       +@interface appdelegate : NSObject
       +        +(void)callmakewin:(id)arg; @end
       +@interface appthreads : NSObject
       +        +(void)callservep9p:(id)arg; @end
       +@interface appview : NSView @end
       +
       +int chatty;
       +int multitouch = 1;
       +
       +void
       +usage(void)
       +{
       +        fprint(2, "usage: devdraw (don't run directly)\n");
       +        exits("usage");
       +}
       +
       +void
       +main(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':
       +                chatty++;
       +                break;
       +        case 'M':
       +                multitouch = 0;
       +                break;
       +        default:
       +                usage();
       +        }ARGEND
       +
       +        /*
       +         * Ignore arguments.  They're only for good ps -a listings.
       +         */
       +
       +
       +        [NSApplication sharedApplication];
       +        [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
       +        [NSApp setDelegate:[appdelegate new]];
       +        [NSApp activateIgnoringOtherApps:YES];
       +        [NSApp run];
       +}
       +
       +static void eresized(int);
       +static void getmousepos(void);
       +static void makemenu(NSString*);
       +static void makewin();
       +static void seticon(NSString*);
       +
       +@implementation appdelegate
       +- (void)applicationDidFinishLaunching:(id)arg
       +{
       +        [NSApplication detachDrawingThread:@selector(callservep9p:)
       +                toTarget:[appthreads class] withObject:nil];
       +}
       ++ (void)callmakewin:(id)arg
       +{
       +        makewin();
       +}
       +- (void)windowDidResize:(id)arg
       +{
       +        eresized(1);
       +}
       +- (void)windowDidBecomeKey:(id)arg
       +{
       +        getmousepos();
       +}
       +- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(id)arg
       +{
       +        return YES;
       +}
       +@end
       +
       +@implementation appthreads
       ++(void)callservep9p:(id)arg
       +{
       +        servep9p();
       +        [NSApp terminate:self];
       +}
       +@end
       +
       +Memimage*
       +attachscreen(char *label, char *winsize)
       +{
       +        static int first = 1;
       +
       +        if(! first--)
       +                panic("attachscreen called twice");
       +
       +        if(label == nil)
       +                label = "gnot a label";
       +
       +        win.label = [[NSString alloc] initWithUTF8String:label];
       +        win.meeting.l = &win.lock;
       +        win.winsize = strdup(winsize);
       +
       +        makemenu(win.label);
       +
       +//        make NSWindow in the main thread,
       +//        else no resize cursor when resizing.
       +        [appdelegate
       +                performSelectorOnMainThread:@selector(callmakewin:)
       +                withObject:nil
       +                waitUntilDone:YES];
       +//        makewin();
       +
       +        seticon(win.label);
       +
       +        eresized(0);
       +
       +        return win.imgbuf;
       +}
       +
       +void
       +makewin(id winsize)
       +{
       +        char *s;
       +        int style;
       +        NSWindow *w;
       +        NSRect r, sr;
       +        Rectangle wr;
       +
       +        s = win.winsize;
       +
       +        if(s && *s){
       +                if(parsewinsize(s, &wr, &win.ispositioned) < 0)
       +                        sysfatal("%r");
       +        }else{
       +                sr = [[NSScreen mainScreen] frame];
       +                wr = Rect(0, 0, sr.size.width*2/3, sr.size.height*2/3);
       +        }
       +//        The origin is the left-bottom corner with Cocoa.
       +//        Does the following work with any rectangles?
       +        r = NSMakeRect(wr.min.x, r.size.height-wr.min.y, Dx(wr), Dy(wr));
       +
       +        style = NSTitledWindowMask
       +                | NSClosableWindowMask
       +                | NSResizableWindowMask
       +                | NSMiniaturizableWindowMask;
       +
       +        w = [[NSWindow alloc]
       +                initWithContentRect:r
       +                styleMask:style
       +                backing:NSBackingStoreBuffered
       +                defer:NO];
       +
       +        [w setAcceptsMouseMovedEvents:YES];
       +#if OSX_VERSION >= 100700
       +        [w setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
       +#endif
       +        [w setContentView:[appview new]];
       +        [w setDelegate:[NSApp delegate]];
       +        [w setMinSize:NSMakeSize(128,128)];
       +        [[w contentView] setAcceptsTouchEvents:YES];
       +
       +        if(win.ispositioned == 0)
       +                [w center];
       +
       +        [w setTitle:win.label];
       +        [w makeKeyAndOrderFront:nil];
       +
       +        win.obj = w;
       +}
       +
       +static void sendmouse(int);
       +
       +static void
       +eresized(int new)
       +{
       +        static int first = 1;
       +        uint ch;
       +        NSSize size;
       +        Rectangle r;
       +        Memimage *m;
       +        int bpl;
       +
       +        if(first--)
       +                memimageinit();
       +
       +        size = [[win.obj contentView] bounds].size;
       +DEBUG(@"eresized called new=%d, [%.0f %.0f] -> [%.0f %.0f]", new,
       +win.imgsize.width, win.imgsize.height, size.width, size.height);
       +
       +        r = Rect(0, 0, size.width, size.height);
       +        ch = XBGR32;
       +        m = allocmemimage(r, ch);
       +        if(m == nil)
       +                panic("allocmemimage: %r");
       +        if(m->data == nil)
       +                panic("m->data == nil");
       +
       +        bpl = bytesperline(r, 32);
       +
       +        CGDataProviderRef dp;
       +        CGImageRef i;
       +        CGColorSpaceRef cs;
       +
       +        dp = CGDataProviderCreateWithData(0, m->data->bdata, Dy(r)*bpl, 0);
       +        cs = CGColorSpaceCreateDeviceRGB();
       +        i = CGImageCreate(Dx(r), Dy(r), 8, 32, bpl,
       +                cs, kCGImageAlphaNone, dp, 0, 0, kCGRenderingIntentDefault);
       +
       +        _drawreplacescreenimage(m);
       +        if(win.img)
       +                [win.img release];
       +
       +        win.img = [[NSImage alloc] initWithCGImage:i size:size];
       +        win.imgbuf = m;
       +        win.imgsize = size;
       +
       +        CGColorSpaceRelease(cs);
       +        CGDataProviderRelease(dp);
       +        CGImageRelease(i);
       +
       +        if(new){
       +                win.isresizing = 1;        // to call before mousetrack
       +                sendmouse(1);
       +        }
       +DEBUG(@"eresized exit");
       +}
       +
       +static void getgesture(NSEvent*);
       +static void getkeyboard(NSEvent*);
       +static void getmouse(NSEvent*);
       +
       +@implementation appview
       +
       +- (void)mouseMoved:(NSEvent*)e{ getmouse(e);}
       +- (void)mouseDown:(NSEvent*)e{ getmouse(e);}
       +- (void)mouseDragged:(NSEvent*)e{ getmouse(e);}
       +- (void)mouseUp:(NSEvent*)e{ getmouse(e);}
       +- (void)otherMouseDown:(NSEvent*)e{ getmouse(e);}
       +- (void)otherMouseDragged:(NSEvent*)e{ getmouse(e);}
       +- (void)otherMouseUp:(NSEvent*)e{ getmouse(e);}
       +- (void)rightMouseDown:(NSEvent*)e{ getmouse(e);}
       +- (void)rightMouseDragged:(NSEvent*)e{ getmouse(e);}
       +- (void)rightMouseUp:(NSEvent*)e{ getmouse(e);}
       +- (void)scrollWheel:(NSEvent*)e{ getmouse(e);}
       +
       +- (void)keyDown:(NSEvent*)e{ getkeyboard(e);}
       +- (void)flagsChanged:(NSEvent*)e{ getkeyboard(e);}
       +
       +- (void)magnifyWithEvent:(NSEvent*)e{ DEBUG(@"magnifyWithEvent"); getgesture(e);}
       +- (void)swipeWithEvent:(NSEvent*)e{ DEBUG(@"swipeWithEvent"); getgesture(e);}
       +- (void)touchesEndedWithEvent:(NSEvent*)e{ DEBUG(@"touchesEndedWithEvent"); getgesture(e);}
       +
       +- (BOOL)acceptsFirstResponder{ return YES; }        // to receive mouseMoved events
       +- (BOOL)isFlipped{ return YES; }
       +- (BOOL)isOpaque{ return YES; }        // to disable background painting before drawRect calls
       +
       +- (void)drawRect:(NSRect)r
       +{
       +        NSRect sr;
       +        NSView *v;
       +
       +        v = [win.obj contentView];
       +
       +        DEBUG(@"drawRect called [%.0f %.0f] [%.0f %.0f]",
       +                r.origin.x, r.origin.y, r.size.width, r.size.height);
       +
       +        if(! NSEqualSizes([v bounds].size, win.imgsize)){
       +                DEBUG(@"drawRect: contentview & img don't correspond: [%.0f %.0f] [%.0f %.0f]",
       +                        [v bounds].size.width, [v bounds].size.height,
       +                        win.imgsize.width, win.imgsize.height);
       +                return;
       +        }
       +
       +        qlock(win.meeting.l);
       +        if(win.isresizing){
       +                if(! NSEqualRects(r, [v bounds])){
       +                        DEBUG(@"drawRect reject osx");
       +                        goto Return;
       +                }
       +                win.isresizing = 0;
       +                DEBUG(@"drawRect serve osx");
       +        }else{
       +                if(! NSEqualRects(r, win.flushr)){
       +                        DEBUG(@"drawRect reject p9p");
       +                        goto Return;
       +                }
       +                DEBUG(@"drawRect serve p9p");
       +        }
       +        win.flushr = r;
       +        win.osxdrawing = 1;
       +        rwakeup(&win.meeting);
       +        DEBUG(@"drawRect rsleep for p9pflushing=1");
       +        while(win.p9pflushing == 0)
       +                rsleep(&win.meeting);
       +
       +        DEBUG(@"drawRect drawInRect [%.0f %.0f] [%.0f %.0f]",
       +                r.origin.x, r.origin.y, r.size.width, r.size.height);
       +
       +        sr =  [v convertRect:r fromView:nil];
       +        [win.img drawInRect:r fromRect:sr
       +                operation:NSCompositeCopy fraction:1
       +                respectFlipped:YES hints:nil];
       +
       +        [win.obj flushWindow];
       +
       +        win.osxdrawing = 0;
       +        rwakeup(&win.meeting);
       +Return:
       +        DEBUG(@"drawRect exit");
       +        qunlock(win.meeting.l);
       +}
       +@end
       +
       +void
       +_flushmemscreen(Rectangle r)
       +{
       +        NSRect rect;
       +        NSView *v;
       +
       +        v = [win.obj contentView];
       +        rect = NSMakeRect(r.min.x, r.min.y, Dx(r), Dy(r));
       +
       +        DEBUG(@"_flushmemscreen called [%.0f %.0f] [%.0f %.0f]",
       +                rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
       +        qlock(win.meeting.l);
       +        if(win.osxdrawing == 0){
       +                DEBUG(@"_flushmemscreen setNeedsDisplayInRect");
       +                [v setNeedsDisplayInRect:rect];
       +                win.flushr = rect;
       +                DEBUG(@"_flushmemscreen rsleep for osxdrawing=1");
       +                while(win.osxdrawing == 0)
       +                        rsleep(&win.meeting);
       +        }
       +        if(! NSEqualRects(rect, win.flushr)){
       +                qunlock(win.meeting.l);
       +                DEBUG(@"_flushmemscreen bad rectangle");
       +                return;
       +        }
       +        win.flushr = NSMakeRect(0,0,0,0);
       +        win.p9pflushing = 1;
       +        rwakeup(&win.meeting);
       +        DEBUG(@"_flushmemscreen rsleep for osxdrawing=0");
       +        while(win.osxdrawing)
       +                rsleep(&win.meeting);
       +
       +        win.p9pflushing = 0;
       +        DEBUG(@"_flushmemscreen exit");
       +        qunlock(win.meeting.l);
       +}
       +
       +static int keycvt[] =
       +{
       +        [QZ_IBOOK_ENTER] '\n',
       +        [QZ_RETURN] '\n',
       +        [QZ_ESCAPE] 27,
       +        [QZ_BACKSPACE] '\b',
       +        [QZ_LALT] Kalt,
       +        [QZ_LCTRL] Kctl,
       +        [QZ_LSHIFT] Kshift,
       +        [QZ_F1] KF+1,
       +        [QZ_F2] KF+2,
       +        [QZ_F3] KF+3,
       +        [QZ_F4] KF+4,
       +        [QZ_F5] KF+5,
       +        [QZ_F6] KF+6,
       +        [QZ_F7] KF+7,
       +        [QZ_F8] KF+8,
       +        [QZ_F9] KF+9,
       +        [QZ_F10] KF+10,
       +        [QZ_F11] KF+11,
       +        [QZ_F12] KF+12,
       +        [QZ_INSERT] Kins,
       +        [QZ_DELETE] 0x7F,
       +        [QZ_HOME] Khome,
       +        [QZ_END] Kend,
       +        [QZ_KP_PLUS] '+',
       +        [QZ_KP_MINUS] '-',
       +        [QZ_TAB] '\t',
       +        [QZ_PAGEUP] Kpgup,
       +        [QZ_PAGEDOWN] Kpgdown,
       +        [QZ_UP] Kup,
       +        [QZ_DOWN] Kdown,
       +        [QZ_LEFT] Kleft,
       +        [QZ_RIGHT] Kright,
       +        [QZ_KP_MULTIPLY] '*',
       +        [QZ_KP_DIVIDE] '/',
       +        [QZ_KP_ENTER] '\n',
       +        [QZ_KP_PERIOD] '.',
       +        [QZ_KP0] '0',
       +        [QZ_KP1] '1',
       +        [QZ_KP2] '2',
       +        [QZ_KP3] '3',
       +        [QZ_KP4] '4',
       +        [QZ_KP5] '5',
       +        [QZ_KP6] '6',
       +        [QZ_KP7] '7',
       +        [QZ_KP8] '8',
       +        [QZ_KP9] '9',
       +};
       +
       +int                kalting;
       +int                kbuttons;
       +int                mbuttons;
       +Point                mpos;
       +int                scroll;
       +
       +static void
       +getkeyboard(NSEvent *e)
       +{
       +        uint code;
       +        int k, m;
       +        char c;
       +
       +        m = [e modifierFlags];
       +
       +        switch([e type]){
       +        case NSKeyDown:
       +                kalting = 0;
       +                c = [[e characters] characterAtIndex:0];
       +                if(m & NSCommandKeyMask){
       +
       +// If I add cmd+h in the menu, does the combination
       +// appear here?  If it doesn't, remove the following
       +//
       +//                        // OS X interprets a few no matter what we do,
       +//                        switch(c) {
       +//                        case 'm':        // minimize window
       +//                        case 'h':        // hide window
       +//                        case 'H':        // hide others
       +//                        case 'q':        // quit
       +//                                return;
       +//                        }
       +                        if(' '<=c && c<='~') {
       +                                keystroke(Kcmd+c);
       +                                return;
       +                        }
       +                        return;
       +                }
       +//                to undersand
       +                k = c;
       +                code = [e keyCode];
       +                if(code < nelem(keycvt) && keycvt[code])
       +                        k = keycvt[code];
       +                if(k == 0)
       +                        return;
       +                if(k > 0)
       +                        keystroke(k);
       +                else
       +                        keystroke(c);
       +                break;
       +
       +        case NSFlagsChanged:
       +                if(mbuttons || kbuttons){
       +                        kbuttons = 0;
       +                        if(m & NSAlternateKeyMask)
       +                                kbuttons |= 2;
       +                        if(m & NSCommandKeyMask)
       +                                kbuttons |= 4;
       +                        sendmouse(0);
       +                }else
       +                if(m & NSAlternateKeyMask) {
       +                        kalting = 1;
       +                        keystroke(Kalt);
       +                }
       +                break;
       +
       +        default:
       +                panic("getkey: unexpected event type");
       +        }
       +}
       +
       +static void
       +getmousepos(void)
       +{
       +        NSPoint p;
       +
       +        p = [win.obj mouseLocationOutsideOfEventStream];
       +        p = [[win.obj contentView] convertPoint:p fromView:nil];
       +//        DEBUG(@"getmousepos: %0.f %0.f", p.x, p.y);
       +        mpos = Pt(p.x, p.y);
       +}
       +
       +static void
       +getmouse(NSEvent *e)
       +{
       +        int b, m;
       +        float d;
       +
       +        getmousepos();
       +
       +        switch([e type]){
       +        case NSLeftMouseDown:
       +        case NSLeftMouseUp:
       +        case NSOtherMouseDown:
       +        case NSOtherMouseUp:
       +        case NSRightMouseDown:
       +        case NSRightMouseUp:
       +
       +                b = [NSEvent pressedMouseButtons];
       +                b = b&~6 | (b&4)>>1 | (b&2)<<1;
       +                b = mouseswap(b);
       +
       +                if(b == 1){
       +                        m = [e modifierFlags];
       +                        if(m & NSAlternateKeyMask) {
       +                                b = 2;
       +                                // Take the ALT away from the keyboard handler.
       +                                if(kalting) {
       +                                        kalting = 0;
       +                                        keystroke(Kalt);
       +                                }
       +                        }else
       +                        if(m & NSCommandKeyMask)
       +                                b = 4;
       +                }
       +                mbuttons = b;
       +                break;
       +
       +        case NSScrollWheel:
       +#if OSX_VERSION >= 100700
       +                d = [e scrollingDeltaY];
       +#else
       +                d = [e deltaY];
       +#endif
       +                if(d>0)
       +                        scroll = 8;
       +                else if(d<0)
       +                        scroll = 16;
       +                break;
       +
       +        case NSMouseMoved:
       +        case NSLeftMouseDragged:
       +        case NSRightMouseDragged:
       +        case NSOtherMouseDragged:
       +                break;
       +
       +        default:
       +                panic("getmouse: unexpected event type");
       +        }
       +        sendmouse(0);
       +}
       +
       +static void sendexec(int);
       +static void sendcmd(int, int*);
       +
       +static void
       +getgesture(NSEvent *e)
       +{
       +        static int undo;
       +        int dx, dy;
       +
       +        switch([e type]){
       +
       +        case NSEventTypeMagnify:
       +#if OSX_VERSION >= 100700
       +                [win.obj toggleFullScreen:nil];
       +#endif
       +                break;
       +
       +        case NSEventTypeSwipe:
       +                
       +                dx = - [e deltaX];
       +                dy = - [e deltaY];
       +
       +                if(dx == -1)
       +                        sendcmd('x', &undo);
       +                else
       +                if(dx == +1)
       +                        sendcmd('v', &undo);
       +                else
       +                if(dy == -1)
       +                        sendexec(0);
       +                else
       +                if(dy == +1)
       +                        sendexec(1);
       +                else                                // fingers lifted
       +                        undo = 0;
       +                break;
       +
       +//        When I lift the fingers from the trackpad, I
       +//        receive 1, 2, or 3 events "touchesEndedWithEvent".
       +//        Their type is either generic (NSEventTypeGesture)
       +//        or specific (NSEventTypeSwipe for example).  I
       +//        always receive at least 1 event of specific type.
       +
       +//        I sometimes receive NSEventTypeEndGesture
       +//        apparently, even without implementing
       +//        "endGestureWithEvent"
       +//        I even received a NSEventTypeBeginGesture once.
       +
       +        case NSEventTypeBeginGesture:
       +                break;
       +
       +        case NSEventTypeGesture:
       +        case NSEventTypeEndGesture:
       +//                do a undo here? because 2 times I had the impression undo was still 1
       +//                after having lifted my fingers
       +                undo = 0;
       +                break;
       +
       +        default:
       +                DEBUG(@"getgesture: unexpected event type: %d", [e type]);
       +        }
       +}
       +
       +static void
       +sendcmd(int c, int *undo)
       +{
       +        if(*undo)
       +                c = 'z';
       +        *undo = ! *undo;
       +        keystroke(Kcmd+c);
       +}
       +
       +static void
       +sendexec(int giveargs)
       +{
       +        mbuttons = 2;
       +        sendmouse(0);
       +
       +        if(giveargs){
       +                mbuttons |= 1;
       +                sendmouse(0);
       +        }
       +        mbuttons = 0;
       +        sendmouse(0);
       +}
       +
       +static uint
       +msec(void)
       +{
       +        return nsec()/1000000;
       +}
       +
       +static void
       +sendmouse(int resized)
       +{
       +        if(resized)
       +                mouseresized = 1;
       +        mouserect = win.imgbuf->r;
       +        mousetrack(mpos.x, mpos.y, kbuttons|mbuttons|scroll, msec());
       +        scroll = 0;
       +}
       +
       +void
       +setmouse(Point p)
       +{
       +        NSPoint q;
       +        NSRect r;
       +
       +        r = [[NSScreen mainScreen] frame];
       +
       +        q = NSMakePoint(p.x,p.y);
       +        q = [[win.obj contentView] convertPoint:q toView:nil];
       +        q = [win.obj convertBaseToScreen:q];
       +        q.y = r.size.height - q.y;
       +
       +        CGWarpMouseCursorPosition(q);
       +
       +//        race condition
       +        mpos = p;
       +}
       +
       +//        setBadgeLabel don't have to be in this function.
       +//        Remove seticon's argument too.
       +static void
       +seticon(NSString *s)
       +{
       +        NSData *d;
       +        NSImage *i;
       +
       +        d = [[NSData alloc]
       +                initWithBytes:glenda_png
       +                length:(sizeof glenda_png)];
       +
       +        i = [[NSImage alloc] initWithData:d];
       +        if(i){
       +                [NSApp setApplicationIconImage:i];
       +                [[NSApp dockTile] display];
       +                [[NSApp dockTile] setBadgeLabel:s];
       +        }
       +        [d release];
       +        [i release];
       +}
       +
       +//        Menu should be called during app creation, not window creation.
       +//        See ./osx-delegate.m implementation.
       +
       +//        If an application supports fullscreen, it should
       +//        add an "Enter Full Screen" menu item to the View
       +//        menu.  The menu item is now available through
       +//        Xcode 4.  You can also add the item
       +//        programmatically, with toggleFullScreen: as the
       +//        action, nil as the target, and cmd-ctrl-f as the
       +//        key equivalent.  AppKit will automatically update
       +//        the menu item title as part of its menu item
       +//        validation.
       +static void
       +makemenu(NSString *s)
       +{
       +        NSString *title;
       +        NSMenu *menu;
       +        NSMenuItem *appmenu, *item;
       +
       +        menu = [NSMenu new];
       +        appmenu = [NSMenuItem new];
       +        [menu addItem:appmenu];
       +        [NSApp setMenu:menu];
       +        [menu release];
       +
       +        title = [@"Quit " stringByAppendingString:win.label];
       +        item = [[NSMenuItem alloc]
       +                initWithTitle:title
       +                action:@selector(terminate:) keyEquivalent:@"q"];
       +
       +        menu = [NSMenu new];
       +        [menu addItem:item];
       +        [item release];
       +        [appmenu setSubmenu:menu];
       +        [appmenu release];
       +        [menu release];
       +}
       +
       +QLock snarfl;
       +
       +char*
       +getsnarf(void)
       +{
       +        NSString *s;
       +        NSPasteboard *pb;
       +
       +        pb = [NSPasteboard generalPasteboard];
       +
       +//        use NSPasteboardTypeString instead of NSStringPboardType
       +        qlock(&snarfl);
       +        s = [pb stringForType:NSStringPboardType];
       +        qunlock(&snarfl);
       +
       +//        change the pastebuffer here to see if s is
       +//        altered. Move the lock accordingly.
       +
       +        if(s)
       +                return strdup((char*)[s UTF8String]);                
       +        else
       +                return nil;
       +//        should I call autorelease here for example?
       +}
       +
       +void
       +putsnarf(char *s)
       +{
       +        NSArray *t;
       +        NSString *str;
       +        NSPasteboard *pb;
       +        int r;
       +
       +        if(strlen(s) >= SnarfSize)
       +                return;
       +
       +        t = [NSArray arrayWithObject:NSPasteboardTypeString];
       +        pb = [NSPasteboard generalPasteboard];
       +        str = [[NSString alloc] initWithUTF8String:s];
       +
       +        qlock(&snarfl);
       +        [pb declareTypes:t owner:nil];
       +        r = [pb setString:str forType:NSPasteboardTypeString];
       +        qunlock(&snarfl);
       +
       +        if(!r)
       +                DEBUG(@"putsnarf: setString failed");
       +}
       +
       +void
       +kicklabel(char *c)
       +{
       +}
       +
       +void
       +setcursor(Cursor *c)
       +{
       +}
 (DIR) diff --git a/src/cmd/devdraw/cocoa-srv.c b/src/cmd/devdraw/cocoa-srv.c
       t@@ -0,0 +1,397 @@
       +/*
       + * Window system protocol server.
       + */
       +
       +#include <u.h>
       +#include <libc.h>
       +#include "cocoa-thread.h"
       +#include <draw.h>
       +#include <memdraw.h>
       +#include <keyboard.h>
       +#include <mouse.h>
       +#include <cursor.h>
       +#include <drawfcall.h>
       +#include "cocoa-screen.h"
       +#include "devdraw.h"
       +
       +typedef struct Kbdbuf Kbdbuf;
       +typedef struct Mousebuf Mousebuf;
       +typedef struct Fdbuf Fdbuf;
       +typedef struct Tagbuf Tagbuf;
       +
       +struct Kbdbuf
       +{
       +        Rune r[32];
       +        int ri;
       +        int wi;
       +        int stall;
       +};
       +
       +struct Mousebuf
       +{
       +        Mouse m[32];
       +        Mouse last;
       +        int ri;
       +        int wi;
       +        int stall;
       +};
       +
       +struct Tagbuf
       +{
       +        int t[32];
       +        int ri;
       +        int wi;
       +};
       +
       +Kbdbuf kbd;
       +Mousebuf mouse;
       +Tagbuf kbdtags;
       +Tagbuf mousetags;
       +
       +void runmsg(Wsysmsg*);
       +void replymsg(Wsysmsg*);
       +void matchkbd(void);
       +void matchmouse(void);
       +
       +
       +QLock lk;
       +void
       +zlock(void)
       +{
       +        qlock(&lk);
       +}
       +
       +void
       +zunlock(void)
       +{
       +        qunlock(&lk);
       +}
       +
       +int trace = 0;
       +
       +void
       +servep9p(void)
       +{
       +        uchar buf[4], *mbuf;
       +        int nmbuf, n, nn;
       +        Wsysmsg m;
       +
       +        fmtinstall('W', drawfcallfmt);
       +        
       +//        notify(bell);
       +
       +        mbuf = nil;
       +        nmbuf = 0;
       +        while((n = read(3, 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(3, 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, "<- %W\n", &m);
       +                runmsg(&m);
       +        }
       +}
       +
       +void
       +replyerror(Wsysmsg *m)
       +{
       +        char err[256];
       +        
       +        rerrstr(err, sizeof err);
       +        m->type = Rerror;
       +        m->error = err;
       +        replymsg(m);
       +}
       +
       +/* 
       + * Handle a single wsysmsg. 
       + * Might queue for later (kbd, mouse read)
       + */
       +void
       +runmsg(Wsysmsg *m)
       +{
       +        static uchar buf[65536];
       +        int n;
       +        Memimage *i;
       +        
       +        switch(m->type){
       +        case Tinit:
       +                memimageinit();
       +                i = attachscreen(m->label, m->winsize);
       +                _initdisplaymemimage(i);
       +                replymsg(m);
       +                break;
       +
       +        case Trdmouse:
       +                zlock();
       +                mousetags.t[mousetags.wi++] = m->tag;
       +                if(mousetags.wi == nelem(mousetags.t))
       +                        mousetags.wi = 0;
       +                if(mousetags.wi == mousetags.ri)
       +                        sysfatal("too many queued mouse reads");
       +                mouse.stall = 0;
       +                matchmouse();
       +                zunlock();
       +                break;
       +
       +        case Trdkbd:
       +                zlock();
       +                kbdtags.t[kbdtags.wi++] = m->tag;
       +                if(kbdtags.wi == nelem(kbdtags.t))
       +                        kbdtags.wi = 0;
       +                if(kbdtags.wi == kbdtags.ri)
       +                        sysfatal("too many queued keyboard reads");
       +                kbd.stall = 0;
       +                matchkbd();
       +                zunlock();
       +                break;
       +
       +        case Tmoveto:
       +                setmouse(m->mouse.xy);
       +                replymsg(m);
       +                break;
       +
       +        case Tcursor:
       +                if(m->arrowcursor)
       +                        setcursor(nil);
       +                else
       +                        setcursor(&m->cursor);
       +                replymsg(m);
       +                break;
       +                        
       +        case Tbouncemouse:
       +        //        _xbouncemouse(&m->mouse);
       +                replymsg(m);
       +                break;
       +
       +        case Tlabel:
       +                kicklabel(m->label);
       +                replymsg(m);
       +                break;
       +
       +        case Trdsnarf:
       +                m->snarf = getsnarf();
       +                replymsg(m);
       +                free(m->snarf);
       +                break;
       +
       +        case Twrsnarf:
       +                putsnarf(m->snarf);
       +                replymsg(m);
       +                break;
       +
       +        case Trddraw:
       +                n = m->count;
       +                if(n > sizeof buf)
       +                        n = sizeof buf;
       +                n = _drawmsgread(buf, n);
       +                if(n < 0)
       +                        replyerror(m);
       +                else{
       +                        m->count = n;
       +                        m->data = buf;
       +                        replymsg(m);
       +                }
       +                break;
       +
       +        case Twrdraw:
       +                if(_drawmsgwrite(m->data, m->count) < 0)
       +                        replyerror(m);
       +                else
       +                        replymsg(m);
       +                break;
       +        
       +        case Ttop:
       +        //        _xtopwindow();
       +                replymsg(m);
       +                break;
       +        
       +        case Tresize:
       +        //        _xresizewindow(m->rect);
       +                replymsg(m);
       +                break;
       +        }
       +}
       +
       +/*
       + * Reply to m.
       + */
       +QLock replylock;
       +void
       +replymsg(Wsysmsg *m)
       +{
       +        int n;
       +        static uchar *mbuf;
       +        static int nmbuf;
       +
       +        /* T -> R msg */
       +        if(m->type%2 == 0)
       +                m->type++;
       +                
       +        if(trace) fprint(2, "-> %W\n", 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(4, mbuf, n) != n)
       +                sysfatal("write: %r");
       +        qunlock(&replylock);
       +}
       +
       +/*
       + * Match queued kbd reads with queued kbd characters.
       + */
       +void
       +matchkbd(void)
       +{
       +        Wsysmsg m;
       +        
       +        if(kbd.stall)
       +                return;
       +        while(kbd.ri != kbd.wi && kbdtags.ri != kbdtags.wi){
       +                m.type = Rrdkbd;
       +                m.tag = kbdtags.t[kbdtags.ri++];
       +                if(kbdtags.ri == nelem(kbdtags.t))
       +                        kbdtags.ri = 0;
       +                m.rune = kbd.r[kbd.ri++];
       +                if(kbd.ri == nelem(kbd.r))
       +                        kbd.ri = 0;
       +                replymsg(&m);
       +        }
       +}
       +
       +/*
       + * Match queued mouse reads with queued mouse events.
       + */
       +void
       +matchmouse(void)
       +{
       +        Wsysmsg m;
       +        
       +        while(mouse.ri != mouse.wi && mousetags.ri != mousetags.wi){
       +                m.type = Rrdmouse;
       +                m.tag = mousetags.t[mousetags.ri++];
       +                if(mousetags.ri == nelem(mousetags.t))
       +                        mousetags.ri = 0;
       +                m.mouse = mouse.m[mouse.ri];
       +                m.resized = mouseresized;
       +                /*
       +                if(m.resized)
       +                        fprint(2, "sending resize\n");
       +                */
       +                mouseresized = 0;
       +                mouse.ri++;
       +                if(mouse.ri == nelem(mouse.m))
       +                        mouse.ri = 0;
       +                replymsg(&m);
       +        }
       +}
       +
       +void
       +mousetrack(int x, int y, int b, int ms)
       +{
       +        Mouse *m;
       +        
       +        if(x < mouserect.min.x)
       +                x = mouserect.min.x;
       +        if(x > mouserect.max.x)
       +                x = mouserect.max.x;
       +        if(y < mouserect.min.y)
       +                y = mouserect.min.y;
       +        if(y > mouserect.max.y)
       +                y = mouserect.max.y;
       +
       +        zlock();
       +        // If reader has stopped reading, don't bother.
       +        // If reader is completely caught up, definitely queue.
       +        // Otherwise, queue only button change events.
       +        if(!mouse.stall)
       +        if(mouse.wi == mouse.ri || mouse.last.buttons != b){
       +                m = &mouse.last;
       +                m->xy.x = x;
       +                m->xy.y = y;
       +                m->buttons = b;
       +                m->msec = ms;
       +
       +                mouse.m[mouse.wi] = *m;
       +                if(++mouse.wi == nelem(mouse.m))
       +                        mouse.wi = 0;
       +                if(mouse.wi == mouse.ri){
       +                        mouse.stall = 1;
       +                        mouse.ri = 0;
       +                        mouse.wi = 1;
       +                        mouse.m[0] = *m;
       +                }
       +                matchmouse();
       +        }
       +        zunlock();
       +}
       +
       +void
       +kputc(int c)
       +{
       +        zlock();
       +        kbd.r[kbd.wi++] = c;
       +        if(kbd.wi == nelem(kbd.r))
       +                kbd.wi = 0;
       +        if(kbd.ri == kbd.wi)
       +                kbd.stall = 1;
       +        matchkbd();
       +        zunlock();
       +}
       +
       +void
       +keystroke(int c)
       +{
       +        static Rune k[10];
       +        static int alting, nk;
       +        int i;
       +
       +        if(c == Kalt){
       +                alting = !alting;
       +                return;
       +        }
       +        if(!alting){
       +                kputc(c);
       +                return;
       +        }
       +        if(nk >= nelem(k))      // should not happen
       +                nk = 0;
       +        k[nk++] = c;
       +        c = _latin1(k, nk);
       +        if(c > 0){
       +                alting = 0;
       +                kputc(c);
       +                nk = 0;
       +                return;
       +        }
       +        if(c == -1){
       +                alting = 0;
       +                for(i=0; i<nk; i++)
       +                        kputc(k[i]);
       +                nk = 0;
       +                return;
       +        }
       +        // need more input
       +        return;
       +}
 (DIR) diff --git a/src/cmd/devdraw/cocoa-thread.c b/src/cmd/devdraw/cocoa-thread.c
       t@@ -0,0 +1,54 @@
       +#include <u.h>
       +#include <libc.h>
       +#include "cocoa-thread.h"
       +
       +static pthread_mutex_t initlock = PTHREAD_MUTEX_INITIALIZER;
       +
       +void
       +qlock(QLock *q)
       +{
       +        if(q->init == 0){
       +                pthread_mutex_lock(&initlock);
       +                if(q->init == 0){
       +                        pthread_mutex_init(&q->m, nil);
       +                        q->init = 1;
       +                }
       +                pthread_mutex_unlock(&initlock);
       +        }
       +        pthread_mutex_lock(&q->m);
       +}
       +
       +void
       +qunlock(QLock *q)
       +{
       +        pthread_mutex_unlock(&q->m);
       +}
       +
       +static void
       +rinit(Rendez *r)
       +{
       +        pthread_mutex_lock(&initlock);
       +        if(r->init == 0){
       +                pthread_cond_init(&r->c, nil);
       +                r->init = 1;
       +        }
       +        pthread_mutex_unlock(&initlock);
       +}
       +
       +void
       +rsleep(Rendez *r)
       +{
       +        if(r->init == 0)
       +                rinit(r);
       +        pthread_cond_wait(&r->c, &r->l->m);
       +}
       +
       +int
       +rwakeup(Rendez *r)
       +{
       +        if(r->init == 0)
       +                rinit(r);
       +        pthread_cond_signal(&r->c);
       +
       +        return 0;
       +}
 (DIR) diff --git a/src/cmd/devdraw/cocoa-thread.h b/src/cmd/devdraw/cocoa-thread.h
       t@@ -0,0 +1,27 @@
       +#define QLock DQLock
       +#define qlock dqlock
       +#define qunlock dqunlock
       +#define Rendez DRendez
       +#define rsleep drsleep
       +#define rwakeup drwakeup
       +
       +typedef struct QLock QLock;
       +typedef struct Rendez Rendez;
       +
       +struct QLock
       +{
       +        pthread_mutex_t m;
       +        int init;
       +};
       +
       +struct Rendez
       +{
       +        QLock *l;
       +        pthread_cond_t c;
       +        int init;
       +};
       +
       +void        qlock(QLock*);
       +void        qunlock(QLock*);
       +void rsleep(Rendez*);
       +int rwakeup(Rendez*);        /* BUG: always returns 0 */
 (DIR) diff --git a/src/cmd/devdraw/mkfile b/src/cmd/devdraw/mkfile
       t@@ -38,6 +38,9 @@ CLEANFILES=latin1.h $O.mklatinkbd
        %-objc.$O: %.m
                $CC $CFLAGS -o $target $stem.m
        
       +cocoa: devdraw.o latin1.o mouseswap.o winsize.o osx-draw.o cocoa-screen-objc.o cocoa-srv.o cocoa-thread.o
       +        $LD -o $target $prereq
       +
        devdraw-cocoa: devdraw.o latin1.o mouseswap.o winsize.o osx-screen-objc.o osx-draw.o osx-srv-objc.o osx-delegate-objc.o
                $LD -o $target $prereq