Devdraw now waits for the image: this means that
there is no more blank image when toggling
fullscreen, when unminimizing, or at startup; this
also means that we can live resize, but we only
enable live resizing with Page, because it seems
useless for other apps, and Acme and Sam bug with
it. The tradeoff is that bottom corners are
sometimes automatically rounded. There is a way to
prevent the rounding here:
http://parmanoir.com/Custom_NSThemeFrame
but this would obfuscate the code. Instead, we
make sure that the corners are always rounded.

Closing the window while its content is updated
causes an exception, without this patch. This
seems to happen regularly with stats(1).

This patch avoids a possible deadlock at startup,
which I never experienced. If I recollect right,
there is little chance that this happens on a
multi-core CPU.

Minimizing now activates next app in line, and
Devdraw now stops drawing while minimized.

R=rsc
CC=plan9port.codebot
http://codereview.appspot.com/5499043
diff --git a/src/cmd/devdraw/cocoa-screen.m b/src/cmd/devdraw/cocoa-screen.m
index 3f2bcb8..9cc0e13 100644
--- a/src/cmd/devdraw/cocoa-screen.m
+++ b/src/cmd/devdraw/cocoa-screen.m
@@ -1,5 +1,8 @@
 /*
  * Cocoa's event loop must be in main thread.
+ *
+ * Unless otherwise stated, all coordinate systems
+ * are bottom-left-based.
  */
 
 #define Cursor OSXCursor
@@ -27,9 +30,11 @@
 
 AUTOFRAMEWORK(Cocoa)
 
-#define panic sysfatal
+#define LOG	if(0)NSLog
+#define panic	sysfatal
 
 int usegestures = 0;
+int useliveresizing = 0;
 int useoldfullscreen = 0;
 int usebigarrow = 0;
 
@@ -91,7 +96,8 @@
 	int			isnfs;
 	NSView		*content;
 	NSBitmapImageRep	*img;
-	int			needflush;
+	int			needimg;
+	int			deferflush;
 	NSCursor		*cursor;
 } win;
 
@@ -106,7 +112,8 @@
 } in;
 
 static void hidebars(int);
-static void drawimg(NSRect);
+static void flushimg(NSRect);
+static void autoflushwin(int);
 static void flushwin(void);
 static void followzoombutton(NSRect);
 static void getmousepos(void);
@@ -140,9 +147,15 @@
 	getmousepos();
 	sendmouse();
 }
+- (void)windowWillStartLiveResize:(id)arg
+{
+	if(useliveresizing == 0)
+		[win.content setHidden:YES];
+}
 - (void)windowDidEndLiveResize:(id)arg
 {
-	[win.content display];
+	if(useliveresizing == 0)
+		[win.content setHidden:NO];
 }
 - (void)windowDidChangeScreen:(id)arg
 {
@@ -174,6 +187,10 @@
 		hidebars(0);
 	}
 }
+- (void)windowWillClose:(id)arg
+{
+	autoflushwin(0);	/* can crash otherwise */
+}
 
 + (void)callservep9p:(id)arg
 {
@@ -189,7 +206,7 @@
 + (void)callflushwin:(id)arg{ flushwin();}
 - (void)calltogglefs:(id)arg{ togglefs();}
 
-+ (void)calldrawimg:(NSValue*)v{ drawimg([v rectValue]);}
++ (void)callflushimg:(NSValue*)v{ flushimg([v rectValue]);}
 + (void)callmakewin:(NSValue*)v{ makewin([v pointerValue]);}
 + (void)callsetcursor0:(NSValue*)v{ setcursor0([v pointerValue]);}
 @end
@@ -208,6 +225,8 @@
 
 	if(label == nil)
 		label = "gnot a label";
+	if(strcmp(label, "page") == 0)
+		useliveresizing = 1;
 
 	/*
 	 * Create window in main thread, else no cursor
@@ -235,6 +254,28 @@
 {
 	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
@@ -302,7 +343,6 @@
 	win.isofs = 0;
 	win.content = [contentview new];
 	[WIN setContentView:win.content];
-	[WIN makeKeyAndOrderFront:nil];
 }
 
 static Memimage*
@@ -313,6 +353,7 @@
 	Rectangle r;
 
 	size = [win.content bounds].size;
+	LOG(@"initimg %.0f %.0f", size.width, size.height);
 
 	r = Rect(0, 0, size.width, size.height);
 	i = allocmemimage(r, XBGR32);
@@ -332,102 +373,204 @@
 		colorSpaceName:NSDeviceRGBColorSpace
 		bytesPerRow:bytesperline(r, 32)
 		bitsPerPixel:32];
-
 	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;
 
-	rect = NSMakeRect(r.min.x, r.min.y, Dx(r), Dy(r));
+	LOG(@"_flushmemscreen");
 
-	/*
-	 * Call "lockFocusIfCanDraw" from main thread, else
-	 * we deadlock while synchronizing both threads with
-	 * qlock(): main thread must apparently be idle while
-	 * we call it.  (This is also why Devdraw shows
-	 * occasionally an empty window: I found no
-	 * satisfactory way to wait for P9P's image.)
-	 */
+	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(calldrawimg:)
+		performSelectorOnMainThread:@selector(callflushimg:)
 		withObject:[NSValue valueWithRect:rect]
-		waitUntilDone:YES];
+		waitUntilDone:YES
+		modes:[NSArray arrayWithObjects:
+			NSRunLoopCommonModes,
+			@"waiting image", nil]];
 }
 
