From http://www.jwz.org/xscreensaver/xscreensaver-5.19.tar.gz
[xscreensaver] / OSX / jwxyz.m
index 7643c4400003513f5a7508fa54c0e727ce3b5089..f9dc5b007b163376c6fd8303b50064cd2164d3bd 100644 (file)
@@ -23,6 +23,7 @@
 # import <UIKit/UIKit.h>
 # import <UIKit/UIScreen.h>
 # import <QuartzCore/QuartzCore.h>
+# import <CoreText/CTFont.h>
 # define NSView  UIView
 # define NSRect  CGRect
 # define NSPoint CGPoint
 
 #import "jwxyz.h"
 #import "jwxyz-timers.h"
+#import "yarandom.h"
+
+#ifdef USE_IPHONE
+# define USE_BACKBUFFER  /* must be in sync with XScreenSaverView.h */
+#endif
 
 #undef  Assert
-#define Assert(C,S) do { \
-  if (!(C)) { \
-    NSLog(@"%s",S); \
-    abort(); \
-  }} while(0)
+#define Assert(C,S) do { if (!(C)) jwxyz_abort ("%s",(S)); } while(0)
 
 # undef MAX
 # undef MIN
@@ -111,12 +113,38 @@ struct jwxyz_Font {
 };
 
 
