aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Jeannot <djeannot24@gmail.com>2011-09-06 10:10:43 -0400
committerRuss Cox <rsc@swtch.com>2011-09-06 10:10:43 -0400
commita287dbab235c9041a32300a9e0bb60ef41864963 (patch)
tree0049709f4a223e0de5fde987ea0d170fcb543b1d
parentf0a4e8bd6cfb318e374e57f34b5676c6890fb1a2 (diff)
downloadplan9port-a287dbab235c9041a32300a9e0bb60ef41864963.tar.gz
plan9port-a287dbab235c9041a32300a9e0bb60ef41864963.tar.bz2
plan9port-a287dbab235c9041a32300a9e0bb60ef41864963.zip
devdraw: draft cocoa support
R=rsc CC=plan9port.codebot http://codereview.appspot.com/4974060
-rw-r--r--CONTRIBUTORS1
-rw-r--r--src/cmd/acme/text.c4
-rw-r--r--src/cmd/devdraw/cocoa-screen.h19
-rw-r--r--src/cmd/devdraw/cocoa-screen.m835
-rw-r--r--src/cmd/devdraw/cocoa-srv.c397
-rw-r--r--src/cmd/devdraw/cocoa-thread.c54
-rw-r--r--src/cmd/devdraw/cocoa-thread.h27
-rw-r--r--src/cmd/devdraw/mkfile3
8 files changed, 1340 insertions, 0 deletions
diff --git a/CONTRIBUTORS b/CONTRIBUTORS
index c1f2b919..8d615a5a 100644
--- a/CONTRIBUTORS
+++ b/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>
diff --git a/src/cmd/acme/text.c b/src/cmd/acme/text.c
index e5541a69..57fd8212 100644
--- a/src/cmd/acme/text.c
+++ b/src/cmd/acme/text.c
@@ -751,6 +751,10 @@ texttype(Text *t, Rune r)
typecommit(t);
cut(t, t, nil, TRUE, FALSE, nil, 0);
return;
+ case Kcmd+'z': /* %Z: undo */
+ typecommit(t);
+ undo(t, nil, nil, TRUE, 0, nil, 0);
+ return;
Tagdown:
/* expand tag to show all text */
diff --git a/src/cmd/devdraw/cocoa-screen.h b/src/cmd/devdraw/cocoa-screen.h
new file mode 100644
index 00000000..3a53efb4
--- /dev/null
+++ b/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;
diff --git a/src/cmd/devdraw/cocoa-screen.m b/src/cmd/devdraw/cocoa-screen.m
new file mode 100644
index 00000000..03f68171
--- /dev/null
+++ b/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)
+{
+}
diff --git a/src/cmd/devdraw/cocoa-srv.c b/src/cmd/devdraw/cocoa-srv.c
new file mode 100644
index 00000000..507138f8
--- /dev/null
+++ b/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;
+}
diff --git a/src/cmd/devdraw/cocoa-thread.c b/src/cmd/devdraw/cocoa-thread.c
new file mode 100644
index 00000000..4b2443ce
--- /dev/null
+++ b/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;
+}
diff --git a/src/cmd/devdraw/cocoa-thread.h b/src/cmd/devdraw/cocoa-thread.h
new file mode 100644
index 00000000..17a12eb7
--- /dev/null
+++ b/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 */
diff --git a/src/cmd/devdraw/mkfile b/src/cmd/devdraw/mkfile
index 83f55242..61d59839 100644
--- a/src/cmd/devdraw/mkfile
+++ b/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