-static void drawresizehandle(NSRect);
+static void drawimg(NSRect, uint);
+static void drawresizehandle(void);
+
+enum
+{
+	Pixel = 1,
+	Barsize = 4*Pixel,
+	Cornersize = 3*Pixel,
+	Handlesize = 3*Barsize + 1*Pixel,
+};
 
 static void
-drawimg(NSRect dr)
+flushimg(NSRect rect)
 {
-	static int first = 1;
-	NSRect sr;
+	NSRect dr, r;
 
-	if(first){
-		[NSTimer scheduledTimerWithTimeInterval:0.033
+	if([win.content lockFocusIfCanDraw] == 0)
+		return;
+
+	if(win.needimg){
+		if(!NSEqualSizes(rect.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];
+	r.size.height -= Cornersize;
+	dr = NSIntersectionRect(r, rect);
+	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];
-		first = 0;
-	}
-	sr =  [win.content convertRect:dr fromView:nil];
-
-	if([win.content lockFocusIfCanDraw]){
-
-		/*
-		 * To round the window's bottom corners, we can use
-		 * "NSCompositeSourceIn", but this slows down
-		 * trackpad scrolling considerably in Acme.  Else we
-		 * can use "bezierPathWithRoundedRect" with "addClip",
-		 * but it's still too slow for wide Acme windows.
-		 */
-		[win.img drawInRect:dr fromRect:sr
-//			operation:NSCompositeSourceIn fraction:1
-			operation:NSCompositeCopy fraction:1
-			respectFlipped:YES hints:nil];
-
-		if(OSX_VERSION<100700 && win.isofs==0)
-			drawresizehandle(dr);
-
-		[win.content unlockFocus];
-		win.needflush = 1;
+		[[NSRunLoop currentRunLoop] addTimer:t
+			forMode:NSRunLoopCommonModes];
+	}else{
+		[t invalidate];
+		t = nil;
+		win.deferflush = 0;
 	}
 }
 
 static void
 flushwin(void)
 {
-	if(win.needflush){
+	if(win.deferflush && win.needimg==0){
 		[WIN flushWindow];
-		win.needflush = 0;
+		win.deferflush = 0;
 	}
 }
 
-enum
+static void
+drawimg(NSRect dr, uint op)
 {
-	Pixel = 1,
-	Barsize = 4*Pixel,
-	Handlesize = 3*Barsize + 1*Pixel,
-};
+	NSRect sr;
+
+	if(NSIsEmptyRect(dr))
+		return;
+
+	sr =  [win.content convertRect:dr fromView:nil];
+
+	[win.img drawInRect:dr fromRect:sr
+		operation:op fraction:1
+		respectFlipped:YES hints:nil];
+
+//	NSFrameRect(dr);
+}
 
 static void
-drawresizehandle(NSRect dr)
+drawresizehandle(void)
 {
 	NSColor *color[Barsize];
 	NSPoint a,b;
-	NSRect r;
-	NSSize size;
 	Point c;
 	int i,j;
 
-	size = [win.img size];
-	c = Pt(size.width, size.height);
-	r = NSMakeRect(0, 0, Handlesize, Handlesize);
-	r.origin = NSMakePoint(c.x-Handlesize, c.y-Handlesize);
-	if(NSIntersectsRect(r,dr) == 0)
-		return;
+	c = Pt([win.img size].width, [win.img size].height);
 
 	[[WIN graphicsContext] setShouldAntialias:NO];
 
@@ -446,15 +589,6 @@
 		}
 }
 
-static void
-resizeimg()
-{
-	[win.img release];
-	_drawreplacescreenimage(initimg());
-	mouseresized = 1;
-	sendmouse();
-}
-
 static void getgesture(NSEvent*);
 static void getkeyboard(NSEvent*);
 static void getmouse(NSEvent*);
@@ -462,20 +596,28 @@
 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;
 
-	if([WIN inLiveResize])
-		return;
+	LOG(@"drawrect %.0f %.0f %.0f %.0f",
+		r.origin.x, r.origin.y, r.size.width, r.size.height);
 
 	if(first)
 		first = 0;
 	else
 		resizeimg();
 
-	/* We should wait for P9P's image here. */
+	if([WIN inLiveResize])
+		waitimg(100);
+	else
+		waitimg(500);
 }
 - (BOOL)isFlipped
 {
@@ -489,8 +631,15 @@
 {
 	[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);}
@@ -881,6 +1030,9 @@
 		CGSetLocalEventsSuppressionInterval(0);
 		first = 0;
 	}
+	if([WIN inLiveResize])
+		return;
+
 	in.mpos = NSMakePoint(p.x, p.y);	// race condition
 
 	r = [[WIN screen] frame];