+/* Instead of calling abort(), throw a real exception, so that
+   XScreenSaverView can catch it and display a dialog.
+ */
+void
+jwxyz_abort (const char *fmt, ...)
+{
+  char s[10240];
+  if (!fmt || !*fmt)
+    strcpy (s, "abort");
+  else
+    {
+      va_list args;
+      va_start (args, fmt);
+      vsprintf (s, fmt, args);
+      va_end (args);
+    }
+  [[NSException exceptionWithName: NSInternalInconsistencyException
+                reason: [NSString stringWithCString: s
+                                  encoding:NSUTF8StringEncoding]
+                userInfo: nil]
+    raise];
+  abort();  // not reached
+}
+
+
 Display *
 jwxyz_make_display (void *nsview_arg, void *cgc_arg)
 {
   CGContextRef cgc = (CGContextRef) cgc_arg;
   NSView *view = (NSView *) nsview_arg;
-  if (!view) abort();
+  Assert (view, "no view");
+  if (!view) return 0;
 
   Display *d = (Display *) calloc (1, sizeof(*d));
   d->screen = (Screen *) calloc (1, sizeof(Screen));
@@ -170,7 +198,7 @@ jwxyz_free_display (Display *dpy)
 void *
 jwxyz_window_view (Window w)
 {
-  Assert (w->type == WINDOW, "not a window");
+  Assert (w && w->type == WINDOW, "not a window");
   return w->window.view;
 }
 
@@ -197,7 +225,7 @@ jwxyz_window_resized (Display *dpy, Window w,
                       void *cgc_arg)
 {
   CGContextRef cgc = (CGContextRef) cgc_arg;
-  Assert (w->type == WINDOW, "not a window");
+  Assert (w && w->type == WINDOW, "not a window");
   w->frame.origin.x    = new_x;
   w->frame.origin.y    = new_y;
   w->frame.size.width  = new_width;
@@ -246,7 +274,7 @@ jwxyz_window_resized (Display *dpy, Window w,
 void
 jwxyz_mouse_moved (Display *dpy, Window w, int x, int y)
 {
-  Assert (w->type == WINDOW, "not a window");
+  Assert (w && w->type == WINDOW, "not a window");
   w->window.last_mouse_x = x;
   w->window.last_mouse_y = y;
 }
@@ -393,7 +421,7 @@ push_gc (Drawable d, GC gc)
     case GXxor:   CGContextSetBlendMode (cgc, kCGBlendModeDifference); break;
     case GXor:    CGContextSetBlendMode (cgc, kCGBlendModeLighten);    break;
     case GXand:   CGContextSetBlendMode (cgc, kCGBlendModeDarken);     break;
-    default: abort(); break;
+    default: Assert(0, "unknown gcv function"); break;
   }
 
   if (gc->gcv.clip_mask)
@@ -451,6 +479,12 @@ push_bg_gc (Drawable d, GC gc, Bool fill_p)
  */
 #define XDRAWPOINTS_IMAGES
 
+/* Update, 2012: Kurt Revis <krevis@snoize.com> points out that diddling
+   the bitmap data directly is faster.  This only works on Pixmaps, though,
+   not Windows.  (Fortunately, on iOS, the Window is really a Pixmap.)
+ */
+#define XDRAWPOINTS_CGDATA
+
 int
 XDrawPoints (Display *dpy, Drawable d, GC gc, 
              XPoint *points, int count, int mode)
@@ -460,68 +494,125 @@ XDrawPoints (Display *dpy, Drawable d, GC gc,
 
   push_fg_gc (d, gc, YES);
 
-# ifdef XDRAWPOINTS_IMAGES
+# ifdef XDRAWPOINTS_CGDATA
 
-  unsigned int argb = gc->gcv.foreground;
-  validate_pixel (argb, gc->depth, gc->gcv.alpha_allowed_p);
-  if (gc->depth == 1)
-    argb = (gc->gcv.foreground ? WhitePixel(0,0) : BlackPixel(0,0));
-
-  CGDataProviderRef prov = CGDataProviderCreateWithData (NULL, &argb, 4, NULL);
-  CGImageRef cgi = CGImageCreate (1, 1,
-                                  8, 32, 4,
-                                  dpy->colorspace, 
-                                  /* Host-ordered, since we're using the
-                                     address of an int as the color data. */
-                                  (kCGImageAlphaNoneSkipFirst | 
-                                   kCGBitmapByteOrder32Host),
-                                  prov, 
-                                  NULL,  /* decode[] */
-                                  NO, /* interpolate */
-                                  kCGRenderingIntentDefault);
-  CGDataProviderRelease (prov);
+#  ifdef USE_BACKBUFFER
+  if (1)  // Because of the backbuffer, all iPhone Windows work like Pixmaps.
+#  else
+  if (d->type == PIXMAP)
+#  endif
+  {
+    CGContextRef cgc = d->cgc;
+    void *data = CGBitmapContextGetData (cgc);
+    size_t bpr = CGBitmapContextGetBytesPerRow (cgc);
+    size_t w = CGBitmapContextGetWidth (cgc);
+    size_t h = CGBitmapContextGetHeight (cgc);
 
-  CGContextRef cgc = d->cgc;
-  CGRect rect;
-  rect.size.width = rect.size.height = 1;
-  for (i = 0; i < count; i++) {
-    if (i > 0 && mode == CoordModePrevious) {
-      rect.origin.x += points->x;
-      rect.origin.x -= points->y;
+    Assert (data, "no bitmap data in Drawable");
+
+    unsigned int argb = gc->gcv.foreground;
+    validate_pixel (argb, gc->depth, gc->gcv.alpha_allowed_p);
+    if (gc->depth == 1)
+      argb = (gc->gcv.foreground ? WhitePixel(0,0) : BlackPixel(0,0));
+
+    CGFloat x0 = wr.origin.x;
+    CGFloat y0 = wr.origin.y + wr.size.height;
+
+    // It's uglier, but faster, to hoist the conditional out of the loop.
+    if (mode == CoordModePrevious) {
+      CGFloat x = x0, y = y0;
+      for (i = 0; i < count; i++, points++) {
+        x += points->x;
+        y -= points->y;
+
+        if (0 <= x && x < w && 0 <= y && y < h) {
+          unsigned int *p = (unsigned int *)
+            ((char *) data + (size_t) y * bpr + (size_t) x * 4);
+          *p = argb;
+        }
+      }
     } else {
-      rect.origin.x = wr.origin.x + points->x;
-      rect.origin.y = wr.origin.y + wr.size.height - points->y - 1;
+      for (i = 0; i < count; i++, points++) {
+        CGFloat x = x0 + points->x;
+        CGFloat y = y0 - points->y;
+
+        if (0 <= x && x < w && 0 <= y && y < h) {
+          unsigned int *p = (unsigned int *)
+            ((char *) data + (size_t) y * bpr + (size_t) x * 4);
+          *p = argb;
+        }
+      }
     }
 
-    //Assert (CGImageGetColorSpace (cgi) == dpy->colorspace, "bad colorspace");
-    CGContextDrawImage (cgc, rect, cgi);
-    points++;
-  }
+  } else       /* d->type == WINDOW */
+
+# endif /* XDRAWPOINTS_CGDATA */
+  {
+
+# ifdef XDRAWPOINTS_IMAGES
 
-  CGImageRelease (cgi);
+    unsigned int argb = gc->gcv.foreground;
+    validate_pixel (argb, gc->depth, gc->gcv.alpha_allowed_p);
+    if (gc->depth == 1)
+      argb = (gc->gcv.foreground ? WhitePixel(0,0) : BlackPixel(0,0));
+
+    CGDataProviderRef prov = CGDataProviderCreateWithData (NULL, &argb, 4,
+                                                           NULL);
+    CGImageRef cgi = CGImageCreate (1, 1,
+                                    8, 32, 4,
+                                    dpy->colorspace, 
+                                    /* Host-ordered, since we're using the
+                                       address of an int as the color data. */
+                                    (kCGImageAlphaNoneSkipFirst | 
+                                     kCGBitmapByteOrder32Host),
+                                    prov, 
+                                    NULL,  /* decode[] */
+                                    NO, /* interpolate */
+                                    kCGRenderingIntentDefault);
+    CGDataProviderRelease (prov);
+
+    CGContextRef cgc = d->cgc;
+    CGRect rect;
+    rect.size.width = rect.size.height = 1;
+    for (i = 0; i < count; i++) {
+      if (i > 0 && mode == CoordModePrevious) {
+        rect.origin.x += points->x;
+        rect.origin.x -= points->y;
+      } else {
+        rect.origin.x = wr.origin.x + points->x;
+        rect.origin.y = wr.origin.y + wr.size.height - points->y - 1;
+      }
+
+      //Assert(CGImageGetColorSpace (cgi) == dpy->colorspace,"bad colorspace");
+      CGContextDrawImage (cgc, rect, cgi);
+      points++;
+    }
+
+    CGImageRelease (cgi);
 
 # else /* ! XDRAWPOINTS_IMAGES */
 
-  CGRect *rects = (CGRect *) malloc (count * sizeof(CGRect));
-  CGRect *r = rects;
+    CGRect *rects = (CGRect *) malloc (count * sizeof(CGRect));
+    CGRect *r = rects;
   
-  for (i = 0; i < count; i++) {
-    r->size.width = r->size.height = 1;
-    if (i > 0 && mode == CoordModePrevious) {
-      r->origin.x = r[-1].origin.x + points->x;
-      r->origin.y = r[-1].origin.x - points->y;
-    } else {
-      r->origin.x = wr.origin.x + points->x;
-      r->origin.y = wr.origin.y + wr.size.height - points->y;
+    for (i = 0; i < count; i++) {
+      r->size.width = r->size.height = 1;
+      if (i > 0 && mode == CoordModePrevious) {
+        r->origin.x = r[-1].origin.x + points->x;
+        r->origin.y = r[-1].origin.x - points->y;
+      } else {
+        r->origin.x = wr.origin.x + points->x;
+        r->origin.y = wr.origin.y + wr.size.height - points->y;
+      }
+      points++;
+      r++;
     }
-    points++;
-    r++;
-  }
 
-  CGContextFillRects (d->cgc, rects, count);
-  free (rects);
+    CGContextFillRects (d->cgc, rects, count);
+    free (rects);
 
 # endif /* ! XDRAWPOINTS_IMAGES */
+  }
 
   pop_gc (d, gc);
   invalidate_drawable_cache (d);
@@ -637,7 +728,7 @@ XCopyArea (Display *dpy, Drawable src, Drawable dst, GC gc,
   BOOL free_cgi_p = NO;
 
 
-#ifndef USE_IPHONE
+#ifndef USE_BACKBUFFER
   // Because of the backbuffer, all iPhone Windows work like Pixmaps.
   if (src->type == PIXMAP)
 # endif
@@ -677,7 +768,7 @@ XCopyArea (Display *dpy, Drawable src, Drawable dst, GC gc,
   if (src->type == PIXMAP && src->pixmap.depth == 1)
       mask_p = YES;
 
-# ifndef USE_IPHONE
+# ifndef USE_BACKBUFFER
   } else { /* (src->type == WINDOW) */
     
     NSRect nsfrom;    // NSRect != CGRect on 10.4
@@ -735,7 +826,7 @@ XCopyArea (Display *dpy, Drawable src, Drawable dst, GC gc,
       CGDataProviderRelease (prov);
     }
 
-# endif // !USE_IPHONE
+# endif // !USE_BACKBUFFER
   }
 
   CGContextRef cgc = dst->cgc;
@@ -781,9 +872,10 @@ XCopyArea (Display *dpy, Drawable src, Drawable dst, GC gc,
     } else {
       // No cgi means src == dst, and both are Windows.
 
-# ifdef USE_IPHONE
-      abort();  // No NSCopyBits on iOS, but shouldn't be reached anyway.
-# else // !USE_IPHONE
+# ifdef USE_BACKBUFFER
+      Assert (0, "NSCopyBits unimplemented"); // shouldn't be reached anyway
+      return 0;
+# else // !USE_BACKBUFFER
       NSRect nsfrom;
       nsfrom.origin.x    = src_rect.origin.x;    // NSRect != CGRect on 10.4
       nsfrom.origin.y    = src_rect.origin.y;
@@ -793,7 +885,7 @@ XCopyArea (Display *dpy, Drawable src, Drawable dst, GC gc,
       nsto.x             = dst_rect.origin.x;
       nsto.y             = dst_rect.origin.y;
       NSCopyBits (0, nsfrom, nsto);
-# endif // !USE_IPHONE
+# endif // !USE_BACKBUFFER
     }
 
     pop_gc (dst, gc);
@@ -929,7 +1021,7 @@ XDrawSegments (Display *dpy, Drawable d, GC gc, XSegment *segments, int count)
 int
 XClearWindow (Display *dpy, Window win)
 {
-  Assert (win->type == WINDOW, "not a window");
+  Assert (win && win->type == WINDOW, "not a window");
   CGRect wr = win->frame;
   return XClearArea (dpy, win, 0, 0, wr.size.width, wr.size.height, 0);
 }
@@ -937,7 +1029,7 @@ XClearWindow (Display *dpy, Window win)
 int
 XSetWindowBackground (Display *dpy, Window w, unsigned long pixel)
 {
-  Assert (w->type == WINDOW, "not a window");
+  Assert (w && w->type == WINDOW, "not a window");
   validate_pixel (pixel, 32, NO);
   w->window.background = pixel;
   return 0;
@@ -1018,7 +1110,7 @@ XFillRectangles (Display *dpy, Drawable d, GC gc, XRectangle *rects, int n)
 int
 XClearArea (Display *dpy, Window win, int x, int y, int w, int h, Bool exp)
 {
-  Assert (win->type == WINDOW, "not a window");
+  Assert (win && win->type == WINDOW, "not a window");
   CGContextRef cgc = win->cgc;
   set_color (cgc, win->window.background, 32, NO, YES);
   draw_rect (dpy, win, 0, x, y, w, h, NO, YES);
@@ -1174,8 +1266,8 @@ static void
 set_gcv (GC gc, XGCValues *from, unsigned long mask)
 {
   if (! mask) return;
-  if (! gc) abort();
-  if (! from) abort();
+  Assert (gc && from, "no gc");
+  if (!gc || !from) return;
 
   if (mask & GCFunction)       gc->gcv.function        = from->function;
   if (mask & GCForeground)     gc->gcv.foreground      = from->foreground;
@@ -1196,17 +1288,18 @@ set_gcv (GC gc, XGCValues *from, unsigned long mask)
   if (mask & GCBackground) validate_pixel (from->background, gc->depth,
                                            gc->gcv.alpha_allowed_p);
     
-  if (mask & GCLineStyle)      abort();
-  if (mask & GCPlaneMask)      abort();
-  if (mask & GCFillStyle)      abort();
-  if (mask & GCTile)           abort();
-  if (mask & GCStipple)                abort();
-  if (mask & GCTileStipXOrigin)        abort();
-  if (mask & GCTileStipYOrigin)        abort();
-  if (mask & GCGraphicsExposures) abort();
-  if (mask & GCDashOffset)     abort();
-  if (mask & GCDashList)       abort();
-  if (mask & GCArcMode)                abort();
+  Assert ((! (mask & (GCLineStyle |
+                      GCPlaneMask |
+                      GCFillStyle |
+                      GCTile |
+                      GCStipple |
+                      GCTileStipXOrigin |
+                      GCTileStipYOrigin |
+                      GCGraphicsExposures |
+                      GCDashOffset |
+                      GCDashList |
+                      GCArcMode))),
+          "unimplemented gcvalues mask");
 }
 
 
@@ -1253,7 +1346,7 @@ XFreeGC (Display *dpy, GC gc)
 Status
 XGetWindowAttributes (Display *dpy, Window w, XWindowAttributes *xgwa)
 {
-  Assert (w->type == WINDOW, "not a window");
+  Assert (w && w->type == WINDOW, "not a window");
   memset (xgwa, 0, sizeof(*xgwa));
   xgwa->x      = w->frame.origin.x;
   xgwa->y      = w->frame.origin.y;
@@ -1453,7 +1546,7 @@ XInitImage (XImage *ximage)
     ximage->f.put_pixel = ximage_putpixel_32;
     ximage->f.get_pixel = ximage_getpixel_32;
   } else {
-    abort();
+    Assert (0, "unknown depth");
   }
   return 1;
 }
@@ -1735,7 +1828,7 @@ XGetImage (Display *dpy, Drawable d, int x, int y,
 {
   const unsigned char *data = 0;
   int depth, ibpp, ibpl, alpha_first_p;
-# ifndef USE_IPHONE
+# ifndef USE_BACKBUFFER
   NSBitmapImageRep *bm = 0;
 # endif
   
@@ -1746,7 +1839,7 @@ XGetImage (Display *dpy, Drawable d, int x, int y,
 
   CGContextRef cgc = d->cgc;
 
-#ifndef USE_IPHONE
+#ifndef USE_BACKBUFFER
   // Because of the backbuffer, all iPhone Windows work like Pixmaps.
   if (d->type == PIXMAP)
 # endif
@@ -1762,7 +1855,7 @@ XGetImage (Display *dpy, Drawable d, int x, int y,
     data = CGBitmapContextGetData (cgc);
     Assert (data, "CGBitmapContextGetData failed");
 
-# ifndef USE_IPHONE
+# ifndef USE_BACKBUFFER
   } else { /* (d->type == WINDOW) */
 
     // get the bits (desired sub-rectangle) out of the NSView
@@ -1778,7 +1871,7 @@ XGetImage (Display *dpy, Drawable d, int x, int y,
     ibpl = [bm bytesPerRow];
     data = [bm bitmapData];
     Assert (data, "NSBitmapImageRep initWithFocusedViewRect failed");
-# endif // !USE_IPHONE
+# endif // !USE_BACKBUFFER
   }
   
   // data points at (x,y) with ibpl rowstride.  ignore x,y from now on.
@@ -1857,7 +1950,7 @@ XGetImage (Display *dpy, Drawable d, int x, int y,
     }
   }
 
-# ifndef USE_IPHONE
+# ifndef USE_BACKBUFFER
   if (bm) [bm release];
 # endif
 
@@ -2074,7 +2167,7 @@ XCreatePixmap (Display *dpy, Drawable d,
 int
 XFreePixmap (Display *d, Pixmap p)
 {
-  Assert (p->type == PIXMAP, "not a pixmap");
+  Assert (p && p->type == PIXMAP, "not a pixmap");
   invalidate_drawable_cache (p);
   CGContextRelease (p->cgc);
   if (p->pixmap.cgc_buffer)
@@ -2141,12 +2234,12 @@ static void
 query_font (Font fid)
 {
   if (!fid || !fid->nsfont) {
-    NSLog(@"no NSFont in fid");
-    abort();
+    Assert (0, "no NSFont in fid");
+    return;
   }
   if (![fid->nsfont fontName]) {
-    NSLog(@"broken NSFont in fid");
-    abort();
+    Assert(0, @"broken NSFont in fid");
+    return;
   }
 
   int first = 32;
@@ -2176,7 +2269,13 @@ query_font (Font fid)
 
 # ifndef USE_IPHONE
   NSBezierPath *bpath = [NSBezierPath bezierPath];
-# endif
+# else  // USE_IPHONE
+  CTFontRef ctfont =
+    CTFontCreateWithName ((CFStringRef) [fid->nsfont fontName],
+                          [fid->nsfont pointSize],
+                          NULL);
+  Assert (ctfont, @"no CTFontRef for UIFont");
+# endif // USE_IPHONE
 
   for (i = first; i <= last; i++) {
     unsigned char str[2];
@@ -2185,8 +2284,8 @@ query_font (Font fid)
 
     NSString *nsstr = [NSString stringWithCString:(char *) str
                                          encoding:NSISOLatin1StringEncoding];
-    NSPoint advancement;
-    NSRect bbox;
+    NSPoint advancement = { 0, };
+    NSRect bbox = {{ 0, }, };
 
 # ifndef USE_IPHONE
 
@@ -2235,33 +2334,35 @@ query_font (Font fid)
 
 # else  // USE_IPHONE
 
-    UIFont *ff = fid->nsfont;
-    CGSize size = [nsstr sizeWithFont:ff];
-
-    /* sizeWithFont gives us a character's "width" and "height".
-       There is no way to get a character's "lbearing", "rbearing",
-       or "descent".  We do have the font's overall "descent", though.
+    /* There is no way to get "lbearing", "rbearing" or "descent" out of
+       NSFont.  'sizeWithFont' gives us "width" and "height" only.
+       Likewise, 'drawAtPoint' (to an offscreen CGContext) gives us the
+       width of the character and the ascent of the font.
 
-       drawAtPoint (to an offscreen CGContext) returns "width" and the
-       "ascent" of the font (not of the glyph, I think) so that doesn't
-       help.
-
-       CGFontGetGlyphBBoxes might help (if it actually returns a bounding
-       box and not just the ascent/width again, which I don't know) but
-       we can't use it anyway because there is no way to map a unichar to
-       a CGGlyph.
-
-       Fuck you sideways, Apple.
+       Maybe we could use CGFontGetGlyphBBoxes() and avoid linking in
+       the CoreText library, but there's no non-CoreText way to turn a
+       unichar into a CGGlyph.
      */
-
-    bbox.origin.x = 0;
-    bbox.origin.y = [ff descender];
-    bbox.size.width  = size.width;
-    bbox.size.height = size.height;
-
-    advancement.x = size.width;
-    advancement.y = 0;
-
+    UniChar uchar = [nsstr characterAtIndex: 0];
+    CGGlyph cgglyph = 0;
+
+    if (CTFontGetGlyphsForCharacters (ctfont, &uchar, &cgglyph, 1))
+      {
+        bbox = CTFontGetBoundingRectsForGlyphs (ctfont,
+                                                kCTFontDefaultOrientation,
+                                                &cgglyph, NULL, 1);
+        CGSize adv = { 0, };
+        CTFontGetAdvancesForGlyphs (ctfont, kCTFontDefaultOrientation,
+                                    &cgglyph, &adv, 1);
+        advancement.x = adv.width;
+        advancement.y = adv.height;
+
+        // Seems to be clipping by a pixel or two.  Add a margin to be safe.
+        bbox.origin.x    -= 2;
+        bbox.origin.y    -= 2;
+        bbox.size.width  += 4;
+        bbox.size.height += 4;
+      }
 # endif // USE_IPHONE
 
     /* Now that we know the advancement and bounding box, we can compute
@@ -2304,6 +2405,10 @@ query_font (Font fid)
             (int) advancement.x, (int) advancement.y);
 #endif
   }
+
+# ifdef USE_IPHONE
+  CFRelease (ctfont);
+# endif
 }
 
 
@@ -2651,7 +2756,8 @@ XLoadFont (Display *dpy, const char *name)
 
   // We should never return NULL for XLFD fonts.
   if (!fid->nsfont) {
-    abort();
+    Assert (0, "no font");
+    return 0;
   }
   CFRetain (fid->nsfont);   // needed for garbage collection?
 
@@ -2970,7 +3076,7 @@ XQueryPointer (Display *dpy, Window w, Window *root_ret, Window *child_ret,
                int *root_x_ret, int *root_y_ret, 
                int *win_x_ret, int *win_y_ret, unsigned int *mask_ret)
 {
-  Assert (w->type == WINDOW, "not a window");
+  Assert (w && w->type == WINDOW, "not a window");
 
 # ifdef USE_IPHONE
   int x = w->window.last_mouse_x;
@@ -3042,7 +3148,7 @@ XTranslateCoordinates (Display *dpy, Window w, Window dest_w,
                        int *dest_x_ret, int *dest_y_ret,
                        Window *child_ret)
 {
-  Assert (w->type == WINDOW, "not a window");
+  Assert (w && w->type == WINDOW, "not a window");
 
 # ifdef USE_IPHONE