| /* |
| * Cocoa's event loop must be in main thread. |
| * |
| * Unless otherwise stated, all coordinate systems |
| * are bottom-left-based. |
| */ |
| |
| #define Cursor OSXCursor |
| #define Point OSXPoint |
| #define Rect OSXRect |
| |
| #import <Cocoa/Cocoa.h> |
| |
| #undef Cursor |
| #undef Point |
| #undef Rect |
| |
| #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 "bigarrow.h" |
| #include "glendapng.h" |
| |
| AUTOFRAMEWORK(Cocoa) |
| |
| #define LOG if(0)NSLog |
| #define panic sysfatal |
| |
| int usegestures = 0; |
| int useliveresizing = 0; |
| int useoldfullscreen = 0; |
| int usebigarrow = 0; |
| |
| /* |
| * By default, devdraw ignores retina displays. A non-zero evironment variable |
| * |devdrawretina| will override this. |
| */ |
| int devdrawretina = 0; |
| |
| void |
| usage(void) |
| { |
| fprint(2, "usage: devdraw (don't run directly)\n"); |
| threadexitsall("usage"); |
| } |
| |
| @interface appdelegate : NSObject @end |
| |
| void |
| threadmain(int argc, char **argv) |
| { |
| char *envvar; |
| |
| /* |
| * 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': /* for good ps -a listings */ |
| break; |
| case 'f': |
| useoldfullscreen = 1; |
| break; |
| case 'g': |
| usegestures = 1; |
| break; |
| case 'b': |
| usebigarrow = 1; |
| break; |
| default: |
| usage(); |
| }ARGEND |
| |
| if (envvar = getenv("devdrawretina")) |
| devdrawretina = atoi(envvar) > 0; |
| |
| if(OSX_VERSION < 100700) |
| [NSAutoreleasePool new]; |
| |
| [NSApplication sharedApplication]; |
| [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; |
| [NSApp setDelegate:[appdelegate new]]; |
| [NSApp activateIgnoringOtherApps:YES]; |
| [NSApp run]; |
| } |
| |
| #define WIN win.ofs[win.isofs] |
| |
| struct |
| { |
| NSWindow *ofs[2]; /* ofs[1] for old fullscreen; ofs[0] else */ |
| int isofs; |
| int isnfs; |
| NSView *content; |
| NSBitmapImageRep *img; |
| int needimg; |
| int deferflush; |
| NSCursor *cursor; |
| CGFloat topointscale; |
| CGFloat topixelscale; |
| } win; |
| |
| struct |
| { |
| NSCursor *bigarrow; |
| int kbuttons; |
| int mbuttons; |
| NSPoint mpos; |
| int mscroll; |
| int willactivate; |
| } in; |
| |
| static void hidebars(int); |
| static void flushimg(NSRect); |
| static void autoflushwin(int); |
| static void flushwin(void); |
| static void followzoombutton(NSRect); |
| static void getmousepos(void); |
| static void makeicon(void); |
| static void makemenu(void); |
| static void makewin(char*); |
| static void sendmouse(void); |
| static void setcursor0(Cursor*); |
| static void togglefs(void); |
| static void acceptresizing(int); |
| |
| static NSCursor* makecursor(Cursor*); |
| |
| static NSSize winsizepixels(); |
| static NSSize winsizepoints(); |
| static NSRect scalerect(NSRect, CGFloat); |
| static NSPoint scalepoint(NSPoint, CGFloat); |
| static NSRect dilate(NSRect); |
| |
| @implementation appdelegate |
| - (void)applicationDidFinishLaunching:(id)arg |
| { |
| in.bigarrow = makecursor(&bigarrow); |
| makeicon(); |
| makemenu(); |
| [NSApplication |
| detachDrawingThread:@selector(callservep9p:) |
| toTarget:[self class] withObject:nil]; |
| } |
| - (void)windowDidBecomeKey:(id)arg |
| { |
| getmousepos(); |
| sendmouse(); |
| } |
| - (void)windowDidResize:(id)arg |
| { |
| getmousepos(); |
| sendmouse(); |
| } |
| - (void)windowWillStartLiveResize:(id)arg |
| { |
| if(useliveresizing == 0) |
| [win.content setHidden:YES]; |
| } |
| - (void)windowDidEndLiveResize:(id)arg |
| { |
| if(useliveresizing == 0) |
| [win.content setHidden:NO]; |
| } |
| - (void)windowDidChangeScreen:(id)arg |
| { |
| if(win.isnfs || win.isofs) |
| hidebars(1); |
| [win.ofs[1] setFrame:[[WIN screen] frame] display:YES]; |
| } |
| - (BOOL)windowShouldZoom:(id)arg toFrame:(NSRect)r |
| { |
| followzoombutton(r); |
| return YES; |
| } |
| - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(id)arg |
| { |
| return YES; |
| } |
| - (void)applicationDidBecomeActive:(id)arg{ in.willactivate = 0;} |
| - (void)windowWillEnterFullScreen:(id)arg{ acceptresizing(1);} |
| - (void)windowDidEnterFullScreen:(id)arg{ win.isnfs = 1; hidebars(1);} |
| - (void)windowWillExitFullScreen:(id)arg{ win.isnfs = 0; hidebars(0);} |
| - (void)windowDidExitFullScreen:(id)arg |
| { |
| NSButton *b; |
| |
| b = [WIN standardWindowButton:NSWindowMiniaturizeButton]; |
| |
| if([b isEnabled] == 0){ |
| [b setEnabled:YES]; |
| hidebars(0); |
| } |
| } |
| - (void)windowWillClose:(id)arg |
| { |
| autoflushwin(0); /* can crash otherwise */ |
| } |
| |
| + (void)callservep9p:(id)arg |
| { |
| servep9p(); |
| [NSApp terminate:self]; |
| } |
| - (void)plumbmanual:(id)arg |
| { |
| if(fork() != 0) |
| return; |
| execl("plumb", "plumb", "devdraw(1)", nil); |
| } |
| + (void)callflushwin:(id)arg{ flushwin();} |
| - (void)calltogglefs:(id)arg{ togglefs();} |
| |
| + (void)callflushimg:(NSValue*)v{ flushimg([v rectValue]);} |
| + (void)callmakewin:(NSValue*)v{ makewin([v pointerValue]);} |
| + (void)callsetcursor0:(NSValue*)v{ setcursor0([v pointerValue]);} |
| @end |
| |
| static Memimage* initimg(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"; |
| if(strcmp(label, "page") == 0) |
| useliveresizing = 1; |
| |
| /* |
| * Create window in main thread, else no cursor |
| * change while resizing. |
| */ |
| [appdelegate |
| performSelectorOnMainThread:@selector(callmakewin:) |
| withObject:[NSValue valueWithPointer:winsize] |
| waitUntilDone:YES]; |
| // makewin(winsize); |
| |
| kicklabel(label); |
| return initimg(); |
| } |
| |
| @interface appwin : NSWindow @end |
| @interface contentview : NSView @end |
| |
| @implementation appwin |
| - (NSTimeInterval)animationResizeTime:(NSRect)r |
| { |
| return 0; |
| } |
| - (BOOL)canBecomeKeyWindow |
| { |
| return YES; /* else no keyboard for old fullscreen */ |
| } |
| - (void)makeKeyAndOrderFront:(id)arg |
| { |
| LOG(@"makeKeyAndOrderFront"); |
| |
| autoflushwin(1); |
| [win.content setHidden:NO]; |
| [super makeKeyAndOrderFront:arg]; |
| } |
| - (void)miniaturize:(id)arg |
| { |
| [super miniaturize:arg]; |
| [NSApp hide:nil]; |
| |
| [win.content setHidden:YES]; |
| autoflushwin(0); |
| } |
| - (void)deminiaturize:(id)arg |
| { |
| autoflushwin(1); |
| [win.content setHidden:NO]; |
| [super deminiaturize:arg]; |
| } |
| @end |
| |
| double |
| min(double a, double b) |
| { |
| return a<b? a : b; |
| } |
| |
| enum |
| { |
| Winstyle = NSTitledWindowMask |
| | NSClosableWindowMask |
| | NSMiniaturizableWindowMask |
| | NSResizableWindowMask |
| }; |
| |
| static void |
| makewin(char *s) |
| { |
| NSRect r, sr; |
| NSWindow *w; |
| Rectangle wr; |
| int i, set; |
| |
| sr = [[NSScreen mainScreen] frame]; |
| r = [[NSScreen mainScreen] visibleFrame]; |
| |
| if(s && *s){ |
| if(parsewinsize(s, &wr, &set) < 0) |
| sysfatal("%r"); |
| }else{ |
| wr = Rect(0, 0, sr.size.width*2/3, sr.size.height*2/3); |
| set = 0; |
| } |
| |
| r.origin.x = wr.min.x; |
| r.origin.y = sr.size.height-wr.max.y; /* winsize is top-left-based */ |
| r.size.width = min(Dx(wr), r.size.width); |
| r.size.height = min(Dy(wr), r.size.height); |
| r = [NSWindow contentRectForFrameRect:r |
| styleMask:Winstyle]; |
| |
| w = [[appwin alloc] |
| initWithContentRect:r |
| styleMask:Winstyle |
| backing:NSBackingStoreBuffered defer:NO]; |
| if(!set) |
| [w center]; |
| #if OSX_VERSION >= 100700 |
| [w setCollectionBehavior: |
| NSWindowCollectionBehaviorFullScreenPrimary]; |
| #endif |
| [w setContentMinSize:NSMakeSize(128,128)]; |
| |
| win.ofs[0] = w; |
| win.ofs[1] = [[appwin alloc] |
| initWithContentRect:sr |
| styleMask:NSBorderlessWindowMask |
| backing:NSBackingStoreBuffered defer:YES]; |
| for(i=0; i<2; i++){ |
| [win.ofs[i] setAcceptsMouseMovedEvents:YES]; |
| [win.ofs[i] setDelegate:[NSApp delegate]]; |
| [win.ofs[i] setDisplaysWhenScreenProfileChanges:NO]; |
| } |
| win.isofs = 0; |
| win.content = [contentview new]; |
| [WIN setContentView:win.content]; |
| } |
| |
| static Memimage* |
| initimg(void) |
| { |
| Memimage *i; |
| NSSize size, ptsize; |
| Rectangle r; |
| |
| size = winsizepixels(); |
| LOG(@"initimg %.0f %.0f", size.width, size.height); |
| |
| r = Rect(0, 0, size.width, size.height); |
| i = allocmemimage(r, XBGR32); |
| if(i == nil) |
| panic("allocmemimage: %r"); |
| if(i->data == nil) |
| panic("i->data == nil"); |
| |
| win.img = [[NSBitmapImageRep alloc] |
| initWithBitmapDataPlanes:&i->data->bdata |
| pixelsWide:Dx(r) |
| pixelsHigh:Dy(r) |
| bitsPerSample:8 |
| samplesPerPixel:3 |
| hasAlpha:NO |
| isPlanar:NO |
| colorSpaceName:NSDeviceRGBColorSpace |
| bytesPerRow:bytesperline(r, 32) |
| bitsPerPixel:32]; |
| ptsize = winsizepoints(); |
| [win.img setSize: ptsize]; |
| win.topixelscale = size.width / ptsize.width; |
| win.topointscale = 1.0f / win.topixelscale; |
| return i; |
| } |
| |
| static void |
| resizeimg() |
| { |
| [win.img release]; |
| _drawreplacescreenimage(initimg()); |
| |
| mouseresized = 1; |
| sendmouse(); |
| } |
| |
| static void |
| waitimg(int msec) |
| { |
| NSDate *limit; |
| int n; |
| |
| win.needimg = 1; |
| win.deferflush = 0; |
| |
| n = 0; |
| limit = [NSDate dateWithTimeIntervalSinceNow:msec/1000.0]; |
| do{ |
| [[NSRunLoop currentRunLoop] |
| runMode:@"waiting image" |
| beforeDate:limit]; |
| n++; |
| }while(win.needimg && [(NSDate*)[NSDate date] compare:limit]<0); |
| |
| win.deferflush = win.needimg; |
| |
| LOG(@"waitimg %s (%d loop)", win.needimg?"defer":"ok", n); |
| } |
| |
| void |
| _flushmemscreen(Rectangle r) |
| { |
| static int n; |
| NSRect rect; |
| |
| LOG(@"_flushmemscreen"); |
| |
| if(n==0){ |
| n++; |
| return; /* to skip useless white init rect */ |
| }else |
| if(n==1){ |
| [WIN performSelectorOnMainThread: |
| @selector(makeKeyAndOrderFront:) |
| withObject:nil |
| waitUntilDone:NO]; |
| n++; |
| }else |
| if([win.content canDraw] == 0) |
| return; |
| |
| rect = NSMakeRect(r.min.x, r.min.y, Dx(r), Dy(r)); |
| [appdelegate |
| performSelectorOnMainThread:@selector(callflushimg:) |
| withObject:[NSValue valueWithRect:rect] |
| waitUntilDone:YES |
| modes:[NSArray arrayWithObjects: |
| NSRunLoopCommonModes, |
| @"waiting image", nil]]; |
| } |
| |
| static void drawimg(NSRect, uint); |
| static void drawresizehandle(void); |
| |
| enum |
| { |
| Pixel = 1, |
| Barsize = 4*Pixel, |
| Cornersize = 3*Pixel, |
| Handlesize = 3*Barsize + 1*Pixel, |
| }; |
| |
| /* |
| * |rect| is in pixel coordinates. |
| */ |
| static void |
| flushimg(NSRect rect) |
| { |
| NSRect dr, r; |
| |
| if([win.content lockFocusIfCanDraw] == 0) |
| return; |
| |
| if(win.needimg){ |
| if(!NSEqualSizes(scalerect(rect, win.topointscale).size, [win.img size])){ |
| LOG(@"flushimg reject %.0f %.0f", |
| rect.size.width, rect.size.height); |
| [win.content unlockFocus]; |
| return; |
| } |
| win.needimg = 0; |
| }else |
| win.deferflush = 1; |
| |
| LOG(@"flushimg ok %.0f %.0f", rect.size.width, rect.size.height); |
| |
| /* |
| * Unless we are inside "drawRect", we have to round |
| * the corners ourselves, if this is the custom. |
| * "NSCompositeSourceIn" can do that, but we don't |
| * apply it to the whole rectangle, because this |
| * slows down trackpad scrolling considerably in |
| * Acme. |
| */ |
| r = [win.content bounds]; |
| rect = dilate(scalerect(rect, win.topointscale)); |
| r.size.height -= Cornersize; |
| dr = NSIntersectionRect(r, rect); |
| LOG(@"r %.0f %.0f", r.origin.x, r.origin.y, rect.size.width, rect.size.height); |
| LOG(@"rect in points %f %f %.0f %.0f", rect.origin.x, rect.origin.y, rect.size.width, rect.size.height); |
| LOG(@"dr in points %f %f %.0f %.0f", dr.origin.x, dr.origin.y, dr.size.width, dr.size.height); |
| drawimg(dr, NSCompositeCopy); |
| |
| r.origin.y = r.size.height; |
| r.size = NSMakeSize(Cornersize, Cornersize); |
| dr = NSIntersectionRect(r, rect); |
| drawimg(dr, NSCompositeSourceIn); |
| |
| r.origin.x = [win.img size].width - Cornersize; |
| dr = NSIntersectionRect(r, rect); |
| drawimg(dr, NSCompositeSourceIn); |
| |
| r.size.width = r.origin.x - Cornersize; |
| r.origin.x -= r.size.width; |
| dr = NSIntersectionRect(r, rect); |
| drawimg(dr, NSCompositeCopy); |
| |
| if(OSX_VERSION<100700 && win.isofs==0){ |
| r.origin.x = [win.img size].width - Handlesize; |
| r.origin.y = [win.img size].height - Handlesize; |
| r.size = NSMakeSize(Handlesize, Handlesize); |
| if(NSIntersectsRect(r, rect)) |
| drawresizehandle(); |
| } |
| [win.content unlockFocus]; |
| } |
| |
| static void |
| autoflushwin(int set) |
| { |
| static NSTimer *t; |
| |
| if(set){ |
| if(t) |
| return; |
| /* |
| * We need "NSRunLoopCommonModes", otherwise the |
| * timer will not fire during live resizing. |
| */ |
| t = [NSTimer |
| timerWithTimeInterval:0.033 |
| target:[appdelegate class] |
| selector:@selector(callflushwin:) userInfo:nil |
| repeats:YES]; |
| [[NSRunLoop currentRunLoop] addTimer:t |
| forMode:NSRunLoopCommonModes]; |
| }else{ |
| [t invalidate]; |
| t = nil; |
| win.deferflush = 0; |
| } |
| } |
| |
| static void |
| flushwin(void) |
| { |
| if(win.deferflush && win.needimg==0){ |
| [WIN flushWindow]; |
| win.deferflush = 0; |
| } |
| } |
| |
| /* |
| * |dr| is sized in points. What if I make it pixels? |
| */ |
| static void |
| drawimg(NSRect dr, uint op) |
| { |
| CGContextRef c; |
| CGImageRef i; |
| NSRect sr; |
| |
| if(NSIsEmptyRect(dr)) |
| return; |
| |
| sr = [win.content convertRect:dr fromView:nil]; |
| LOG(@"before dr: %f %f %f %f\n", dr.origin.x, dr.origin.y, dr.size.width, dr.size.height); |
| LOG(@"before sr: %f %f %f %f\n", sr.origin.x, sr.origin.y, sr.size.width, sr.size.height); |
| |
| dr = scalerect(dr, win.topixelscale); |
| sr = scalerect(sr, win.topixelscale); |
| |
| LOG(@"dr: %f %f %f %f\n", dr.origin.x, dr.origin.y, dr.size.width, dr.size.height); |
| LOG(@"sr: %f %f %f %f\n", sr.origin.x, sr.origin.y, sr.size.width, sr.size.height); |
| if(OSX_VERSION >= 100800){ |
| i = CGImageCreateWithImageInRect([win.img CGImage], NSRectToCGRect(dr)); |
| c = [[WIN graphicsContext] graphicsPort]; |
| |
| CGContextSaveGState(c); |
| if(op == NSCompositeSourceIn) |
| CGContextSetBlendMode(c, kCGBlendModeSourceIn); |
| LOG(@"wim.img size %f %f\n", [win.img size].width, [win.img size].height); |
| CGContextTranslateCTM(c, 0, [win.img size].height); |
| CGContextScaleCTM(c, win.topointscale, -win.topointscale); |
| CGContextDrawImage(c, NSRectToCGRect(sr), i); |
| CGContextRestoreGState(c); |
| |
| CGImageRelease(i); |
| }else{ |
| [win.img drawInRect:dr fromRect:sr |
| operation:op fraction:1 |
| respectFlipped:YES hints:nil]; |
| } |
| // NSFrameRect(dr); |
| } |
| |
| static void |
| drawresizehandle(void) |
| { |
| NSColor *color[Barsize]; |
| NSPoint a,b; |
| Point c; |
| int i,j; |
| |
| c = Pt([win.img size].width, [win.img size].height); |
| |
| [[WIN graphicsContext] setShouldAntialias:NO]; |
| |
| color[0] = [NSColor clearColor]; |
| color[1] = [NSColor darkGrayColor]; |
| color[2] = [NSColor lightGrayColor]; |
| color[3] = [NSColor whiteColor]; |
| |
| for(i=1; i+Barsize <= Handlesize; ) |
| for(j=0; j<Barsize; j++){ |
| [color[j] setStroke]; |
| i++; |
| a = NSMakePoint(c.x-i, c.y-1); |
| b = NSMakePoint(c.x-2, c.y+1-i); |
| [NSBezierPath strokeLineFromPoint:a toPoint:b]; |
| } |
| } |
| |
| static void getgesture(NSEvent*); |
| static void getkeyboard(NSEvent*); |
| static void getmouse(NSEvent*); |
| static void gettouch(NSEvent*, int); |
| static void updatecursor(void); |
| |
| @implementation contentview |
| /* |
| * "drawRect" is called each time Cocoa needs an |
| * image, and each time we call "display". It is |
| * preceded by background painting, and followed by |
| * "flushWindow". |
| */ |
| - (void)drawRect:(NSRect)r |
| { |
| static int first = 1; |
| |
| LOG(@"drawrect %.0f %.0f %.0f %.0f", |
| r.origin.x, r.origin.y, r.size.width, r.size.height); |
| |
| if(first) |
| first = 0; |
| else |
| resizeimg(); |
| |
| if([WIN inLiveResize]) |
| waitimg(100); |
| else |
| waitimg(500); |
| } |
| - (BOOL)isFlipped |
| { |
| return YES; /* to make the content's origin top left */ |
| } |
| - (BOOL)acceptsFirstResponder |
| { |
| return YES; /* else no keyboard */ |
| } |
| - (id)initWithFrame:(NSRect)r |
| { |
| [super initWithFrame:r]; |
| [self setAcceptsTouchEvents:YES]; |
| [self setHidden:YES]; /* to avoid early "drawRect" call */ |
| return self; |
| } |
| - (void)setHidden:(BOOL)set |
| { |
| if(!set) |
| [WIN makeFirstResponder:self]; /* for keyboard focus */ |
| [super setHidden:set]; |
| } |
| - (void)cursorUpdate:(NSEvent*)e{ updatecursor();} |
| |
| - (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{ 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 |
| |
| 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', |
| }; |
| |
| @interface apptext : NSTextView @end |
| |
| @implementation apptext |
| - (void)doCommandBySelector:(SEL)s{} /* Esc key beeps otherwise */ |
| - (void)insertText:(id)arg{} /* to avoid a latency after some time */ |
| @end |
| |
| static void |
| interpretdeadkey(NSEvent *e) |
| { |
| static apptext *t; |
| |
| if(t == nil) |
| t = [apptext new]; |
| [t interpretKeyEvents:[NSArray arrayWithObject:e]]; |
| } |
| |
| static void |
| getkeyboard(NSEvent *e) |
| { |
| static int omod; |
| NSString *s; |
| char c; |
| int k, m; |
| uint code; |
| |
| m = [e modifierFlags]; |
| |
| switch([e type]){ |
| case NSKeyDown: |
| s = [e characters]; |
| c = [s UTF8String][0]; |
| |
| interpretdeadkey(e); |
| |
| if(m & NSCommandKeyMask){ |
| if(' '<=c && c<='~') |
| keystroke(Kcmd+c); |
| break; |
| } |
| k = c; |
| code = [e keyCode]; |
| if(code<nelem(keycvt) && keycvt[code]) |
| k = keycvt[code]; |
| if(k==0) |
| break; |
| if(k>0) |
| keystroke(k); |
| else |
| keystroke([s characterAtIndex:0]); |
| break; |
| |
| case NSFlagsChanged: |
| if(in.mbuttons || in.kbuttons){ |
| in.kbuttons = 0; |
| if(m & NSAlternateKeyMask) |
| in.kbuttons |= 2; |
| if(m & NSCommandKeyMask) |
| in.kbuttons |= 4; |
| sendmouse(); |
| }else |
| if(m&NSAlternateKeyMask && (omod&NSAlternateKeyMask)==0) |
| keystroke(Kalt); |
| break; |
| |
| default: |
| panic("getkey: unexpected event type"); |
| } |
| omod = m; |
| } |
| |
| /* |
| * Devdraw does not use NSTrackingArea, that often |
| * forgets to update the cursor on entering and on |
| * leaving the area, and that sometimes stops sending |
| * us MouseMove events, at least on OS X Lion. |
| */ |
| static void |
| updatecursor(void) |
| { |
| NSCursor *c; |
| int isdown, isinside; |
| |
| isinside = NSPointInRect(in.mpos, [win.content bounds]); |
| isdown = (in.mbuttons || in.kbuttons); |
| |
| if(win.cursor && (isinside || isdown)) |
| c = win.cursor; |
| else if(isinside && usebigarrow) |
| c = in.bigarrow; |
| else |
| c = [NSCursor arrowCursor]; |
| [c set]; |
| |
| /* |
| * Without this trick, we can come back from the dock |
| * with a resize cursor. |
| */ |
| if(OSX_VERSION >= 100700) |
| [NSCursor unhide]; |
| } |
| |
| static void |
| acceptresizing(int set) |
| { |
| uint old, style; |
| |
| old = [WIN styleMask]; |
| |
| if((old | NSResizableWindowMask) != Winstyle) |
| return; /* when entering new fullscreen */ |
| |
| if(set) |
| style = Winstyle; |
| else |
| style = Winstyle & ~NSResizableWindowMask; |
| |
| if(style != old) |
| [WIN setStyleMask:style]; |
| } |
| |
| static void |
| getmousepos(void) |
| { |
| NSPoint p, q; |
| |
| p = [WIN mouseLocationOutsideOfEventStream]; |
| q = [win.content convertPoint:p fromView:nil]; |
| |
| /* q is in point coordinates. in.mpos is in pixels. */ |
| q = scalepoint(q, win.topixelscale); |
| |
| in.mpos.x = round(q.x); |
| in.mpos.y = round(q.y); |
| |
| updatecursor(); |
| |
| if(win.isnfs || win.isofs) |
| hidebars(1); |
| else if(OSX_VERSION>=100700 && [WIN inLiveResize]==0){ |
| if(p.x<12 && p.y<12 && p.x>2 && p.y>2) |
| acceptresizing(0); |
| else |
| acceptresizing(1); |
| } |
| } |
| |
| static void |
| getmouse(NSEvent *e) |
| { |
| float d; |
| int b, m; |
| |
| if([WIN isKeyWindow] == 0) |
| return; |
| |
| 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){ |
| abortcompose(); |
| b = 2; |
| }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(); |
| } |
| |
| #define Minpinch 0.02 |
| |
| static void |
| getgesture(NSEvent *e) |
| { |
| switch([e type]){ |
| case NSEventTypeMagnify: |
| if(fabs([e magnification]) > Minpinch) |
| togglefs(); |
| break; |
| } |
| } |
| |
| static void sendclick(int); |
| |
| static uint |
| msec(void) |
| { |
| return nsec()/1000000; |
| } |
| |
| static void |
| gettouch(NSEvent *e, int type) |
| { |
| static int tapping; |
| static uint taptime; |
| NSSet *set; |
| int p; |
| |
| 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; |
| break; |
| |
| case NSTouchPhaseMoved: |
| tapping = 0; |
| break; |
| |
| case NSTouchPhaseEnded: |
| p = NSTouchPhaseTouching; |
| set = [e touchesMatchingPhase:p inView:nil]; |
| if(set.count == 0){ |
| if(tapping && msec()-taptime<400) |
| sendclick(2); |
| tapping = 0; |
| } |
| break; |
| |
| case NSTouchPhaseCancelled: |
| break; |
| |
| default: |
| panic("gettouch: unexpected event type"); |
| } |
| } |
| |
| static void |
| sendclick(int b) |
| { |
| in.mbuttons = b; |
| sendmouse(); |
| in.mbuttons = 0; |
| sendmouse(); |
| } |
| |
| static void |
| sendmouse(void) |
| { |
| NSSize size; |
| int b; |
| |
| size = winsizepixels(); |
| mouserect = Rect(0, 0, size.width, size.height); |
| |
| b = in.kbuttons | in.mbuttons | in.mscroll; |
| mousetrack(in.mpos.x, in.mpos.y, b, msec()); |
| in.mscroll = 0; |
| } |
| |
| /* |
| * |p| is in pixels. |
| */ |
| void |
| setmouse(Point p) |
| { |
| static int first = 1; |
| NSPoint q; |
| NSRect r; |
| |
| if([NSApp isActive]==0 && in.willactivate==0) |
| return; |
| |
| if(first){ |
| /* Try to move Acme's scrollbars without that! */ |
| CGSetLocalEventsSuppressionInterval(0); |
| first = 0; |
| } |
| if([WIN inLiveResize]) |
| return; |
| |
| in.mpos = scalepoint(NSMakePoint(p.x, p.y), win.topointscale); // race condition |
| |
| q = [win.content convertPoint:in.mpos toView:nil]; |
| q = [WIN convertBaseToScreen:q]; |
| |
| r = [[[NSScreen screens] objectAtIndex:0] frame]; |
| q.y = r.size.height - q.y; /* Quartz is top-left-based here */ |
| |
| CGWarpMouseCursorPosition(NSPointToCGPoint(q)); |
| } |
| |
| /* |
| * |r| is in points. |
| */ |
| static void |
| followzoombutton(NSRect r) |
| { |
| NSRect wr; |
| Point p; |
| NSPoint pt; |
| |
| wr = [WIN frame]; |
| wr.origin.y += wr.size.height; |
| r.origin.y += r.size.height; |
| |
| getmousepos(); |
| pt.x = in.mpos.x; |
| pt.y = in.mpos.y; |
| pt = scalepoint(pt, win.topointscale); |
| pt.x = (r.origin.x - wr.origin.x) + pt.x; |
| pt.y = -(r.origin.y - wr.origin.y) + pt.y; |
| pt = scalepoint(pt, win.topixelscale); |
| |
| p.x = pt.x; |
| p.y = pt.y; |
| |
| setmouse(p); |
| } |
| |
| static void |
| togglefs(void) |
| { |
| uint opt, tmp; |
| |
| #if OSX_VERSION >= 100700 |
| NSScreen *s, *s0; |
| |
| s = [WIN screen]; |
| s0 = [[NSScreen screens] objectAtIndex:0]; |
| |
| if((s==s0 && useoldfullscreen==0) || win.isnfs) { |
| [WIN toggleFullScreen:nil]; |
| return; |
| } |
| #endif |
| [win.content retain]; |
| [WIN orderOut:nil]; |
| [WIN setContentView:nil]; |
| |
| win.isofs = ! win.isofs; |
| hidebars(win.isofs); |
| |
| /* |
| * If we move the window from one space to another, |
| * ofs[0] and ofs[1] can be on different spaces. |
| * This "setCollectionBehavior" trick moves the |
| * window to the active space. |
| */ |
| opt = [WIN collectionBehavior]; |
| tmp = opt | NSWindowCollectionBehaviorCanJoinAllSpaces; |
| [WIN setContentView:win.content]; |
| [WIN setCollectionBehavior:tmp]; |
| [WIN makeKeyAndOrderFront:nil]; |
| [WIN setCollectionBehavior:opt]; |
| [win.content release]; |
| } |
| |
| enum |
| { |
| Autohiddenbars = NSApplicationPresentationAutoHideDock |
| | NSApplicationPresentationAutoHideMenuBar, |
| |
| Hiddenbars = NSApplicationPresentationHideDock |
| | NSApplicationPresentationHideMenuBar, |
| }; |
| |
| static void |
| hidebars(int set) |
| { |
| NSScreen *s,*s0; |
| uint old, opt; |
| |
| s = [WIN screen]; |
| s0 = [[NSScreen screens] objectAtIndex:0]; |
| old = [NSApp presentationOptions]; |
| |
| #if OSX_VERSION >= 100700 |
| /* This bit can get lost, resulting in dreadful bugs. */ |
| if(win.isnfs) |
| old |= NSApplicationPresentationFullScreen; |
| #endif |
| |
| if(set && s==s0) |
| opt = (old & ~Autohiddenbars) | Hiddenbars; |
| else |
| opt = old & ~(Autohiddenbars | Hiddenbars); |
| |
| if(opt != old) |
| [NSApp setPresentationOptions:opt]; |
| } |
| |
| static void |
| makemenu(void) |
| { |
| NSMenu *m; |
| NSMenuItem *i0,*i1; |
| |
| m = [NSMenu new]; |
| i0 = [m addItemWithTitle:@"app" action:NULL keyEquivalent:@""]; |
| i1 = [m addItemWithTitle:@"help" action:NULL keyEquivalent:@""]; |
| [NSApp setMainMenu:m]; |
| [m release]; |
| |
| m = [[NSMenu alloc] initWithTitle:@"app"]; |
| [m addItemWithTitle:@"Full Screen" |
| action:@selector(calltogglefs:) |
| keyEquivalent:@"f"]; |
| [m addItemWithTitle:@"Hide" |
| action:@selector(hide:) |
| keyEquivalent:@"h"]; |
| [m addItemWithTitle:@"Quit" |
| action:@selector(terminate:) |
| keyEquivalent:@"q"]; |
| [i0 setSubmenu:m]; |
| [m release]; |
| |
| m = [[NSMenu alloc] initWithTitle:@"help"]; |
| [m addItemWithTitle:@"Plumb devdraw(1)" |
| action:@selector(plumbmanual:) |
| keyEquivalent:@""]; |
| [i1 setSubmenu:m]; |
| [m release]; |
| } |
| |
| // FIXME: Introduce a high-resolution Glenda image. |
| 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]; |
| |
| qlock(&snarfl); |
| s = [pb stringForType:NSPasteboardTypeString]; |
| qunlock(&snarfl); |
| |
| if(s) |
| return strdup((char*)[s UTF8String]); |
| else |
| return nil; |
| } |
| |
| void |
| putsnarf(char *s) |
| { |
| NSArray *t; |
| NSPasteboard *pb; |
| NSString *str; |
| |
| if(strlen(s) >= SnarfSize) |
| return; |
| |
| t = [NSArray arrayWithObject:NSPasteboardTypeString]; |
| pb = [NSPasteboard generalPasteboard]; |
| str = [[NSString alloc] initWithUTF8String:s]; |
| |
| qlock(&snarfl); |
| [pb declareTypes:t owner:nil]; |
| [pb setString:str forType:NSPasteboardTypeString]; |
| qunlock(&snarfl); |
| |
| [str release]; |
| } |
| |
| void |
| kicklabel(char *label) |
| { |
| NSString *s; |
| |
| if(label == nil) |
| return; |
| |
| s = [[NSString alloc] initWithUTF8String:label]; |
| [win.ofs[0] setTitle:s]; |
| [win.ofs[1] setTitle:s]; |
| [[NSApp dockTile] setBadgeLabel:s]; |
| [s release]; |
| } |
| |
| void |
| setcursor(Cursor *c) |
| { |
| /* |
| * No cursor change unless in main thread. |
| */ |
| [appdelegate |
| performSelectorOnMainThread:@selector(callsetcursor0:) |
| withObject:[NSValue valueWithPointer:c] |
| waitUntilDone:YES]; |
| } |
| |
| static void |
| setcursor0(Cursor *c) |
| { |
| NSCursor *d; |
| |
| d = win.cursor; |
| |
| if(c) |
| win.cursor = makecursor(c); |
| else |
| win.cursor = nil; |
| |
| updatecursor(); |
| |
| if(d) |
| [d release]; |
| } |
| |
| /* |
| * Cursors will be scaled on retina display. |
| */ |
| static NSCursor* |
| makecursor(Cursor *c) |
| { |
| NSBitmapImageRep *r; |
| NSCursor *d; |
| NSImage *i; |
| NSPoint p; |
| int b; |
| uchar *plane[5]; |
| |
| 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]; |
| [r release]; |
| |
| d = [[NSCursor alloc] initWithImage:i hotSpot:p]; |
| [i release]; |
| return d; |
| } |
| |
| void |
| topwin(void) |
| { |
| [WIN performSelectorOnMainThread: |
| @selector(makeKeyAndOrderFront:) |
| withObject:nil |
| waitUntilDone:NO]; |
| |
| in.willactivate = 1; |
| [NSApp activateIgnoringOtherApps:YES]; |
| } |
| |
| static NSSize |
| winsizepoints() |
| { |
| return [win.content bounds].size; |
| } |
| |
| static NSSize |
| winsizepixels() |
| { |
| if (OSX_VERSION >= 100700 && devdrawretina) |
| return [win.content convertSizeToBacking: winsizepoints()]; |
| else |
| return winsizepoints(); |
| } |
| |
| static NSRect |
| scalerect(NSRect r, CGFloat scale) |
| { |
| r.origin.x *= scale; |
| r.origin.y *= scale; |
| r.size.width *= scale; |
| r.size.height *= scale; |
| return r; |
| } |
| |
| /* |
| * Expands rectangle |r|'s bounds to more inclusive integer bounds to |
| * eliminate 1 pixel gaps. |
| */ |
| static NSRect |
| dilate(NSRect r) |
| { |
| if(win.topixelscale > 1.0f){ |
| r.origin.x = floorf(r.origin.x); |
| r.origin.y = floorf(r.origin.y); |
| r.size.width = ceilf(r.size.width + 0.5); |
| r.size.height = ceilf(r.size.height + 0.5); |
| } |
| return r; |
| } |
| |
| static NSPoint |
| scalepoint(NSPoint pt, CGFloat scale) |
| { |
| pt.x *= scale; |
| pt.y *= scale; |
| return pt; |
| } |