devdraw: MacBook retina support

Enable with export devdrawretina=1 (everything will be smaller).

R=rsc
CC=plan9port.codebot
http://codereview.appspot.com/6592072
diff --git a/man/man1/devdraw.1 b/man/man1/devdraw.1
index 6ffe9ec..009f5a9 100644
--- a/man/man1/devdraw.1
+++ b/man/man1/devdraw.1
@@ -10,6 +10,13 @@
 .I Devdraw
 serves a custom graphics protocol and is the only program
 that talks directly to X window servers.
+On Macintosh, setting
+.BI devdrawretina
+to
+.BI 1
+will cause
+.I devdraw
+to use all available physical pixels on a retina display.
 .SH SOURCE
 .B \*9/src/cmd/devdraw
 .SH "SEE ALSO
diff --git a/src/cmd/devdraw/cocoa-screen.m b/src/cmd/devdraw/cocoa-screen.m
index 6baffd4..bceb7e3 100644
--- a/src/cmd/devdraw/cocoa-screen.m
+++ b/src/cmd/devdraw/cocoa-screen.m
@@ -38,6 +38,12 @@
 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)
 {
@@ -50,6 +56,8 @@
 void
 threadmain(int argc, char **argv)
 {
+	char *envvar;
+
 	/*
 	 * Move the protocol off stdin/stdout so that
 	 * any inadvertent prints don't screw things up.
@@ -77,6 +85,9 @@
 		usage();
 	}ARGEND
 
+	if (envvar = getenv("devdrawretina"))
+		devdrawretina = atoi(envvar) > 0;
+
 	if(OSX_VERSION < 100700)
 		[NSAutoreleasePool new];
 
@@ -99,6 +110,8 @@
 	int			needimg;
 	int			deferflush;
 	NSCursor		*cursor;
+	CGFloat		topointscale;
+	CGFloat		topixelscale;
 } win;
 
 struct
@@ -127,6 +140,12 @@
 
 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
 {
@@ -349,10 +368,10 @@
 initimg(void)
 {
 	Memimage *i;
-	NSSize size;
+	NSSize size, ptsize;
 	Rectangle r;
 
-	size = [win.content bounds].size;
+	size = winsizepixels();
 	LOG(@"initimg %.0f %.0f", size.width, size.height);
 
 	r = Rect(0, 0, size.width, size.height);
@@ -373,6 +392,10 @@
 		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;
 }
 
@@ -452,6 +475,9 @@
 	Handlesize = 3*Barsize + 1*Pixel,
 };
 
+/*
+ * |rect| is in pixel coordinates.
+ */
 static void
 flushimg(NSRect rect)
 {
@@ -461,7 +487,7 @@
 		return;
 
 	if(win.needimg){
-		if(!NSEqualSizes(rect.size, [win.img size])){
+		if(!NSEqualSizes(scalerect(rect, win.topointscale).size, [win.img size])){
 			LOG(@"flushimg reject %.0f %.0f",
 				rect.size.width, rect.size.height);
 			[win.content unlockFocus];
@@ -482,8 +508,12 @@
 	 * 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;
@@ -545,6 +575,9 @@
 	}
 }
 
+/*
+ * |dr| is sized in points. What if I make it pixels?
+ */
 static void
 drawimg(NSRect dr, uint op)
 {
@@ -556,7 +589,14 @@
 		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];
@@ -564,8 +604,9 @@
 		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, 1, -1);
+		CGContextScaleCTM(c, win.topointscale, -win.topointscale);
 		CGContextDrawImage(c, NSRectToCGRect(sr), i);
 		CGContextRestoreGState(c);
 
@@ -871,6 +912,10 @@
 
 	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);
 
@@ -1023,7 +1068,7 @@
 	NSSize size;
 	int b;
 
-	size = [win.content bounds].size;
+	size = winsizepixels();
 	mouserect = Rect(0, 0, size.width, size.height);
 
 	b = in.kbuttons | in.mbuttons | in.mscroll;
@@ -1031,6 +1076,9 @@
 	in.mscroll = 0;
 }
 
+/*
+ * |p| is in pixels.
+ */
 void
 setmouse(Point p)
 {
@@ -1049,7 +1097,7 @@
 	if([WIN inLiveResize])
 		return;
 
-	in.mpos = NSMakePoint(p.x, p.y);	// race condition
+	in.mpos = scalepoint(NSMakePoint(p.x, p.y), win.topointscale);	// race condition
 
 	q = [win.content convertPoint:in.mpos toView:nil];
 	q = [WIN convertBaseToScreen:q];
@@ -1060,19 +1108,31 @@
 	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();
-	p.x = (r.origin.x - wr.origin.x) + in.mpos.x;
-	p.y = -(r.origin.y - wr.origin.y) + in.mpos.y;
+	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);
 }
 
@@ -1181,6 +1241,7 @@
 	[m release];
 }
 
+// FIXME: Introduce a high-resolution Glenda image.
 static void
 makeicon(void)
 {
@@ -1285,6 +1346,9 @@
 		[d release];
 }
 
+/*
+ * Cursors will be scaled on retina display.
+ */
 static NSCursor*
 makecursor(Cursor *c)
 {
@@ -1334,3 +1398,52 @@
 	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;
+}