commit a287dbab235c9041a32300a9e0bb60ef41864963 from: David Jeannot via: Russ Cox date: Tue Sep 06 14:10:43 2011 UTC devdraw: draft cocoa support R=rsc CC=plan9port.codebot http://codereview.appspot.com/4974060 commit - f0a4e8bd6cfb318e374e57f34b5676c6890fb1a2 commit + a287dbab235c9041a32300a9e0bb60ef41864963 blob - c1f2b919984c68bcdbca74e7f329c678db3285c9 blob + 8d615a5adba2bc19a80ce276023bc84c23f7c70f --- CONTRIBUTORS +++ CONTRIBUTORS @@ -10,6 +10,7 @@ Anthony Sorace Arvindh Rajesh Tamilmani Benjamin Huntsman David du Colombier <0intro@gmail.com> +David Jeannot David Swasey Enrique Soriano Eoghan Sherry 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 + +#undef Rect +#undef Point +#undef Cursor + +#include +#include +#include "cocoa-thread.h" +#include +#include +#include +#include +#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 +#include +#include "cocoa-thread.h" +#include +#include +#include +#include +#include +#include +#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 +#include +#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