/* * 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) { }