/* * 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" // try libthread when possible #include #include #include #include #include "cocoa-screen.h" #include "osx-keycodes.h" #include "devdraw.h" #include "glendapng.h" AUTOFRAMEWORK(Cocoa) #define panic sysfatal /* * Incompatible with Magic Mouse? */ int reimplementswipe = 0; int usecopygesture = 0; int useoldfullscreen = 0; void usage(void) { fprint(2, "usage: devdraw (don't run directly)\n"); exits("usage"); } @interface appdelegate : NSObject @end 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); // Libdraw don't permit arguments currently. ARGBEGIN{ case 'D': // only for good ps -a listings break; default: usage(); }ARGEND if(usecopygesture) reimplementswipe = 1; [NSApplication sharedApplication]; [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; [NSApp setDelegate:[appdelegate new]]; [NSApp activateIgnoringOtherApps:YES]; [NSApp run]; } struct { NSWindow *p; NSView *content; Cursor *cursor; NSString *title; QLock titlel; char *rectstr; NSRect lastrect; NSBitmapImageRep *img; NSRect flushrect; int needflush; } win; static void drawimg(void); static void flushwin(void); static void fullscreen(void); static void getmousepos(void); static void makeicon(void); static void makemenu(void); static void makewin(void); static void resize(void); static void sendmouse(int); static void setcursor0(void); @implementation appdelegate - (void)applicationDidFinishLaunching:(id)arg { makeicon(); makemenu(); [NSApplication detachDrawingThread:@selector(callservep9p:) toTarget:[self class] withObject:nil]; } - (void)windowDidBecomeKey:(id)arg { getmousepos(); sendmouse(0); } - (void)windowDidResize:(id)arg { getmousepos(); sendmouse(0); if([win.p inLiveResize]) return; resize(); } - (void)windowDidEndLiveResize:(id)arg { resize(); } - (void)windowDidDeminiaturize:(id)arg { resize(); } - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(id)arg { return YES; } + (void)callservep9p:(id)arg { servep9p(); [NSApp terminate:self]; } + (void)calldrawimg:(id)arg{ drawimg();} + (void)callflushwin:(id)arg{ flushwin();} - (void)callfullscreen:(id)arg{ fullscreen();} + (void)callmakewin:(id)arg{ makewin();} + (void)callsetcursor0:(id)arg{ setcursor0();} @end static Memimage* makeimg(void); Memimage* attachscreen(char *label, char *winsize) { static int first = 1; if(first) first = 0; else panic("attachscreen called twice"); if(label == nil) label = "gnot a label"; win.rectstr = strdup(winsize); // Create window in main thread, // else no cursor change when resizing. [appdelegate performSelectorOnMainThread:@selector(callmakewin:) withObject:nil waitUntilDone:YES]; // makewin(); kicklabel(label); return makeimg(); } @interface appview : NSView @end @interface appwin : NSWindow @end @implementation appwin - (NSTimeInterval)animationResizeTime:(NSRect)r { return 0; } @end enum { Winstyle = NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask }; static void makewin(void) { NSRect r, sr; NSView *v; NSWindow *w; Rectangle wr; char *s; int set; s = win.rectstr; if(s && *s){ if(parsewinsize(s, &wr, &set) < 0) sysfatal("%r"); }else{ sr = [[NSScreen mainScreen] frame]; wr = Rect(0, 0, sr.size.width*2/3, sr.size.height*2/3); set = 0; } // The origin is the left-bottom corner with Cocoa. r = NSMakeRect(wr.min.x, r.size.height-wr.min.y, Dx(wr), Dy(wr)); v = [appview new]; [v setAcceptsTouchEvents:YES]; w = [[appwin alloc] initWithContentRect:r styleMask:Winstyle backing:NSBackingStoreBuffered defer:NO]; if(!set) [w center]; #if OSX_VERSION >= 100700 [w setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary]; #endif [w setAcceptsMouseMovedEvents:YES]; [w setContentView:v]; [w setDelegate:[NSApp delegate]]; [w setMinSize:NSMakeSize(128,128)]; [w makeKeyAndOrderFront:nil]; win.content = v; win.p = w; } static Memimage* makeimg(void) { static int first = 1; Memimage *m; NSSize size; Rectangle r; uint ch; if(first){ memimageinit(); first = 0; } size = [win.content bounds].size; if(size.width<=0 || size.height<=0){ NSLog(@"bad content size: %.0f %.0f", size.width, size.height); return nil; } 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"); if(win.img) [win.img release]; win.img = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:&m->data->bdata pixelsWide:Dx(r) pixelsHigh:Dy(r) bitsPerSample:8 samplesPerPixel:4 hasAlpha:YES isPlanar:NO colorSpaceName:NSDeviceRGBColorSpace bytesPerRow:bytesperline(r, 32) bitsPerPixel:32]; _drawreplacescreenimage(m); return m; } static void resize(void) { makeimg(); sendmouse(1); } void _flushmemscreen(Rectangle r) { win.flushrect = NSMakeRect(r.min.x, r.min.y, Dx(r), Dy(r)); // Call "lockFocusIfCanDraw" from main thread, else // we deadlock while synchronizing both threads with // qlock(): main thread must apparently be idle while we call it. [appdelegate performSelectorOnMainThread:@selector(calldrawimg:) withObject:nil waitUntilDone:YES]; } static void drawimg(void) { static int first = 1; NSRect dr, sr; if(first){ [NSTimer scheduledTimerWithTimeInterval:0.033 target:[appdelegate class] selector:@selector(callflushwin:) userInfo:nil repeats:YES]; first = 0; } dr = win.flushrect; sr = [win.content convertRect:dr fromView:nil]; if([win.content lockFocusIfCanDraw]){ [win.img drawInRect:dr fromRect:sr operation:NSCompositeCopy fraction:1 respectFlipped:YES hints:nil]; [win.content unlockFocus]; win.needflush = 1; } } static void flushwin(void) { if(win.needflush){ [win.p flushWindow]; win.needflush = 0; } } static void getgesture(NSEvent*); static void getkeyboard(NSEvent*); static void getmouse(NSEvent*); static void gettouch(NSEvent*, int); @implementation appview - (void)drawRect:(NSRect)r { // else no window background } - (BOOL)isFlipped { return YES; // to have the origin at top left } - (BOOL)acceptsFirstResponder { return YES; // to receive mouseMoved events } - (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)swipeWithEvent:(NSEvent*)e{ getgesture(e);} - (void)magnifyWithEvent:(NSEvent*)e{ getgesture(e);} - (void)touchesBeganWithEvent:(NSEvent*)e { gettouch(e, NSTouchPhaseBegan); } - (void)touchesMovedWithEvent:(NSEvent*)e { gettouch(e, NSTouchPhaseMoved); } - (void)touchesEndedWithEvent:(NSEvent*)e { gettouch(e, NSTouchPhaseEnded); } - (void)touchesCancelledWithEvent:(NSEvent*)e { gettouch(e, NSTouchPhaseCancelled); } @end struct { int kalting; int kbuttons; int mbuttons; Point mpos; int mscroll; int undo; } in; 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', }; static void getkeyboard(NSEvent *e) { char c; int k, m; uint code; m = [e modifierFlags]; switch([e type]){ case NSKeyDown: in.kalting = 0; c = [[e characters] characterAtIndex:0]; if(m & NSCommandKeyMask){ if(' '<=c && c<='~'){ keystroke(Kcmd+c); } return; } // to understand 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(in.mbuttons || in.kbuttons){ in.kbuttons = 0; if(m & NSAlternateKeyMask) in.kbuttons |= 2; if(m & NSCommandKeyMask) in.kbuttons |= 4; sendmouse(0); }else if(m & NSAlternateKeyMask){ in.kalting = 1; keystroke(Kalt); } break; default: panic("getkey: unexpected event type"); } } static void getmousepos(void) { NSPoint p; p = [win.p mouseLocationOutsideOfEventStream]; p = [win.content convertPoint:p fromView:nil]; in.mpos = Pt(p.x, p.y); } static void getmouse(NSEvent *e) { float d; int b, m; 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(in.kalting){ in.kalting = 0; keystroke(Kalt); } }else if(m & NSCommandKeyMask) b = 4; } in.mbuttons = b; break; case NSScrollWheel: #if OSX_VERSION >= 100700 d = [e scrollingDeltaY]; #else d = [e deltaY]; #endif if(d>0) in.mscroll = 8; else if(d<0) in.mscroll = 16; break; case NSMouseMoved: case NSLeftMouseDragged: case NSRightMouseDragged: case NSOtherMouseDragged: break; default: panic("getmouse: unexpected event type"); } sendmouse(0); } static void sendswipe(int, int); static void getgesture(NSEvent *e) { switch([e type]){ case NSEventTypeMagnify: // if(fabs([e magnification]) > 0.025) fullscreen(); break; case NSEventTypeSwipe: if(reimplementswipe) break; sendswipe(-[e deltaX], -[e deltaY]); break; } } static void sendclick(int); static void sendchord(int, int); static void sendcmd(int); static uint msec(void) { return nsec()/1000000; } enum { Msec = 1, Maxtap = 400*Msec, Maxtouch = 3, Mindelta = 0, Minswipe = 15, }; static void gettouch(NSEvent *e, int type) { static NSPoint delta, odelta; static NSTouch *toucha[Maxtouch]; static NSTouch *touchb[Maxtouch]; static int done, ntouch, tapping; static uint taptime; NSArray *a; NSPoint d; NSSet *set; NSSize s; int i, p; if(reimplementswipe==0 && type!=NSTouchPhaseEnded) return; switch(type){ case NSTouchPhaseBegan: p = NSTouchPhaseTouching; set = [e touchesMatchingPhase:p inView:nil]; if(set.count == 3){ tapping = 1; taptime = msec(); }else if(set.count > 3) tapping = 0; return; case NSTouchPhaseMoved: p = NSTouchPhaseMoved; set = [e touchesMatchingPhase:p inView:nil]; a = [set allObjects]; if(set.count > Maxtouch) return; if(ntouch==0){ ntouch = set.count; for(i=0; iMindelta || fabs(d.y)>Mindelta){ tapping = 0; if(ntouch != 3){ done = 1; goto Return; } delta = NSMakePoint(delta.x+d.x, delta.y+d.y); d = NSMakePoint(fabs(delta.x), fabs(delta.y)); if(d.x>Minswipe || d.y>Minswipe){ if(d.x > d.y) delta = NSMakePoint(-copysign(1,delta.x), 0); else delta = NSMakePoint(0, copysign(1,delta.y)); if(! NSEqualPoints(delta, odelta)){ // if(ntouch == 3) sendswipe(-delta.x, -delta.y); odelta = delta; } done = 1; goto Return; } for(i=0; i= 100700 if(useoldfullscreen == 0){ [win.p toggleFullScreen:nil]; return; } #endif NSScreen *screen; int opt; screen = [win.p screen]; if(NSEqualRects([win.p frame], [screen frame])){ opt = NSApplicationPresentationDefault; [NSApp setPresentationOptions:opt]; [win.p setStyleMask:Winstyle]; [win.p setFrame:win.lastrect display:YES]; }else{ win.lastrect = [win.p frame]; opt = NSApplicationPresentationAutoHideDock | NSApplicationPresentationAutoHideMenuBar; [NSApp setPresentationOptions:opt]; [win.p setStyleMask:NSBorderlessWindowMask]; [win.p setFrame:[screen frame] display:YES]; } // On OS X Lion, after "setStyleMask", window is activated (the gesture work for example), but no keyboard input until mouse or trackpad pressed // What I tried without success on OS X Lion: // [NSApp activateIgnoringOtherApps:YES]; // [win.p makeKeyAndOrderFront:nil]; // implementing canBecomeKeyWindow // implementing canBecomeMainWindow // saving/restoring [win.content nextResponder] // using enterFullScreenMode instead // What I didn't try: // using 2 windows instead: one for each mode qlock(&win.titlel); [win.p setTitle:win.title]; qunlock(&win.titlel); } // Rewrite this function // See ./osx-delegate.m implementation (NSLocalizedString) static void makemenu(void) { NSString *title; NSMenu *menu; NSMenuItem *appmenu, *item; menu = [NSMenu new]; appmenu = [NSMenuItem new]; [menu addItem:appmenu]; [NSApp setMenu:menu]; [menu release]; menu = [NSMenu new]; title = @"Full Screen"; item = [[NSMenuItem alloc] initWithTitle:title action:@selector(callfullscreen:) keyEquivalent:@"f"]; [menu addItem:item]; [item release]; title = @"Quit"; item = [[NSMenuItem alloc] initWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"]; [menu addItem:item]; [item release]; [appmenu setSubmenu:menu]; [appmenu release]; [menu release]; } static void makeicon(void) { NSData *d; NSImage *i; d = [[NSData alloc] initWithBytes:glenda_png length:(sizeof glenda_png)]; i = [[NSImage alloc] initWithData:d]; [NSApp setApplicationIconImage:i]; [[NSApp dockTile] display]; [i release]; [d release]; } QLock snarfl; char* getsnarf(void) { NSPasteboard *pb; NSString *s; 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; // "you should periodically drain and create // autorelease pools (like the Application Kit does // on the main thread); otherwise, autoreleased // objects accumulate and your memory footprint // grows." // Should we do it here? // Verify that we need it before. } void putsnarf(char *s) { NSArray *t; NSPasteboard *pb; NSString *str; 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) NSLog(@"putsnarf: setString failed"); } void kicklabel(char *label) { if(label == nil) return; qlock(&win.titlel); if(win.title) [win.title release]; win.title = [[NSString alloc] initWithUTF8String:label]; [win.p setTitle:win.title]; [[NSApp dockTile] setBadgeLabel:win.title]; qunlock(&win.titlel); } void setcursor(Cursor *cursor) { win.cursor = cursor; // cursor change only if main thread [appdelegate performSelectorOnMainThread:@selector(callsetcursor0:) withObject:nil waitUntilDone:YES]; // setcursor0(); win.cursor = nil; } static void setcursor0(void) { Cursor *c; NSBitmapImageRep *r; NSCursor *d; NSImage *i; NSPoint p; int b; uchar *plane[5]; c = win.cursor; if(c == nil){ [[NSCursor arrowCursor] set]; return; } r = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:nil pixelsWide:16 pixelsHigh:16 bitsPerSample:1 samplesPerPixel:2 hasAlpha:YES isPlanar:YES colorSpaceName:NSDeviceBlackColorSpace bytesPerRow:2 bitsPerPixel:1]; [r getBitmapDataPlanes:plane]; for(b=0; b<2*16; b++){ plane[0][b] = c->set[b]; plane[1][b] = c->clr[b]; } p = NSMakePoint(-c->offset.x, -c->offset.y); i = [NSImage new]; [i addRepresentation:r]; d = [[NSCursor alloc] initWithImage:i hotSpot:p]; [d set]; [d release]; [r release]; [i release]; }