Commit Diff


commit - f0a4e8bd6cfb318e374e57f34b5676c6890fb1a2
commit + a287dbab235c9041a32300a9e0bb60ef41864963
blob - c1f2b919984c68bcdbca74e7f329c678db3285c9
blob + 8d615a5adba2bc19a80ce276023bc84c23f7c70f
--- CONTRIBUTORS
+++ CONTRIBUTORS
@@ -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>
blob - e5541a69780255e8e3015c7d224ac303d1aed794
blob + 57fd8212932d22b1eaf3067af3a94ca352c12ffb
--- src/cmd/acme/text.c
+++ src/cmd/acme/text.c
@@ -750,6 +750,10 @@ texttype(Text *t, Rune r)
 	case Kcmd+'c':	/* %C: copy */
 		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:
blob - /dev/null
blob + 3a53efb4daf4df47170107b536cb3dbbb5edfe8f (mode 644)
--- /dev/null
+++ src/cmd/devdraw/cocoa-screen.h
@@ -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;
blob - /dev/null
blob + 03f681712db9ef2e4da2f217dbf3e2da30a10f7f (mode 644)
--- /dev/null
+++ src/cmd/devdraw/cocoa-screen.m
@@ -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)
+{
+}
blob - /dev/null
blob + 507138f8e4e19b1d9cd06b61d10a3690ad24e563 (mode 644)
--- /dev/null
+++ src/cmd/devdraw/cocoa-srv.c
@@ -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;
+}
blob - /dev/null
blob + 4b2443cebee3a3a214ba3a99ca3935817bfcb11f (mode 644)
--- /dev/null
+++ src/cmd/devdraw/cocoa-thread.c
@@ -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;
+}
blob - /dev/null
blob + 17a12eb78b1970540f72a2d13f633bc013c5f438 (mode 644)
--- /dev/null
+++ src/cmd/devdraw/cocoa-thread.h
@@ -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 */
blob - 83f55242473d613aaef5a6f24bac28aec1a0f122
blob + 61d59839ab1343010c33b15e2d35ae716efef996
--- src/cmd/devdraw/mkfile
+++ src/cmd/devdraw/mkfile
@@ -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