diff options
Diffstat (limited to 'src/cmd/devdraw/cocoa-screen.m')
-rw-r--r-- | src/cmd/devdraw/cocoa-screen.m | 835 |
1 files changed, 835 insertions, 0 deletions
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) +{ +} |