URI: 
       tdevdraw: add multitouch code from Paul Lalonde - 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 100ec44e5170878bbd7fd28f7f68d884d0618173
   DIR parent b968422f51732c492ff4081786b713ace99835c1
  HTML Author: Russ Cox <rsc@swtch.com>
       Date:   Mon,  4 Jan 2010 10:23:35 -0800
       
       devdraw: add multitouch code from Paul Lalonde
       
       Various tweaks to avoid breaking standard mice,
       but probably needs tweaks to work with multitouch
       mice again.  Still, it's a start.
       
       R=rsc
       CC=plalonde, r
       http://codereview.appspot.com/181124
       
       Diffstat:
         M src/cmd/devdraw/mkwsysrules.sh      |       6 +++++-
         A src/cmd/devdraw/osx-screen-carbon.m |    1161 +++++++++++++++++++++++++++++++
         D src/cmd/devdraw/osx-screen.c        |     913 -------------------------------
         M src/cmd/devdraw/osx-srv.c           |       4 ++++
       
       4 files changed, 1170 insertions(+), 914 deletions(-)
       ---
   DIR diff --git a/src/cmd/devdraw/mkwsysrules.sh b/src/cmd/devdraw/mkwsysrules.sh
       t@@ -47,7 +47,11 @@ if [ $WSYSTYPE = x11 ]; then
                XO=`ls x11-*.c 2>/dev/null | sed 's/\.c$/.o/'`
                echo 'WSYSOFILES=$WSYSOFILES '$XO
        elif [ $WSYSTYPE = osx ]; then
       -        echo 'WSYSOFILES=$WSYSOFILES osx-screen.o osx-draw.o osx-srv.o'
       +        if [ -d /System/Library/PrivateFrameworks/MultitouchSupport.framework ]; then
       +                echo 'CFLAGS=$CFLAGS -DMULTITOUCH'
       +                echo 'LDFLAGS=$LDFLAGS -F/System/Library/PrivateFrameworks'
       +        fi
       +        echo 'WSYSOFILES=$WSYSOFILES osx-screen-carbon-objc.o osx-draw.o osx-srv.o'
        elif [ $WSYSTYPE = nowsys ]; then
                echo 'WSYSOFILES=nowsys.o'
        fi
   DIR diff --git a/src/cmd/devdraw/osx-screen-carbon.m b/src/cmd/devdraw/osx-screen-carbon.m
       t@@ -0,0 +1,1161 @@
       +#define Point OSXPoint
       +#define Rect OSXRect
       +#define Cursor OSXCursor
       +#include <Carbon/Carbon.h>
       +#undef Rect
       +#undef Point
       +#undef Cursor
       +#undef offsetof
       +#undef nil
       +
       +#include "u.h"
       +#include "libc.h"
       +#include <thread.h>
       +#include <draw.h>
       +#include <memdraw.h>
       +#include <keyboard.h>
       +#include "mouse.h"
       +#include <cursor.h>
       +#include "osx-screen.h"
       +#include "osx-keycodes.h"
       +#include "devdraw.h"
       +#include "glendapng.h"
       +
       +AUTOFRAMEWORK(Carbon)
       +AUTOFRAMEWORK(Cocoa)
       +
       +#ifdef MULTITOUCH
       +AUTOFRAMEWORK(MultiTouchSupport)
       +#endif
       +
       +#define panic sysfatal
       +
       +extern Rectangle mouserect;
       +
       +struct {
       +        char *label;
       +        char *winsize;
       +        QLock labellock;
       +
       +        Rectangle fullscreenr;
       +        Rectangle screenr;
       +        Memimage *screenimage;
       +        int isfullscreen;
       +        ulong fullscreentime;
       +        
       +        Point xy;
       +        int buttons;
       +        int kbuttons;
       +
       +        CGDataProviderRef provider;
       +        MenuRef wmenu;
       +        MenuRef vmenu;
       +        WindowRef window;
       +        CGImageRef image;
       +        CGContextRef windowctx;
       +        PasteboardRef snarf;
       +        int needflush;
       +        QLock flushlock;
       +        int active;
       +        int infullscreen;
       +        int kalting;                // last keystroke was Kalt
       +        int touched;                // last mouse event was touchCallback
       +        NSMutableArray* devicelist;
       +} osx;
       +
       +/* 
       + These structs are required, in order to handle some parameters returned from the 
       + Support.framework 
       + */ 
       +typedef struct { 
       +        float x; 
       +        float y; 
       +}mtPoint; 
       +
       +typedef struct { 
       +        mtPoint position; 
       +        mtPoint velocity; 
       +}mtReadout; 
       +
       +/* 
       + Some reversed engineered informations from MultiTouchSupport.framework 
       + */ 
       +typedef struct 
       +{ 
       +        int frame; //the current frame 
       +        double timestamp; //event timestamp 
       +        int identifier; //identifier guaranteed unique for life of touch per device 
       +        int state; //the current state (not sure what the values mean) 
       +        int unknown1; //no idea what this does 
       +        int unknown2; //no idea what this does either 
       +        mtReadout normalized; //the normalized position and vector of the touch (0,0 to 1,1) 
       +        float size; //the size of the touch (the area of your finger being tracked) 
       +        int unknown3; //no idea what this does 
       +        float angle; //the angle of the touch -| 
       +        float majorAxis; //the major axis of the touch -|-- an ellipsoid. you can track the angle of each finger! 
       +        float minorAxis; //the minor axis of the touch -| 
       +        mtReadout unknown4; //not sure what this is for 
       +        int unknown5[2]; //no clue 
       +        float unknown6; //no clue 
       +}Touch; 
       +
       +//a reference pointer for the multitouch device 
       +typedef void *MTDeviceRef; 
       +
       +//the prototype for the callback function 
       +typedef int (*MTContactCallbackFunction)(int,Touch*,int,double,int); 
       +
       +//returns a pointer to the default device (the trackpad?) 
       +MTDeviceRef MTDeviceCreateDefault(void); 
       +
       +//returns a CFMutableArrayRef array of all multitouch devices 
       +CFMutableArrayRef MTDeviceCreateList(void); 
       +
       +//registers a device's frame callback to your callback function 
       +void MTRegisterContactFrameCallback(MTDeviceRef, MTContactCallbackFunction); 
       +
       +//start sending events 
       +void MTDeviceStart(MTDeviceRef, int); 
       +void MTDeviceStop(MTDeviceRef); 
       +
       +#define kNTracks 10
       +struct TouchTrack {
       +        int id;
       +        float firstThreshTime;
       +        mtPoint pos;
       +} tracks[kNTracks];
       +
       +#define kSizeSensitivity 1.25f
       +#define kTimeSensitivity 0.03f /* seconds */
       +#define kButtonLimit 0.6f /* percentage from base of pad */
       +
       +int
       +findTrack(int id)
       +{
       +        int i;
       +        for(i = 0; i < kNTracks; ++i)
       +                if(tracks[i].id == id)
       +                        return i;
       +        return -1;
       +}
       +
       +#define kMoveSensitivity 0.05f
       +
       +int
       +moved(mtPoint a, mtPoint b)
       +{
       +        if(fabs(a.x - b.x) > kMoveSensitivity)
       +                return 1;
       +        if(fabs(a.y - b.y) > kMoveSensitivity)
       +                return 1;
       +        return 0;
       +}
       +
       +int
       +classifyTouch(Touch *t)
       +{
       +        mtPoint p;
       +        int i;
       +
       +        p = t->normalized.position;
       +
       +        i = findTrack(t->identifier);
       +        if(i == -1) {
       +                i = findTrack(-1);
       +                if(i == -1)
       +                        return 0;        // No empty tracks.
       +                tracks[i].id = t->identifier;
       +                tracks[i].firstThreshTime = t->timestamp;
       +                tracks[i].pos = p;
       +                // we don't have a touch yet - we wait kTimeSensitivity before reporting it.
       +                return 0;
       +        }
       +                
       +        if(t->size == 0) { // lost touch
       +                tracks[i].id = -1;
       +                return 0;
       +        }
       +        if(t->size < kSizeSensitivity) {
       +                tracks[i].firstThreshTime = t->timestamp;
       +        }
       +        if((t->timestamp - tracks[i].firstThreshTime) < kTimeSensitivity) {
       +                return 0;
       +        }
       +        if(p.y > kButtonLimit && t->size > kSizeSensitivity ) {
       +                if(p.x < 0.35)
       +                        return 1;
       +                if(p.x > 0.65)
       +                        return 4;
       +                if(p.x > 0.35 && p.x < 0.65)
       +                        return 2;
       +        }
       +        return 0;
       +}
       +
       +static ulong msec(void);
       +
       +int
       +touchCallback(int device, Touch *data, int nFingers, double timestamp, int frame)
       +{
       +#ifdef MULTITOUCH
       +        int buttons, delta, i;
       +        static int obuttons;
       +        CGPoint p;
       +        CGEventRef e;
       +
       +        osx.touched = 1;
       +        buttons = 0;
       +        for(i = 0; i < nFingers; ++i)
       +                buttons |= classifyTouch(data+i);
       +        delta = buttons ^ obuttons;
       +        obuttons = buttons;
       +        p.x = osx.xy.x+osx.screenr.min.x;
       +        p.y = osx.xy.y+osx.screenr.min.y;
       +        if(delta & 1) {
       +                e = CGEventCreateMouseEvent(NULL, 
       +                        (buttons & 1) ? kCGEventOtherMouseDown : kCGEventOtherMouseUp, 
       +                        p,
       +                        29);
       +                CGEventPost(kCGSessionEventTap, e);
       +                CFRelease(e);
       +        }
       +        if(delta & 2) {
       +                e = CGEventCreateMouseEvent(NULL,
       +                        (buttons & 2) ? kCGEventOtherMouseDown : kCGEventOtherMouseUp, 
       +                        p,
       +                        30);
       +                CGEventPost(kCGSessionEventTap, e);
       +                CFRelease(e);
       +        }
       +        if(delta & 4){
       +                e = CGEventCreateMouseEvent(NULL, 
       +                        (buttons & 4) ? kCGEventOtherMouseDown : kCGEventOtherMouseUp, 
       +                        p,
       +                        31);
       +                CGEventPost(kCGSessionEventTap, e);
       +                CFRelease(e);
       +        }
       +        return delta != 0;
       +#else
       +        return 0;
       +#endif
       +}
       +
       +extern int multitouch;
       +
       +enum
       +{
       +        WindowAttrs =
       +                kWindowCloseBoxAttribute |
       +                kWindowCollapseBoxAttribute |
       +                kWindowResizableAttribute |
       +                kWindowStandardHandlerAttribute |
       +                kWindowFullZoomAttribute
       +};
       +
       +enum
       +{
       +        P9PEventLabelUpdate = 1
       +};
       +
       +static void screenproc(void*);
       +static void eresized(int);
       +static void fullscreen(int);
       +static void seticon(void);
       +static void activated(int);
       +
       +static OSStatus quithandler(EventHandlerCallRef, EventRef, void*);
       +static OSStatus eventhandler(EventHandlerCallRef, EventRef, void*);
       +static OSStatus cmdhandler(EventHandlerCallRef, EventRef, void*);
       +
       +enum
       +{
       +        CmdFullScreen = 1,
       +};
       +
       +void screeninit(void);
       +void _flushmemscreen(Rectangle r);
       +
       +
       +static void
       +InitMultiTouch(void)
       +{
       +#ifdef MULTITOUCH
       +        int i;
       +
       +        /*
       +         * Setup multitouch queues
       +         */
       +        if(!multitouch)
       +                return;
       +
       +        for(i = 0; i<kNTracks; ++i)
       +                tracks[i].id = -1;
       +
       +        osx.devicelist = (NSMutableArray*)MTDeviceCreateList(); //grab our device list 
       +        for(i = 0; i<[osx.devicelist count]; i++) { //iterate available devices 
       +                MTRegisterContactFrameCallback([osx.devicelist objectAtIndex:i], touchCallback); //assign callback for device 
       +        }
       +#endif
       +}
       +
       +Memimage*
       +attachscreen(char *label, char *winsize)
       +{
       +        if(label == nil)
       +                label = "gnot a label";
       +        osx.label = strdup(label);
       +        osx.winsize = winsize;
       +        if(osx.screenimage == nil){
       +                screeninit();
       +                if(osx.screenimage == nil)
       +                        panic("cannot create OS X screen");
       +        }
       +        return osx.screenimage;
       +}
       +
       +extern int multitouch;
       +
       +void
       +_screeninit(void)
       +{
       +        CGRect cgr;
       +        OSXRect or;
       +        Rectangle r;
       +        int havemin;
       +
       +        memimageinit();
       +
       +        ProcessSerialNumber psn = { 0, kCurrentProcess };
       +        TransformProcessType(&psn, kProcessTransformToForegroundApplication);
       +        SetFrontProcess(&psn);
       +
       +        cgr = CGDisplayBounds(CGMainDisplayID());
       +        osx.fullscreenr = Rect(0, 0, cgr.size.width, cgr.size.height);
       +        
       +        InitCursor();
       +        
       +        // Create minimal menu with full-screen option.
       +        ClearMenuBar();
       +        CreateStandardWindowMenu(0, &osx.wmenu);
       +        InsertMenu(osx.wmenu, 0);
       +        MenuItemIndex ix;
       +        CreateNewMenu(1004, 0, &osx.vmenu);        // XXX 1004?
       +        SetMenuTitleWithCFString(osx.vmenu, CFSTR("View"));
       +        AppendMenuItemTextWithCFString(osx.vmenu,
       +                CFSTR("Full Screen"), 0, CmdFullScreen, &ix);
       +        SetMenuItemCommandKey(osx.vmenu, ix, 0, 'F');
       +        AppendMenuItemTextWithCFString(osx.vmenu,
       +                CFSTR("Cmd-F exits full screen"),
       +                kMenuItemAttrDisabled, CmdFullScreen, &ix);
       +        InsertMenu(osx.vmenu, GetMenuID(osx.wmenu));
       +        DrawMenuBar();
       +
       +        // Create the window.
       +        r = Rect(0, 0, Dx(osx.fullscreenr)*2/3, Dy(osx.fullscreenr)*2/3);
       +        havemin = 0;
       +        if(osx.winsize && osx.winsize[0]){
       +                if(parsewinsize(osx.winsize, &r, &havemin) < 0)
       +                        sysfatal("%r");
       +        }
       +        if(!havemin)
       +                r = rectaddpt(r, Pt((Dx(osx.fullscreenr)-Dx(r))/2, (Dy(osx.fullscreenr)-Dy(r))/2));
       +        or.left = r.min.x;
       +        or.top = r.min.y;
       +        or.right = r.max.x;
       +        or.bottom = r.max.y;
       +        CreateNewWindow(kDocumentWindowClass, WindowAttrs, &or, &osx.window);
       +        setlabel(osx.label);
       +        seticon();
       +        
       +        // Set up the clip board.
       +        if(PasteboardCreate(kPasteboardClipboard, &osx.snarf) != noErr)
       +                panic("pasteboard create");
       +
       +        // Explain in great detail which events we want to handle.
       +        // Why can't we just have one handler?
       +        const EventTypeSpec quits[] = {
       +                { kEventClassApplication, kEventAppQuit }
       +        };
       +        const EventTypeSpec cmds[] = {
       +                { kEventClassWindow, kEventWindowClosed },
       +                { kEventClassWindow, kEventWindowBoundsChanged },
       +                { kEventClassCommand, kEventCommandProcess },
       +                { kEventClassWindow, kEventWindowActivated },
       +                { kEventClassWindow, kEventWindowDeactivated },
       +        };
       +        const EventTypeSpec events[] = {
       +                { kEventClassApplication, kEventAppShown },
       +                { kEventClassKeyboard, kEventRawKeyDown },
       +                { kEventClassKeyboard, kEventRawKeyModifiersChanged },
       +                { kEventClassKeyboard, kEventRawKeyRepeat },
       +                { kEventClassMouse, kEventMouseDown },
       +                { kEventClassMouse, kEventMouseUp },
       +                { kEventClassMouse, kEventMouseMoved },
       +                { kEventClassMouse, kEventMouseDragged },
       +                { kEventClassMouse, kEventMouseWheelMoved },
       +                { 'P9PE', P9PEventLabelUpdate}
       +        };
       +
       +        InstallApplicationEventHandler(
       +                NewEventHandlerUPP(quithandler),
       +                nelem(quits), quits, nil, nil);
       +
       +         InstallApplicationEventHandler(
       +                 NewEventHandlerUPP(eventhandler),
       +                nelem(events), events, nil, nil);
       +
       +        InstallWindowEventHandler(osx.window,
       +                NewEventHandlerUPP(cmdhandler),
       +                nelem(cmds), cmds, osx.window, nil);
       +
       +        // Finally, put the window on the screen.
       +        ShowWindow(osx.window);
       +        ShowMenuBar();
       +        eresized(0);
       +        SelectWindow(osx.window);
       +
       +        if(multitouch)
       +                InitMultiTouch();
       +        
       +        InitCursor();
       +}
       +
       +static Rendez scr;
       +static QLock slock;
       +
       +void
       +screeninit(void)
       +{
       +        scr.l = &slock;
       +        qlock(scr.l);
       +        proccreate(screenproc, nil, 256*1024);
       +        while(osx.window == nil)
       +                rsleep(&scr);
       +        qunlock(scr.l);
       +}
       +
       +static void
       +screenproc(void *v)
       +{
       +        qlock(scr.l);
       +        _screeninit();
       +        rwakeup(&scr);
       +        qunlock(scr.l);
       +        RunApplicationEventLoop();
       +}
       +
       +static OSStatus kbdevent(EventRef);
       +static OSStatus mouseevent(EventRef);
       +
       +static OSStatus
       +cmdhandler(EventHandlerCallRef next, EventRef event, void *arg)
       +{
       +        return eventhandler(next, event, arg);
       +}
       +
       +static OSStatus
       +quithandler(EventHandlerCallRef next, EventRef event, void *arg)
       +{
       +        exit(0);
       +        return 0;
       +}
       +
       +static OSStatus
       +eventhandler(EventHandlerCallRef next, EventRef event, void *arg)
       +{
       +        OSStatus result;
       +
       +        result = CallNextEventHandler(next, event);
       +
       +        switch(GetEventClass(event)){
       +
       +        case 'P9PE':
       +                if(GetEventKind(event) == P9PEventLabelUpdate) {
       +                        qlock(&osx.labellock);
       +                        setlabel(osx.label);
       +                        qunlock(&osx.labellock);
       +                        return noErr;
       +                } else
       +                        return eventNotHandledErr;
       +
       +        case kEventClassApplication:;
       +                Rectangle r = Rect(0, 0, Dx(osx.screenr), Dy(osx.screenr));
       +                _flushmemscreen(r);
       +                return eventNotHandledErr;
       +
       +        case kEventClassKeyboard:
       +                return kbdevent(event);
       +        
       +        case kEventClassMouse:
       +                return mouseevent(event);
       +        
       +        case kEventClassCommand:;
       +                HICommand cmd;
       +                GetEventParameter(event, kEventParamDirectObject,
       +                        typeHICommand, nil, sizeof cmd, nil, &cmd);
       +                switch(cmd.commandID){
       +                case kHICommandQuit:
       +                        exit(0);
       +                
       +                case CmdFullScreen:
       +                        fullscreen(1);
       +                        break;
       +                
       +                default:
       +                        return eventNotHandledErr;
       +                }
       +                break;
       +        
       +        case kEventClassWindow:
       +                switch(GetEventKind(event)){
       +                case kEventWindowClosed:
       +                        exit(0);
       +                
       +                case kEventWindowBoundsChanged:
       +                        eresized(1);
       +                        break;
       +                
       +                case kEventWindowActivated:
       +                        activated(1);
       +                        return eventNotHandledErr;
       +                                        
       +                case kEventWindowDeactivated:
       +                        activated(0);
       +                        return eventNotHandledErr;
       +
       +                default:
       +                        return eventNotHandledErr;
       +                }
       +                break;
       +        }
       +        
       +        return result;
       +}
       +
       +static ulong
       +msec(void)
       +{
       +        return nsec()/1000000;
       +}
       +
       +static OSStatus
       +mouseevent(EventRef event)
       +{
       +        int wheel;
       +        OSXPoint op;
       +        
       +        GetEventParameter(event, kEventParamMouseLocation,
       +                typeQDPoint, 0, sizeof op, 0, &op);
       +
       +        osx.xy = subpt(Pt(op.h, op.v), osx.screenr.min);
       +        wheel = 0;
       +
       +        switch(GetEventKind(event)){
       +        case kEventMouseWheelMoved:;
       +                SInt32 delta;
       +                GetEventParameter(event, kEventParamMouseWheelDelta,
       +                        typeSInt32, 0, sizeof delta, 0, &delta);
       +                
       +                // if I have any active touches in my region, I need to ignore the wheel motion.
       +                //int i;
       +                //for(i = 0; i < kNTracks; ++i) {
       +                //        if(tracks[i].id != -1 && tracks[i].pos.y > kButtonLimit) break;
       +                //}
       +                //if(i == kNTracks) { // No active touches, go ahead and scroll.
       +                        if(delta > 0)
       +                                wheel = 8;
       +                        else
       +                                wheel = 16;
       +                //}
       +                break;
       +        
       +        case kEventMouseDown:
       +        case kEventMouseUp:;
       +                UInt32 but, mod;
       +                GetEventParameter(event, kEventParamMouseChord,
       +                        typeUInt32, 0, sizeof but, 0, &but);
       +                GetEventParameter(event, kEventParamKeyModifiers,
       +                        typeUInt32, 0, sizeof mod, 0, &mod);
       +                
       +                if(osx.touched) {
       +                        // in multitouch we use the clicks down to enable our 
       +                        // virtual buttons.
       +                        if(but & 0x3)
       +                                but = but >> 29;
       +                        else 
       +                                but = 0;
       +                        osx.touched = 0;
       +                } else {
       +                        // OS X swaps button 2 and 3
       +                        but = (but & ~6) | ((but & 4)>>1) | ((but&2)<<1);
       +                        but = mouseswap(but);
       +                }
       +
       +                // Apply keyboard modifiers and pretend it was a real mouse button.
       +                // (Modifiers typed while holding the button go into kbuttons,
       +                // but this one does not.)
       +                if(but == 1){
       +                        if(mod & optionKey) {
       +                                // Take the ALT away from the keyboard handler.
       +                                if(osx.kalting) {
       +                                        osx.kalting = 0;
       +                                        keystroke(Kalt);
       +                                }
       +                                but = 2;
       +                        }
       +                        else if(mod & cmdKey)
       +                                but = 4;
       +                }
       +                osx.buttons = but;
       +                break;
       +
       +        case kEventMouseMoved:
       +        case kEventMouseDragged:
       +                break;
       +        
       +        default:
       +                return eventNotHandledErr;
       +        }
       +
       +        mousetrack(osx.xy.x, osx.xy.y, osx.buttons|osx.kbuttons|wheel, msec());
       +        return noErr;        
       +}
       +
       +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',
       +};
       +
       +static OSStatus
       +kbdevent(EventRef event)
       +{
       +        char ch;
       +        UInt32 code;
       +        UInt32 mod;
       +        int k;
       +
       +        GetEventParameter(event, kEventParamKeyMacCharCodes,
       +                typeChar, nil, sizeof ch, nil, &ch);
       +        GetEventParameter(event, kEventParamKeyCode,
       +                typeUInt32, nil, sizeof code, nil, &code);
       +        GetEventParameter(event, kEventParamKeyModifiers,
       +                typeUInt32, nil, sizeof mod, nil, &mod);
       +
       +        switch(GetEventKind(event)){
       +        case kEventRawKeyDown:
       +        case kEventRawKeyRepeat:
       +                osx.kalting = 0;
       +                if(mod == cmdKey){
       +                        if(ch == 'F' || ch == 'f'){
       +                                if(osx.isfullscreen && msec() - osx.fullscreentime > 500)
       +                                        fullscreen(0);
       +                                return noErr;
       +                        }
       +                        
       +                        // Pass most Cmd keys through as Kcmd + ch.
       +                        // OS X interprets a few no matter what we do,
       +                        // so it is useless to pass them through as keystrokes too.
       +                        switch(ch) {
       +                        case 'm':        // minimize window
       +                        case 'h':        // hide window
       +                        case 'H':        // hide others
       +                        case 'q':        // quit
       +                                return eventNotHandledErr;
       +                        }
       +                        if(' ' <= ch && ch <= '~') {
       +                                keystroke(Kcmd + ch);
       +                                return noErr;
       +                        }
       +                        return eventNotHandledErr;
       +                }
       +                k = ch;
       +                if(code < nelem(keycvt) && keycvt[code])
       +                        k = keycvt[code];
       +                if(k == 0)
       +                        return noErr;
       +                if(k > 0)
       +                        keystroke(k);
       +                else{
       +                        UniChar uc;
       +                        OSStatus s;
       +
       +                        s = GetEventParameter(event, kEventParamKeyUnicodes,
       +                                typeUnicodeText, nil, sizeof uc, nil, &uc);
       +                        if(s == noErr)
       +                                keystroke(uc);
       +                }
       +                break;
       +
       +        case kEventRawKeyModifiersChanged:
       +                if(!osx.buttons && !osx.kbuttons){
       +                        if(mod == optionKey) {
       +                                osx.kalting = 1;
       +                                keystroke(Kalt);
       +                        }
       +                        break;
       +                }
       +                
       +                // If the mouse button is being held down, treat 
       +                // changes in the keyboard modifiers as changes
       +                // in the mouse buttons.
       +                osx.kbuttons = 0;
       +                if(mod & optionKey)
       +                        osx.kbuttons |= 2;
       +                if(mod & cmdKey)
       +                        osx.kbuttons |= 4;
       +                mousetrack(osx.xy.x, osx.xy.y, osx.buttons|osx.kbuttons, msec());
       +                break;
       +        }
       +        return noErr;
       +}
       +
       +static void
       +eresized(int new)
       +{
       +        Memimage *m;
       +        OSXRect or;
       +        ulong chan;
       +        Rectangle r;
       +        int bpl;
       +        CGDataProviderRef provider;
       +        CGImageRef image;
       +        CGColorSpaceRef cspace;
       +
       +        GetWindowBounds(osx.window, kWindowContentRgn, &or);
       +        r = Rect(or.left, or.top, or.right, or.bottom);
       +        if(Dx(r) == Dx(osx.screenr) && Dy(r) == Dy(osx.screenr)){
       +                // No need to make new image.
       +                osx.screenr = r;
       +                return;
       +        }
       +
       +        chan = XBGR32;
       +        m = allocmemimage(Rect(0, 0, Dx(r), Dy(r)), chan);
       +        if(m == nil)
       +                panic("allocmemimage: %r");
       +        if(m->data == nil)
       +                panic("m->data == nil");
       +        bpl = bytesperline(r, 32);
       +        provider = CGDataProviderCreateWithData(0,
       +                m->data->bdata, Dy(r)*bpl, 0);
       +        //cspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
       +        cspace = CGColorSpaceCreateDeviceRGB();
       +        image = CGImageCreate(Dx(r), Dy(r), 8, 32, bpl,
       +                cspace,
       +                kCGImageAlphaNoneSkipLast,
       +                provider, 0, 0, kCGRenderingIntentDefault);
       +        CGColorSpaceRelease(cspace);
       +        CGDataProviderRelease(provider);        // CGImageCreate did incref
       +        
       +        mouserect = m->r;
       +        if(new){
       +                mouseresized = 1;
       +                mousetrack(osx.xy.x, osx.xy.y, osx.buttons|osx.kbuttons, msec());
       +        }
       +//        termreplacescreenimage(m);
       +        _drawreplacescreenimage(m);        // frees old osx.screenimage if any
       +        if(osx.image)
       +                CGImageRelease(osx.image);
       +        osx.image = image;
       +        osx.screenimage = m;
       +        osx.screenr = r;
       +        
       +        // I'm not 100% sure why this is necessary
       +        // but otherwise some resizes (esp. vertical ones)
       +        // stop updating the screen.
       +        qlock(&osx.flushlock);
       +        QDEndCGContext(GetWindowPort(osx.window), &osx.windowctx);
       +        osx.windowctx = nil;
       +        qunlock(&osx.flushlock);
       +}
       +
       +void
       +flushproc(void *v)
       +{
       +        for(;;){
       +                if(osx.needflush && osx.windowctx && canqlock(&osx.flushlock)){
       +                        if(osx.windowctx){
       +                                CGContextFlush(osx.windowctx);
       +                                osx.needflush = 0;
       +                        }
       +                        qunlock(&osx.flushlock);
       +                }
       +                usleep(33333);
       +        }
       +}
       +
       +void
       +_flushmemscreen(Rectangle r)
       +{
       +        CGRect cgr;
       +        CGImageRef subimg;
       +
       +        qlock(&osx.flushlock);
       +        if(osx.windowctx == nil){
       +                QDBeginCGContext(GetWindowPort(osx.window), &osx.windowctx);
       +                proccreate(flushproc, nil, 256*1024);
       +        }
       +        
       +        cgr.origin.x = r.min.x;
       +        cgr.origin.y = r.min.y;
       +        cgr.size.width = Dx(r);
       +        cgr.size.height = Dy(r);
       +        subimg = CGImageCreateWithImageInRect(osx.image, cgr);
       +        cgr.origin.y = Dy(osx.screenr) - r.max.y; // XXX how does this make any sense?
       +        CGContextDrawImage(osx.windowctx, cgr, subimg);
       +        osx.needflush = 1;
       +        qunlock(&osx.flushlock);
       +        CGImageRelease(subimg);
       +}
       +
       +void
       +activated(int active)
       +{
       +#ifdef MULTITOUCH
       +        int i;
       +        if(active) {
       +                for(i = 0; i<[osx.devicelist count]; i++) { //iterate available devices 
       +                        MTDeviceStart([osx.devicelist objectAtIndex:i], 0); //start sending events 
       +                } 
       +        } else {
       +                for(i = 0; i<[osx.devicelist count]; i++) { //iterate available devices 
       +                        MTDeviceStop([osx.devicelist objectAtIndex:i]); //stop sending events 
       +                } 
       +                for(i = 0; i<kNTracks; ++i) {
       +                        tracks[i].id = -1;
       +                }
       +        }
       +#endif
       +        osx.active = active;
       +}
       +
       +void
       +fullscreen(int wascmd)
       +{
       +        static OSXRect oldrect;
       +        GDHandle device;
       +        OSXRect dr;
       +
       +        if(!wascmd)
       +                return;
       +        
       +        if(!osx.isfullscreen){
       +                GetWindowGreatestAreaDevice(osx.window,
       +                        kWindowTitleBarRgn, &device, nil);
       +                dr = (*device)->gdRect;
       +                if(dr.top == 0 && dr.left == 0)
       +                        HideMenuBar();
       +                GetWindowBounds(osx.window, kWindowContentRgn, &oldrect);
       +                ChangeWindowAttributes(osx.window,
       +                        kWindowNoTitleBarAttribute,
       +                        kWindowResizableAttribute);
       +                MoveWindow(osx.window, 0, 0, 1);
       +                MoveWindow(osx.window, dr.left, dr.top, 0);
       +                SizeWindow(osx.window,
       +                        dr.right - dr.left,
       +                        dr.bottom - dr.top, 0);
       +                osx.isfullscreen = 1;
       +        }else{
       +                ShowMenuBar();
       +                ChangeWindowAttributes(osx.window,
       +                        kWindowResizableAttribute,
       +                        kWindowNoTitleBarAttribute);
       +                SizeWindow(osx.window,
       +                        oldrect.right - oldrect.left,
       +                        oldrect.bottom - oldrect.top, 0);
       +                MoveWindow(osx.window, oldrect.left, oldrect.top, 0);
       +                osx.isfullscreen = 0;
       +        }
       +        eresized(1);
       +}
       +                
       +void
       +setmouse(Point p)
       +{
       +        CGPoint cgp;
       +        
       +        cgp.x = p.x + osx.screenr.min.x;
       +        cgp.y = p.y + osx.screenr.min.y;
       +        CGWarpMouseCursorPosition(cgp);
       +        osx.xy = p;
       +}
       +
       +void
       +setcursor(Cursor *c)
       +{
       +        OSXCursor oc;
       +        int i;
       +
       +        if(c == nil){
       +                InitCursor();
       +                return;
       +        }
       +        
       +        // SetCursor is deprecated, but what replaces it?
       +        for(i=0; i<16; i++){
       +                oc.data[i] = ((ushort*)c->set)[i];
       +                oc.mask[i] = oc.data[i] | ((ushort*)c->clr)[i];
       +        }
       +        oc.hotSpot.h = - c->offset.x;
       +        oc.hotSpot.v = - c->offset.y;
       +        SetCursor(&oc);
       +}
       +
       +void
       +getcolor(ulong i, ulong *r, ulong *g, ulong *b)
       +{
       +        ulong v;
       +        
       +        v = 0;
       +        *r = (v>>16)&0xFF;
       +        *g = (v>>8)&0xFF;
       +        *b = v&0xFF;
       +}
       +
       +int
       +setcolor(ulong i, ulong r, ulong g, ulong b)
       +{
       +        /* no-op */
       +        return 0;
       +}
       +
       +
       +int
       +hwdraw(Memdrawparam *p)
       +{
       +        return 0;
       +}
       +
       +struct {
       +        QLock lk;
       +        char buf[SnarfSize];
       +        Rune rbuf[SnarfSize];
       +        PasteboardRef apple;
       +} clip;
       +
       +char*
       +getsnarf(void)
       +{
       +        char *s;
       +        CFArrayRef flavors;
       +        CFDataRef data;
       +        CFIndex nflavor, ndata, j;
       +        CFStringRef type;
       +        ItemCount nitem;
       +        PasteboardItemID id;
       +        PasteboardSyncFlags flags;
       +        UInt32 i;
       +        u16int *u;
       +        Fmt fmt;
       +        Rune r;
       +
       +/*        fprint(2, "applegetsnarf\n"); */
       +        qlock(&clip.lk);
       +        clip.apple = osx.snarf;
       +        if(clip.apple == nil){
       +                if(PasteboardCreate(kPasteboardClipboard, &clip.apple) != noErr){
       +                        fprint(2, "apple pasteboard create failed\n");
       +                        qunlock(&clip.lk);
       +                        return nil;
       +                }
       +        }
       +        flags = PasteboardSynchronize(clip.apple);
       +        if(flags&kPasteboardClientIsOwner){
       +                s = strdup(clip.buf);
       +                qunlock(&clip.lk);
       +                return s;
       +        }
       +        if(PasteboardGetItemCount(clip.apple, &nitem) != noErr){
       +                fprint(2, "apple pasteboard get item count failed\n");
       +                qunlock(&clip.lk);
       +                return nil;
       +        }
       +        for(i=1; i<=nitem; i++){
       +                if(PasteboardGetItemIdentifier(clip.apple, i, &id) != noErr)
       +                        continue;
       +                if(PasteboardCopyItemFlavors(clip.apple, id, &flavors) != noErr)
       +                        continue;
       +                nflavor = CFArrayGetCount(flavors);
       +                for(j=0; j<nflavor; j++){
       +                        type = (CFStringRef)CFArrayGetValueAtIndex(flavors, j);
       +                        if(!UTTypeConformsTo(type, CFSTR("public.utf16-plain-text")))
       +                                continue;
       +                        if(PasteboardCopyItemFlavorData(clip.apple, id, type, &data) != noErr)
       +                                continue;
       +                        qunlock(&clip.lk);
       +                        ndata = CFDataGetLength(data)/2;
       +                        u = (u16int*)CFDataGetBytePtr(data);
       +                        fmtstrinit(&fmt);
       +                        // decode utf-16.  what was apple thinking?
       +                        for(i=0; i<ndata; i++) {
       +                                r = u[i];
       +                                if(0xd800 <= r && r < 0xdc00 && i+1 < ndata && 0xdc00 <= u[i+1] && u[i+1] < 0xe000) {
       +                                        r = (((r - 0xd800)<<10) |  (u[i+1] - 0xdc00)) + 0x10000;
       +                                        i++;
       +                                }
       +                                else if(0xd800 <= r && r < 0xe000)
       +                                        r = Runeerror;
       +                                if(r == '\r')
       +                                        r = '\n';
       +                                fmtrune(&fmt, r);
       +                        }
       +                        CFRelease(flavors);
       +                        CFRelease(data);
       +                        return fmtstrflush(&fmt);
       +                }
       +                CFRelease(flavors);
       +        }
       +        qunlock(&clip.lk);
       +        return nil;                
       +}
       +
       +void
       +putsnarf(char *s)
       +{
       +        CFDataRef cfdata;
       +        PasteboardSyncFlags flags;
       +        u16int *u, *p;
       +        Rune r;
       +        int i;
       +
       +/*        fprint(2, "appleputsnarf\n"); */
       +
       +        if(strlen(s) >= SnarfSize)
       +                return;
       +        qlock(&clip.lk);
       +        strcpy(clip.buf, s);
       +        runesnprint(clip.rbuf, nelem(clip.rbuf), "%s", s);
       +        clip.apple = osx.snarf;
       +        if(PasteboardClear(clip.apple) != noErr){
       +                fprint(2, "apple pasteboard clear failed\n");
       +                qunlock(&clip.lk);
       +                return;
       +        }
       +        flags = PasteboardSynchronize(clip.apple);
       +        if((flags&kPasteboardModified) || !(flags&kPasteboardClientIsOwner)){
       +                fprint(2, "apple pasteboard cannot assert ownership\n");
       +                qunlock(&clip.lk);
       +                return;
       +        }
       +        u = malloc(runestrlen(clip.rbuf)*4);
       +        p = u;
       +        for(i=0; clip.rbuf[i]; i++) {
       +                r = clip.rbuf[i];
       +                // convert to utf-16
       +                if(0xd800 <= r && r < 0xe000)
       +                        r = Runeerror;
       +                if(r >= 0x10000) {
       +                        r -= 0x10000;
       +                        *p++ = 0xd800 + (r>>10);
       +                        *p++ = 0xdc00 + (r & ((1<<10)-1));
       +                } else
       +                        *p++ = r;
       +        }
       +        cfdata = CFDataCreate(kCFAllocatorDefault, 
       +                (uchar*)u, (p-u)*2);
       +        free(u);
       +        if(cfdata == nil){
       +                fprint(2, "apple pasteboard cfdatacreate failed\n");
       +                qunlock(&clip.lk);
       +                return;
       +        }
       +        if(PasteboardPutItemFlavor(clip.apple, (PasteboardItemID)1,
       +                CFSTR("public.utf16-plain-text"), cfdata, 0) != noErr){
       +                fprint(2, "apple pasteboard putitem failed\n");
       +                CFRelease(cfdata);
       +                qunlock(&clip.lk);
       +                return;
       +        }
       +        CFRelease(cfdata);
       +        qunlock(&clip.lk);
       +}
       +
       +void
       +setlabel(char *label)
       +{
       +        CFStringRef cs;
       +
       +        cs = CFStringCreateWithBytes(nil, (uchar*)label, strlen(label), kCFStringEncodingUTF8, false);
       +        SetWindowTitleWithCFString(osx.window, cs);
       +        CFRelease(cs);
       +}
       +
       +void
       +kicklabel(char *label)
       +{
       +        char *p;
       +        EventRef e;
       +
       +        p = strdup(label);
       +        if(p == nil)
       +                return;
       +        qlock(&osx.labellock);
       +        free(osx.label);
       +        osx.label = p;
       +        qunlock(&osx.labellock);
       +        
       +        CreateEvent(nil, 'P9PE', P9PEventLabelUpdate, 0, kEventAttributeUserEvent, &e);
       +        PostEventToQueue(GetMainEventQueue(), e, kEventPriorityStandard);
       +        
       +}
       +
       +static void
       +seticon(void)
       +{
       +        CGImageRef im;
       +        CGDataProviderRef d;
       +
       +        d = CGDataProviderCreateWithData(nil, glenda_png, sizeof glenda_png, nil);
       +        im = CGImageCreateWithPNGDataProvider(d, nil, true, kCGRenderingIntentDefault);
       +        if(im)
       +                SetApplicationDockTileImage(im);
       +        CGImageRelease(im);
       +        CGDataProviderRelease(d);
       +}
       +
   DIR diff --git a/src/cmd/devdraw/osx-screen.c b/src/cmd/devdraw/osx-screen.c
       t@@ -1,913 +0,0 @@
       -#define Point OSXPoint
       -#define Rect OSXRect
       -#define Cursor OSXCursor
       -#include <Carbon/Carbon.h>
       -#undef Rect
       -#undef Point
       -#undef Cursor
       -#undef offsetof
       -#undef nil
       -
       -#include "u.h"
       -#include "libc.h"
       -#include <thread.h>
       -#include <draw.h>
       -#include <memdraw.h>
       -#include <keyboard.h>
       -#include "mouse.h"
       -#include <cursor.h>
       -#include "osx-screen.h"
       -#include "osx-keycodes.h"
       -#include "devdraw.h"
       -#include "glendapng.h"
       -
       -AUTOFRAMEWORK(Carbon)
       -
       -#define panic sysfatal
       -
       -extern Rectangle mouserect;
       -
       -struct {
       -        char *label;
       -        char *winsize;
       -        QLock labellock;
       -
       -        Rectangle fullscreenr;
       -        Rectangle screenr;
       -        Memimage *screenimage;
       -        int isfullscreen;
       -        ulong fullscreentime;
       -        
       -        Point xy;
       -        int buttons;
       -        int kbuttons;
       -
       -        CGDataProviderRef provider;
       -        MenuRef wmenu;
       -        MenuRef vmenu;
       -        WindowRef window;
       -        CGImageRef image;
       -        CGContextRef windowctx;
       -        PasteboardRef snarf;
       -        int needflush;
       -        QLock flushlock;
       -        int active;
       -        int infullscreen;
       -        int kalting;                // last keystroke was Kalt
       -} osx;
       -
       -enum
       -{
       -        WindowAttrs =
       -                kWindowCloseBoxAttribute |
       -                kWindowCollapseBoxAttribute |
       -                kWindowResizableAttribute |
       -                kWindowStandardHandlerAttribute |
       -                kWindowFullZoomAttribute
       -};
       -
       -enum
       -{
       -        P9PEventLabelUpdate = 1
       -};
       -
       -static void screenproc(void*);
       -static void eresized(int);
       -static void fullscreen(int);
       -static void seticon(void);
       -static void activated(int);
       -
       -static OSStatus quithandler(EventHandlerCallRef, EventRef, void*);
       -static OSStatus eventhandler(EventHandlerCallRef, EventRef, void*);
       -static OSStatus cmdhandler(EventHandlerCallRef, EventRef, void*);
       -
       -enum
       -{
       -        CmdFullScreen = 1,
       -};
       -
       -void screeninit(void);
       -void _flushmemscreen(Rectangle r);
       -
       -Memimage*
       -attachscreen(char *label, char *winsize)
       -{
       -        if(label == nil)
       -                label = "gnot a label";
       -        osx.label = strdup(label);
       -        osx.winsize = winsize;
       -        if(osx.screenimage == nil){
       -                screeninit();
       -                if(osx.screenimage == nil)
       -                        panic("cannot create OS X screen");
       -        }
       -        return osx.screenimage;
       -}
       -
       -void
       -_screeninit(void)
       -{
       -        CGRect cgr;
       -        OSXRect or;
       -        Rectangle r;
       -        int havemin;
       -
       -        memimageinit();
       -
       -        ProcessSerialNumber psn = { 0, kCurrentProcess };
       -        TransformProcessType(&psn, kProcessTransformToForegroundApplication);
       -        SetFrontProcess(&psn);
       -
       -        cgr = CGDisplayBounds(CGMainDisplayID());
       -        osx.fullscreenr = Rect(0, 0, cgr.size.width, cgr.size.height);
       -        
       -        InitCursor();
       -        
       -        // Create minimal menu with full-screen option.
       -        ClearMenuBar();
       -        CreateStandardWindowMenu(0, &osx.wmenu);
       -        InsertMenu(osx.wmenu, 0);
       -        MenuItemIndex ix;
       -        CreateNewMenu(1004, 0, &osx.vmenu);        // XXX 1004?
       -        SetMenuTitleWithCFString(osx.vmenu, CFSTR("View"));
       -        AppendMenuItemTextWithCFString(osx.vmenu,
       -                CFSTR("Full Screen"), 0, CmdFullScreen, &ix);
       -        SetMenuItemCommandKey(osx.vmenu, ix, 0, 'F');
       -        AppendMenuItemTextWithCFString(osx.vmenu,
       -                CFSTR("Cmd-F exits full screen"),
       -                kMenuItemAttrDisabled, CmdFullScreen, &ix);
       -        InsertMenu(osx.vmenu, GetMenuID(osx.wmenu));
       -        DrawMenuBar();
       -
       -        // Create the window.
       -        r = Rect(0, 0, Dx(osx.fullscreenr)*2/3, Dy(osx.fullscreenr)*2/3);
       -        havemin = 0;
       -        if(osx.winsize && osx.winsize[0]){
       -                if(parsewinsize(osx.winsize, &r, &havemin) < 0)
       -                        sysfatal("%r");
       -        }
       -        if(!havemin)
       -                r = rectaddpt(r, Pt((Dx(osx.fullscreenr)-Dx(r))/2, (Dy(osx.fullscreenr)-Dy(r))/2));
       -        or.left = r.min.x;
       -        or.top = r.min.y;
       -        or.right = r.max.x;
       -        or.bottom = r.max.y;
       -        CreateNewWindow(kDocumentWindowClass, WindowAttrs, &or, &osx.window);
       -        setlabel(osx.label);
       -        seticon();
       -        
       -        // Set up the clip board.
       -        if(PasteboardCreate(kPasteboardClipboard, &osx.snarf) != noErr)
       -                panic("pasteboard create");
       -
       -        // Explain in great detail which events we want to handle.
       -        // Why can't we just have one handler?
       -        const EventTypeSpec quits[] = {
       -                { kEventClassApplication, kEventAppQuit }
       -        };
       -        const EventTypeSpec cmds[] = {
       -                { kEventClassWindow, kEventWindowClosed },
       -                { kEventClassWindow, kEventWindowBoundsChanged },
       -                { kEventClassCommand, kEventCommandProcess },
       -                { kEventClassWindow, kEventWindowActivated },
       -                { kEventClassWindow, kEventWindowDeactivated },
       -        };
       -        const EventTypeSpec events[] = {
       -                { kEventClassApplication, kEventAppShown },
       -                { kEventClassKeyboard, kEventRawKeyDown },
       -                { kEventClassKeyboard, kEventRawKeyModifiersChanged },
       -                { kEventClassKeyboard, kEventRawKeyRepeat },
       -                { kEventClassMouse, kEventMouseDown },
       -                { kEventClassMouse, kEventMouseUp },
       -                { kEventClassMouse, kEventMouseMoved },
       -                { kEventClassMouse, kEventMouseDragged },
       -                { kEventClassMouse, kEventMouseWheelMoved },
       -                { 'P9PE', P9PEventLabelUpdate}
       -        };
       -
       -        InstallApplicationEventHandler(
       -                NewEventHandlerUPP(quithandler),
       -                nelem(quits), quits, nil, nil);
       -
       -         InstallApplicationEventHandler(
       -                 NewEventHandlerUPP(eventhandler),
       -                nelem(events), events, nil, nil);
       -
       -        InstallWindowEventHandler(osx.window,
       -                NewEventHandlerUPP(cmdhandler),
       -                nelem(cmds), cmds, osx.window, nil);
       -
       -        // Finally, put the window on the screen.
       -        ShowWindow(osx.window);
       -        ShowMenuBar();
       -        eresized(0);
       -        SelectWindow(osx.window);
       -        
       -        InitCursor();
       -}
       -
       -static Rendez scr;
       -static QLock slock;
       -
       -void
       -screeninit(void)
       -{
       -        scr.l = &slock;
       -        qlock(scr.l);
       -        proccreate(screenproc, nil, 256*1024);
       -        while(osx.window == nil)
       -                rsleep(&scr);
       -        qunlock(scr.l);
       -}
       -
       -static void
       -screenproc(void *v)
       -{
       -        qlock(scr.l);
       -        _screeninit();
       -        rwakeup(&scr);
       -        qunlock(scr.l);
       -        RunApplicationEventLoop();
       -}
       -
       -static OSStatus kbdevent(EventRef);
       -static OSStatus mouseevent(EventRef);
       -
       -static OSStatus
       -cmdhandler(EventHandlerCallRef next, EventRef event, void *arg)
       -{
       -        return eventhandler(next, event, arg);
       -}
       -
       -static OSStatus
       -quithandler(EventHandlerCallRef next, EventRef event, void *arg)
       -{
       -        exit(0);
       -        return 0;
       -}
       -
       -static OSStatus
       -eventhandler(EventHandlerCallRef next, EventRef event, void *arg)
       -{
       -        OSStatus result;
       -
       -        result = CallNextEventHandler(next, event);
       -
       -        switch(GetEventClass(event)){
       -
       -        case 'P9PE':
       -                if (GetEventKind(event) == P9PEventLabelUpdate) {
       -                        qlock(&osx.labellock);
       -                        setlabel(osx.label);
       -                        qunlock(&osx.labellock);
       -                        return noErr;
       -                } else
       -                        return eventNotHandledErr;
       -
       -        case kEventClassApplication:;
       -                Rectangle r = Rect(0, 0, Dx(osx.screenr), Dy(osx.screenr));
       -                _flushmemscreen(r);
       -                return eventNotHandledErr;
       -
       -        case kEventClassKeyboard:
       -                return kbdevent(event);
       -        
       -        case kEventClassMouse:
       -                return mouseevent(event);
       -        
       -        case kEventClassCommand:;
       -                HICommand cmd;
       -                GetEventParameter(event, kEventParamDirectObject,
       -                        typeHICommand, nil, sizeof cmd, nil, &cmd);
       -                switch(cmd.commandID){
       -                case kHICommandQuit:
       -                        exit(0);
       -                
       -                case CmdFullScreen:
       -                        fullscreen(1);
       -                        break;
       -                
       -                default:
       -                        return eventNotHandledErr;
       -                }
       -                break;
       -        
       -        case kEventClassWindow:
       -                switch(GetEventKind(event)){
       -                case kEventWindowClosed:
       -                        exit(0);
       -                
       -                case kEventWindowBoundsChanged:
       -                        eresized(1);
       -                        break;
       -                
       -                case kEventWindowActivated:
       -                        activated(1);
       -                        return eventNotHandledErr;
       -                                        
       -                case kEventWindowDeactivated:
       -                        activated(0);
       -                        return eventNotHandledErr;
       -
       -                default:
       -                        return eventNotHandledErr;
       -                }
       -                break;
       -        }
       -        
       -        return result;
       -}
       -
       -static ulong
       -msec(void)
       -{
       -        return nsec()/1000000;
       -}
       -
       -static OSStatus
       -mouseevent(EventRef event)
       -{
       -        int wheel;
       -        OSXPoint op;
       -        
       -        GetEventParameter(event, kEventParamMouseLocation,
       -                typeQDPoint, 0, sizeof op, 0, &op);
       -
       -        osx.xy = subpt(Pt(op.h, op.v), osx.screenr.min);
       -        wheel = 0;
       -
       -        switch(GetEventKind(event)){
       -        case kEventMouseWheelMoved:;
       -                SInt32 delta;
       -                GetEventParameter(event, kEventParamMouseWheelDelta,
       -                        typeSInt32, 0, sizeof delta, 0, &delta);
       -                if(delta > 0)
       -                        wheel = 8;
       -                else
       -                        wheel = 16;
       -                break;
       -        
       -        case kEventMouseDown:
       -        case kEventMouseUp:;
       -                UInt32 but, mod;
       -                GetEventParameter(event, kEventParamMouseChord,
       -                        typeUInt32, 0, sizeof but, 0, &but);
       -                GetEventParameter(event, kEventParamKeyModifiers,
       -                        typeUInt32, 0, sizeof mod, 0, &mod);
       -                
       -                // OS X swaps button 2 and 3
       -                but = (but & ~6) | ((but & 4)>>1) | ((but&2)<<1);
       -
       -                but = mouseswap(but);
       -                
       -                // Apply keyboard modifiers and pretend it was a real mouse button.
       -                // (Modifiers typed while holding the button go into kbuttons,
       -                // but this one does not.)
       -                if(but == 1){
       -                        if(mod & optionKey) {
       -                                // Take the ALT away from the keyboard handler.
       -                                if(osx.kalting) {
       -                                        osx.kalting = 0;
       -                                        keystroke(Kalt);
       -                                }
       -                                but = 2;
       -                        }
       -                        else if(mod & cmdKey)
       -                                but = 4;
       -                }
       -                osx.buttons = but;
       -                break;
       -
       -        case kEventMouseMoved:
       -        case kEventMouseDragged:
       -                break;
       -        
       -        default:
       -                return eventNotHandledErr;
       -        }
       -
       -        mousetrack(osx.xy.x, osx.xy.y, osx.buttons|osx.kbuttons|wheel, msec());
       -        return noErr;        
       -}
       -
       -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',
       -};
       -
       -static OSStatus
       -kbdevent(EventRef event)
       -{
       -        char ch;
       -        UInt32 code;
       -        UInt32 mod;
       -        int k;
       -
       -        GetEventParameter(event, kEventParamKeyMacCharCodes,
       -                typeChar, nil, sizeof ch, nil, &ch);
       -        GetEventParameter(event, kEventParamKeyCode,
       -                typeUInt32, nil, sizeof code, nil, &code);
       -        GetEventParameter(event, kEventParamKeyModifiers,
       -                typeUInt32, nil, sizeof mod, nil, &mod);
       -
       -        switch(GetEventKind(event)){
       -        case kEventRawKeyDown:
       -        case kEventRawKeyRepeat:
       -                osx.kalting = 0;
       -                if(mod == cmdKey){
       -                        if(ch == 'F' || ch == 'f'){
       -                                if(osx.isfullscreen && msec() - osx.fullscreentime > 500)
       -                                        fullscreen(0);
       -                                return noErr;
       -                        }
       -                        
       -                        // Pass most Cmd keys through as Kcmd + ch.
       -                        // OS X interprets a few no matter what we do,
       -                        // so it is useless to pass them through as keystrokes too.
       -                        switch(ch) {
       -                        case 'm':        // minimize window
       -                        case 'h':        // hide window
       -                        case 'H':        // hide others
       -                        case 'q':        // quit
       -                                return eventNotHandledErr;
       -                        }
       -                        if(' ' <= ch && ch <= '~') {
       -                                keystroke(Kcmd + ch);
       -                                return noErr;
       -                        }
       -                        return eventNotHandledErr;
       -                }
       -                k = ch;
       -                if(code < nelem(keycvt) && keycvt[code])
       -                        k = keycvt[code];
       -                if(k == 0)
       -                        return noErr;
       -                else if(k > 0)
       -                        keystroke(k);
       -                else{
       -                        UniChar uc;
       -                        OSStatus s;
       -
       -                        s = GetEventParameter(event, kEventParamKeyUnicodes,
       -                                typeUnicodeText, nil, sizeof uc, nil, &uc);
       -                        if(s == noErr)
       -                                keystroke(uc);
       -                }
       -                break;
       -
       -        case kEventRawKeyModifiersChanged:
       -                if(!osx.buttons && !osx.kbuttons){
       -                        if(mod == optionKey) {
       -                                osx.kalting = 1;
       -                                keystroke(Kalt);
       -                        }
       -                        break;
       -                }
       -                
       -                // If the mouse button is being held down, treat 
       -                // changes in the keyboard modifiers as changes
       -                // in the mouse buttons.
       -                osx.kbuttons = 0;
       -                if(mod & optionKey)
       -                        osx.kbuttons |= 2;
       -                if(mod & cmdKey)
       -                        osx.kbuttons |= 4;
       -                mousetrack(osx.xy.x, osx.xy.y, osx.buttons|osx.kbuttons, msec());
       -                break;
       -        }
       -        return noErr;
       -}
       -
       -static void
       -eresized(int new)
       -{
       -        Memimage *m;
       -        OSXRect or;
       -        ulong chan;
       -        Rectangle r;
       -        int bpl;
       -        CGDataProviderRef provider;
       -        CGImageRef image;
       -        CGColorSpaceRef cspace;
       -
       -        GetWindowBounds(osx.window, kWindowContentRgn, &or);
       -        r = Rect(or.left, or.top, or.right, or.bottom);
       -        if(Dx(r) == Dx(osx.screenr) && Dy(r) == Dy(osx.screenr)){
       -                // No need to make new image.
       -                osx.screenr = r;
       -                return;
       -        }
       -
       -        chan = XBGR32;
       -        m = allocmemimage(Rect(0, 0, Dx(r), Dy(r)), chan);
       -        if(m == nil)
       -                panic("allocmemimage: %r");
       -        if(m->data == nil)
       -                panic("m->data == nil");
       -        bpl = bytesperline(r, 32);
       -        provider = CGDataProviderCreateWithData(0,
       -                m->data->bdata, Dy(r)*bpl, 0);
       -        //cspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
       -        cspace = CGColorSpaceCreateDeviceRGB();
       -        image = CGImageCreate(Dx(r), Dy(r), 8, 32, bpl,
       -                cspace,
       -                kCGImageAlphaNoneSkipLast,
       -                provider, 0, 0, kCGRenderingIntentDefault);
       -        CGColorSpaceRelease(cspace);
       -        CGDataProviderRelease(provider);        // CGImageCreate did incref
       -        
       -        mouserect = m->r;
       -        if(new){
       -                mouseresized = 1;
       -                mousetrack(osx.xy.x, osx.xy.y, osx.buttons|osx.kbuttons, msec());
       -        }
       -//        termreplacescreenimage(m);
       -        _drawreplacescreenimage(m);        // frees old osx.screenimage if any
       -        if(osx.image)
       -                CGImageRelease(osx.image);
       -        osx.image = image;
       -        osx.screenimage = m;
       -        osx.screenr = r;
       -        
       -        // I'm not 100% sure why this is necessary
       -        // but otherwise some resizes (esp. vertical ones)
       -        // stop updating the screen.
       -        qlock(&osx.flushlock);
       -        QDEndCGContext(GetWindowPort(osx.window), &osx.windowctx);
       -        osx.windowctx = nil;
       -        qunlock(&osx.flushlock);
       -}
       -
       -void
       -flushproc(void *v)
       -{
       -        for(;;){
       -                if(osx.needflush && osx.windowctx && canqlock(&osx.flushlock)){
       -                        if(osx.windowctx){
       -                                CGContextFlush(osx.windowctx);
       -                                osx.needflush = 0;
       -                        }
       -                        qunlock(&osx.flushlock);
       -                }
       -                usleep(33333);
       -        }
       -}
       -
       -void
       -_flushmemscreen(Rectangle r)
       -{
       -        CGRect cgr;
       -        CGImageRef subimg;
       -
       -        qlock(&osx.flushlock);
       -        if(osx.windowctx == nil){
       -                QDBeginCGContext(GetWindowPort(osx.window), &osx.windowctx);
       -                proccreate(flushproc, nil, 256*1024);
       -        }
       -        
       -        cgr.origin.x = r.min.x;
       -        cgr.origin.y = r.min.y;
       -        cgr.size.width = Dx(r);
       -        cgr.size.height = Dy(r);
       -        subimg = CGImageCreateWithImageInRect(osx.image, cgr);
       -        cgr.origin.y = Dy(osx.screenr) - r.max.y; // XXX how does this make any sense?
       -        CGContextDrawImage(osx.windowctx, cgr, subimg);
       -        osx.needflush = 1;
       -        qunlock(&osx.flushlock);
       -        CGImageRelease(subimg);
       -}
       -
       -void
       -activated(int active)
       -{
       -        osx.active = active;
       -}
       -
       -void
       -fullscreen(int wascmd)
       -{
       -        static OSXRect oldrect;
       -        GDHandle device;
       -        OSXRect dr;
       -
       -        if(!wascmd)
       -                return;
       -        
       -        if(!osx.isfullscreen){
       -                GetWindowGreatestAreaDevice(osx.window,
       -                        kWindowTitleBarRgn, &device, nil);
       -                dr = (*device)->gdRect;
       -                if(dr.top == 0 && dr.left == 0)
       -                        HideMenuBar();
       -                GetWindowBounds(osx.window, kWindowContentRgn, &oldrect);
       -                ChangeWindowAttributes(osx.window,
       -                        kWindowNoTitleBarAttribute,
       -                        kWindowResizableAttribute);
       -                MoveWindow(osx.window, 0, 0, 1);
       -                MoveWindow(osx.window, dr.left, dr.top, 0);
       -                SizeWindow(osx.window,
       -                        dr.right - dr.left,
       -                        dr.bottom - dr.top, 0);
       -                osx.isfullscreen = 1;
       -        }else{
       -                ShowMenuBar();
       -                ChangeWindowAttributes(osx.window,
       -                        kWindowResizableAttribute,
       -                        kWindowNoTitleBarAttribute);
       -                SizeWindow(osx.window,
       -                        oldrect.right - oldrect.left,
       -                        oldrect.bottom - oldrect.top, 0);
       -                MoveWindow(osx.window, oldrect.left, oldrect.top, 0);
       -                osx.isfullscreen = 0;
       -        }
       -        eresized(1);
       -}
       -                
       -void
       -setmouse(Point p)
       -{
       -        CGPoint cgp;
       -        
       -        cgp.x = p.x + osx.screenr.min.x;
       -        cgp.y = p.y + osx.screenr.min.y;
       -        CGWarpMouseCursorPosition(cgp);
       -}
       -
       -void
       -setcursor(Cursor *c)
       -{
       -        OSXCursor oc;
       -        int i;
       -
       -        if(c == nil){
       -                InitCursor();
       -                return;
       -        }
       -        
       -        // SetCursor is deprecated, but what replaces it?
       -        for(i=0; i<16; i++){
       -                oc.data[i] = ((ushort*)c->set)[i];
       -                oc.mask[i] = oc.data[i] | ((ushort*)c->clr)[i];
       -        }
       -        oc.hotSpot.h = - c->offset.x;
       -        oc.hotSpot.v = - c->offset.y;
       -        SetCursor(&oc);
       -}
       -
       -void
       -getcolor(ulong i, ulong *r, ulong *g, ulong *b)
       -{
       -        ulong v;
       -        
       -        v = 0;
       -        *r = (v>>16)&0xFF;
       -        *g = (v>>8)&0xFF;
       -        *b = v&0xFF;
       -}
       -
       -int
       -setcolor(ulong i, ulong r, ulong g, ulong b)
       -{
       -        /* no-op */
       -        return 0;
       -}
       -
       -
       -int
       -hwdraw(Memdrawparam *p)
       -{
       -        return 0;
       -}
       -
       -struct {
       -        QLock lk;
       -        char buf[SnarfSize];
       -        Rune rbuf[SnarfSize];
       -        PasteboardRef apple;
       -} clip;
       -
       -char*
       -getsnarf(void)
       -{
       -        char *s;
       -        CFArrayRef flavors;
       -        CFDataRef data;
       -        CFIndex nflavor, ndata, j;
       -        CFStringRef type;
       -        ItemCount nitem;
       -        PasteboardItemID id;
       -        PasteboardSyncFlags flags;
       -        UInt32 i;
       -        u16int *u;
       -        Fmt fmt;
       -        Rune r;
       -
       -/*        fprint(2, "applegetsnarf\n"); */
       -        qlock(&clip.lk);
       -        clip.apple = osx.snarf;
       -        if(clip.apple == nil){
       -                if(PasteboardCreate(kPasteboardClipboard, &clip.apple) != noErr){
       -                        fprint(2, "apple pasteboard create failed\n");
       -                        qunlock(&clip.lk);
       -                        return nil;
       -                }
       -        }
       -        flags = PasteboardSynchronize(clip.apple);
       -        if(flags&kPasteboardClientIsOwner){
       -                s = strdup(clip.buf);
       -                qunlock(&clip.lk);
       -                return s;
       -        }
       -        if(PasteboardGetItemCount(clip.apple, &nitem) != noErr){
       -                fprint(2, "apple pasteboard get item count failed\n");
       -                qunlock(&clip.lk);
       -                return nil;
       -        }
       -        for(i=1; i<=nitem; i++){
       -                if(PasteboardGetItemIdentifier(clip.apple, i, &id) != noErr)
       -                        continue;
       -                if(PasteboardCopyItemFlavors(clip.apple, id, &flavors) != noErr)
       -                        continue;
       -                nflavor = CFArrayGetCount(flavors);
       -                for(j=0; j<nflavor; j++){
       -                        type = (CFStringRef)CFArrayGetValueAtIndex(flavors, j);
       -                        if(!UTTypeConformsTo(type, CFSTR("public.utf16-plain-text")))
       -                                continue;
       -                        if(PasteboardCopyItemFlavorData(clip.apple, id, type, &data) != noErr)
       -                                continue;
       -                        qunlock(&clip.lk);
       -                        ndata = CFDataGetLength(data)/2;
       -                        u = (u16int*)CFDataGetBytePtr(data);
       -                        fmtstrinit(&fmt);
       -                        // decode utf-16.  what was apple thinking?
       -                        for(i=0; i<ndata; i++) {
       -                                r = u[i];
       -                                if(0xd800 <= r && r < 0xdc00 && i+1 < ndata && 0xdc00 <= u[i+1] && u[i+1] < 0xe000) {
       -                                        r = (((r - 0xd800)<<10) |  (u[i+1] - 0xdc00)) + 0x10000;
       -                                        i++;
       -                                }
       -                                else if(0xd800 <= r && r < 0xe000)
       -                                        r = Runeerror;
       -                                if(r == '\r')
       -                                        r = '\n';
       -                                fmtrune(&fmt, r);
       -                        }
       -                        CFRelease(flavors);
       -                        CFRelease(data);
       -                        return fmtstrflush(&fmt);
       -                }
       -                CFRelease(flavors);
       -        }
       -        qunlock(&clip.lk);
       -        return nil;                
       -}
       -
       -void
       -putsnarf(char *s)
       -{
       -        CFDataRef cfdata;
       -        PasteboardSyncFlags flags;
       -        u16int *u, *p;
       -        Rune r;
       -        int i;
       -
       -/*        fprint(2, "appleputsnarf\n"); */
       -
       -        if(strlen(s) >= SnarfSize)
       -                return;
       -        qlock(&clip.lk);
       -        strcpy(clip.buf, s);
       -        runesnprint(clip.rbuf, nelem(clip.rbuf), "%s", s);
       -        clip.apple = osx.snarf;
       -        if(PasteboardClear(clip.apple) != noErr){
       -                fprint(2, "apple pasteboard clear failed\n");
       -                qunlock(&clip.lk);
       -                return;
       -        }
       -        flags = PasteboardSynchronize(clip.apple);
       -        if((flags&kPasteboardModified) || !(flags&kPasteboardClientIsOwner)){
       -                fprint(2, "apple pasteboard cannot assert ownership\n");
       -                qunlock(&clip.lk);
       -                return;
       -        }
       -        u = malloc(runestrlen(clip.rbuf)*4);
       -        p = u;
       -        for(i=0; clip.rbuf[i]; i++) {
       -                r = clip.rbuf[i];
       -                // convert to utf-16
       -                if(0xd800 <= r && r < 0xe000)
       -                        r = Runeerror;
       -                if(r >= 0x10000) {
       -                        r -= 0x10000;
       -                        *p++ = 0xd800 + (r>>10);
       -                        *p++ = 0xdc00 + (r & ((1<<10)-1));
       -                } else
       -                        *p++ = r;
       -        }
       -        cfdata = CFDataCreate(kCFAllocatorDefault, 
       -                (uchar*)u, (p-u)*2);
       -        free(u);
       -        if(cfdata == nil){
       -                fprint(2, "apple pasteboard cfdatacreate failed\n");
       -                qunlock(&clip.lk);
       -                return;
       -        }
       -        if(PasteboardPutItemFlavor(clip.apple, (PasteboardItemID)1,
       -                CFSTR("public.utf16-plain-text"), cfdata, 0) != noErr){
       -                fprint(2, "apple pasteboard putitem failed\n");
       -                CFRelease(cfdata);
       -                qunlock(&clip.lk);
       -                return;
       -        }
       -        CFRelease(cfdata);
       -        qunlock(&clip.lk);
       -}
       -
       -void
       -setlabel(char *label)
       -{
       -        CFStringRef cs;
       -
       -        cs = CFStringCreateWithBytes(nil, (uchar*)label, strlen(label), kCFStringEncodingUTF8, false);
       -        SetWindowTitleWithCFString(osx.window, cs);
       -        CFRelease(cs);
       -}
       -
       -void
       -kicklabel(char *label)
       -{
       -        char *p;
       -        EventRef e;
       -
       -        p = strdup(label);
       -        if(p == nil)
       -                return;
       -        qlock(&osx.labellock);
       -        free(osx.label);
       -        osx.label = p;
       -        qunlock(&osx.labellock);
       -        
       -        CreateEvent(nil, 'P9PE', P9PEventLabelUpdate, 0, kEventAttributeUserEvent, &e);
       -        PostEventToQueue(GetMainEventQueue(), e, kEventPriorityStandard);
       -        
       -}
       -
       -static void
       -seticon(void)
       -{
       -        CGImageRef im;
       -        CGDataProviderRef d;
       -
       -        d = CGDataProviderCreateWithData(nil, glenda_png, sizeof glenda_png, nil);
       -        im = CGImageCreateWithPNGDataProvider(d, nil, true, kCGRenderingIntentDefault);
       -        if(im)
       -                SetApplicationDockTileImage(im);
       -        CGImageRelease(im);
       -        CGDataProviderRelease(d);
       -}
       -
   DIR diff --git a/src/cmd/devdraw/osx-srv.c b/src/cmd/devdraw/osx-srv.c
       t@@ -89,6 +89,7 @@ zunlock(void)
        int chatty;
        int drawsleep;
        int trace;
       +int multitouch = 1;
        
        void
        usage(void)
       t@@ -130,6 +131,9 @@ threadmain(int argc, char **argv)
                case 'D':
                        chatty++;
                        break;
       +        case 'M':
       +                multitouch = 0;
       +                break;
                default:
                        usage();
                }ARGEND