/* xscreensaver, Copyright (c) 1991-2013 Jamie Zawinski * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that * the above copyright notice appear in all copies and that both that * copyright notice and this permission notice appear in supporting * documentation. No representations are made about the suitability of this * software for any purpose. It is provided "as is" without express or * implied warranty. */ /* JWXYZ Is Not Xlib. But it's a bunch of function definitions that bear some resemblance to Xlib and that do Cocoa-ish things that bear some resemblance to the things that Xlib might have done. */ #import #import #import #ifdef USE_IPHONE # import # import # import # import # define NSView UIView # define NSRect CGRect # define NSPoint CGPoint # define NSSize CGSize # define NSColor UIColor # define NSImage UIImage # define NSEvent UIEvent # define NSFont UIFont # define NSGlyph CGGlyph # define NSWindow UIWindow # define NSMakeSize CGSizeMake # define NSBezierPath UIBezierPath #else # import #endif #import "jwxyz.h" #import "jwxyz-timers.h" #import "yarandom.h" # define USE_BACKBUFFER /* must be in sync with XScreenSaverView.h */ #undef Assert #define Assert(C,S) do { if (!(C)) jwxyz_abort ("%s",(S)); } while(0) # undef MAX # undef MIN # define MAX(a,b) ((a)>(b)?(a):(b)) # define MIN(a,b) ((a)<(b)?(a):(b)) struct jwxyz_Drawable { enum { WINDOW, PIXMAP } type; CGContextRef cgc; CGImageRef cgi; CGRect frame; union { struct { NSView *view; unsigned long background; int last_mouse_x, last_mouse_y; } window; struct { int depth; void *cgc_buffer; // the bits to which CGContextRef renders } pixmap; }; }; struct jwxyz_Display { Window main_window; Screen *screen; struct jwxyz_sources_data *timers_data; # ifndef USE_IPHONE CGDirectDisplayID cgdpy; /* ...of the one and only Window, main_window. This can change if the window is dragged to a different screen. */ # endif CGColorSpaceRef colorspace; /* Color space of this screen. We tag all of our images with this to avoid translation when rendering. */ }; struct jwxyz_Screen { Display *dpy; Visual *visual; }; struct jwxyz_GC { XGCValues gcv; unsigned int depth; CGImageRef clip_mask; // CGImage copy of the Pixmap in gcv.clip_mask }; struct jwxyz_Font { char *ps_name; NSFont *nsfont; float size; // points // In X11, "Font" is just an ID, and "XFontStruct" contains the metrics. // But we need the metrics on both of them, so they go here. XFontStruct metrics; }; /* 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; Assert (view, "no view"); if (!view) return 0; Display *d = (Display *) calloc (1, sizeof(*d)); d->screen = (Screen *) calloc (1, sizeof(Screen)); d->screen->dpy = d; Visual *v = (Visual *) calloc (1, sizeof(Visual)); v->class = TrueColor; v->red_mask = 0x00FF0000; v->green_mask = 0x0000FF00; v->blue_mask = 0x000000FF; v->bits_per_rgb = 8; d->screen->visual = v; d->timers_data = jwxyz_sources_init (XtDisplayToApplicationContext (d)); Window w = (Window) calloc (1, sizeof(*w)); w->type = WINDOW; w->window.view = view; CFRetain (w->window.view); // needed for garbage collection? w->window.background = BlackPixel(0,0); d->main_window = w; # ifndef USE_IPHONE if (! cgc) { [view lockFocus]; cgc = [[[view window] graphicsContext] graphicsPort]; [view unlockFocus]; w->cgc = cgc; } # endif Assert (cgc, "no CGContext"); return d; } void jwxyz_free_display (Display *dpy) { jwxyz_XtRemoveInput_all (dpy); // #### jwxyz_XtRemoveTimeOut_all (); free (dpy->screen->visual); free (dpy->screen); free (dpy->main_window); free (dpy); } void * jwxyz_window_view (Window w) { Assert (w && w->type == WINDOW, "not a window"); return w->window.view; } /* Call this after any modification to the bits on a Pixmap or Window. Most Pixmaps are used frequently as sources and infrequently as destinations, so it pays to cache the data as a CGImage as needed. */ static void invalidate_drawable_cache (Drawable d) { if (d && d->cgi) { CGImageRelease (d->cgi); d->cgi = 0; } } /* Call this when the View changes size or position. */ void jwxyz_window_resized (Display *dpy, Window w, int new_x, int new_y, int new_width, int new_height, void *cgc_arg) { CGContextRef cgc = (CGContextRef) cgc_arg; 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; w->frame.size.height = new_height; if (cgc) w->cgc = cgc; Assert (w->cgc, "no CGContext"); # ifndef USE_IPHONE // Figure out which screen the window is currently on. { int wx, wy; XTranslateCoordinates (dpy, w, NULL, 0, 0, &wx, &wy, NULL); CGPoint p; p.x = wx; p.y = wy; CGDisplayCount n; dpy->cgdpy = 0; CGGetDisplaysWithPoint (p, 1, &dpy->cgdpy, &n); // Auuugh! if (! dpy->cgdpy) { p.x = p.y = 0; CGGetDisplaysWithPoint (p, 1, &dpy->cgdpy, &n); } Assert (dpy->cgdpy, "unable to find CGDisplay"); } # endif // USE_IPHONE # ifndef USE_BACKBUFFER // Funny thing: As of OS X 10.9, if USE_BACKBUFFER is turned off, // then this one's faster. { // Figure out this screen's colorspace, and use that for every CGImage. // CMProfileRef profile = 0; CMGetProfileByAVID ((CMDisplayIDType) dpy->cgdpy, &profile); Assert (profile, "unable to find colorspace profile"); dpy->colorspace = CGColorSpaceCreateWithPlatformColorSpace (profile); Assert (dpy->colorspace, "unable to find colorspace"); } # else // USE_BACKBUFFER // WTF? It's faster if we *do not* use the screen's colorspace! // dpy->colorspace = CGColorSpaceCreateDeviceRGB(); # endif // USE_BACKBUFFER invalidate_drawable_cache (w); } #ifdef USE_IPHONE void jwxyz_mouse_moved (Display *dpy, Window w, int x, int y) { Assert (w && w->type == WINDOW, "not a window"); w->window.last_mouse_x = x; w->window.last_mouse_y = y; } #endif // USE_IPHONE void jwxyz_flush_context (Display *dpy) { // This is only used when USE_BACKBUFFER is off. CGContextFlush(dpy->main_window->cgc); // CGContextSynchronize is another possibility. } jwxyz_sources_data * display_sources_data (Display *dpy) { return dpy->timers_data; } Window XRootWindow (Display *dpy, int screen) { return dpy->main_window; } Screen * XDefaultScreenOfDisplay (Display *dpy) { return dpy->screen; } Visual * XDefaultVisualOfScreen (Screen *screen) { return screen->visual; } Display * XDisplayOfScreen (Screen *s) { return s->dpy; } int XDisplayNumberOfScreen (Screen *s) { return 0; } int XScreenNumberOfScreen (Screen *s) { return 0; } int XDisplayWidth (Display *dpy, int screen) { return (int) dpy->main_window->frame.size.width; } int XDisplayHeight (Display *dpy, int screen) { return (int) dpy->main_window->frame.size.height; } static void validate_pixel (unsigned long pixel, unsigned int depth, BOOL alpha_allowed_p) { if (depth == 1) Assert ((pixel == 0 || pixel == 1), "bogus mono pixel"); else if (!alpha_allowed_p) Assert (((pixel & BlackPixel(0,0)) == BlackPixel(0,0)), "bogus color pixel"); } static void set_color (CGContextRef cgc, unsigned long argb, unsigned int depth, BOOL alpha_allowed_p, BOOL fill_p) { validate_pixel (argb, depth, alpha_allowed_p); if (depth == 1) { if (fill_p) CGContextSetGrayFillColor (cgc, (argb ? 1.0 : 0.0), 1.0); else CGContextSetGrayStrokeColor (cgc, (argb ? 1.0 : 0.0), 1.0); } else { float a = ((argb >> 24) & 0xFF) / 255.0; float r = ((argb >> 16) & 0xFF) / 255.0; float g = ((argb >> 8) & 0xFF) / 255.0; float b = ((argb ) & 0xFF) / 255.0; if (fill_p) CGContextSetRGBFillColor (cgc, r, g, b, a); else CGContextSetRGBStrokeColor (cgc, r, g, b, a); } } static void set_line_mode (CGContextRef cgc, XGCValues *gcv) { CGContextSetLineWidth (cgc, gcv->line_width ? gcv->line_width : 1); CGContextSetLineJoin (cgc, gcv->join_style == JoinMiter ? kCGLineJoinMiter : gcv->join_style == JoinRound ? kCGLineJoinRound : kCGLineJoinBevel); CGContextSetLineCap (cgc, gcv->cap_style == CapNotLast ? kCGLineCapButt : gcv->cap_style == CapButt ? kCGLineCapButt : gcv->cap_style == CapRound ? kCGLineCapRound : kCGLineCapSquare); } static void set_clip_mask (Drawable d, GC gc) { Assert (!!gc->gcv.clip_mask == !!gc->clip_mask, "GC clip mask mixup"); Pixmap p = gc->gcv.clip_mask; if (!p) return; Assert (p->type == PIXMAP, "not a pixmap"); CGRect wr = d->frame; CGRect to; to.origin.x = wr.origin.x + gc->gcv.clip_x_origin; to.origin.y = wr.origin.y + wr.size.height - gc->gcv.clip_y_origin - p->frame.size.height; to.size.width = p->frame.size.width; to.size.height = p->frame.size.height; CGContextClipToMask (d->cgc, to, gc->clip_mask); } /* Pushes a GC context; sets BlendMode and ClipMask. */ static void push_gc (Drawable d, GC gc) { CGContextRef cgc = d->cgc; CGContextSaveGState (cgc); switch (gc->gcv.function) { case GXset: case GXclear: case GXcopy:/*CGContextSetBlendMode (cgc, kCGBlendModeNormal);*/ break; case GXxor: CGContextSetBlendMode (cgc, kCGBlendModeDifference); break; case GXor: CGContextSetBlendMode (cgc, kCGBlendModeLighten); break; case GXand: CGContextSetBlendMode (cgc, kCGBlendModeDarken); break; default: Assert(0, "unknown gcv function"); break; } if (gc->gcv.clip_mask) set_clip_mask (d, gc); } #define pop_gc(d,gc) CGContextRestoreGState (d->cgc) /* Pushes a GC context; sets BlendMode, ClipMask, Fill, and Stroke colors. */ static void push_color_gc (Drawable d, GC gc, unsigned long color, BOOL antialias_p, Bool fill_p) { push_gc (d, gc); int depth = gc->depth; switch (gc->gcv.function) { case GXset: color = (depth == 1 ? 1 : WhitePixel(0,0)); break; case GXclear: color = (depth == 1 ? 0 : BlackPixel(0,0)); break; } CGContextRef cgc = d->cgc; set_color (cgc, color, depth, gc->gcv.alpha_allowed_p, fill_p); CGContextSetShouldAntialias (cgc, antialias_p); } /* Pushes a GC context; sets Fill and Stroke colors to the foreground color. */ static void push_fg_gc (Drawable d, GC gc, Bool fill_p) { push_color_gc (d, gc, gc->gcv.foreground, gc->gcv.antialias_p, fill_p); } /* Pushes a GC context; sets Fill and Stroke colors to the background color. */ static void push_bg_gc (Drawable d, GC gc, Bool fill_p) { push_color_gc (d, gc, gc->gcv.background, gc->gcv.antialias_p, fill_p); } /* You've got to be fucking kidding me! It is *way* faster to draw points by creating and drawing a 1x1 CGImage with repeated calls to CGContextDrawImage than it is to make a single call to CGContextFillRects() with a list of 1x1 rectangles! I still wouldn't call it *fast*, however... */ #define XDRAWPOINTS_IMAGES /* Update, 2012: Kurt Revis 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) { int i; CGRect wr = d->frame; push_fg_gc (d, gc, YES); # ifdef XDRAWPOINTS_CGDATA # 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); 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; // Y axis is refreshingly not flipped. // 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 (x >= 0 && x < w && y >= 0 && y < h) { unsigned int *p = (unsigned int *) ((char *) data + (size_t) y * bpr + (size_t) x * 4); *p = argb; } } } else { for (i = 0; i < count; i++, points++) { CGFloat x = x0 + points->x; CGFloat y = y0 + points->y; if (x >= 0 && x < w && y >= 0 && y < h) { unsigned int *p = (unsigned int *) ((char *) data + (size_t) y * bpr + (size_t) x * 4); *p = argb; } } } } else /* d->type == WINDOW */ # endif /* XDRAWPOINTS_CGDATA */ { # ifdef XDRAWPOINTS_IMAGES 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; 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++; } CGContextFillRects (d->cgc, rects, count); free (rects); # endif /* ! XDRAWPOINTS_IMAGES */ } pop_gc (d, gc); invalidate_drawable_cache (d); return 0; } int XDrawPoint (Display *dpy, Drawable d, GC gc, int x, int y) { XPoint p; p.x = x; p.y = y; return XDrawPoints (dpy, d, gc, &p, 1, CoordModeOrigin); } static void draw_rect (Display *, Drawable, GC, int x, int y, unsigned int width, unsigned int height, BOOL foreground_p, BOOL fill_p); static Bool bitmap_context_p (Drawable d) { # ifdef USE_BACKBUFFER return True; # else // Because of the backbuffer, all iPhone Windows work like Pixmaps. return d->type == PIXMAP; # endif } static void fill_rect_memset (void *dst, size_t dst_pitch, uint32_t fill_data, size_t fill_width, size_t fill_height) { Assert(sizeof(wchar_t) == 4, "somebody changed the ABI"); while (fill_height) { // Would be nice if Apple used SSE/NEON in wmemset. Maybe someday. wmemset (dst, fill_data, fill_width); --fill_height; dst = (char *) dst + dst_pitch; } } static void * seek_xy (void *dst, size_t dst_pitch, unsigned x, unsigned y) { return (char *)dst + dst_pitch * y + x * 4; } static unsigned int drawable_depth (Drawable d) { return (d->type == WINDOW ? visual_depth (NULL, NULL) : d->pixmap.depth); } int XCopyArea (Display *dpy, Drawable src, Drawable dst, GC gc, int src_x, int src_y, unsigned int width, unsigned int height, int dst_x, int dst_y) { Assert (gc, "no GC"); Assert ((width < 65535), "improbably large width"); Assert ((height < 65535), "improbably large height"); Assert ((src_x < 65535 && src_x > -65535), "improbably large src_x"); Assert ((src_y < 65535 && src_y > -65535), "improbably large src_y"); Assert ((dst_x < 65535 && dst_x > -65535), "improbably large dst_x"); Assert ((dst_y < 65535 && dst_y > -65535), "improbably large dst_y"); if (width == 0 || height == 0) return 0; if (gc->gcv.function == GXset || gc->gcv.function == GXclear) { // "set" and "clear" are dumb drawing modes that ignore the source // bits and just draw solid rectangles. set_color (dst->cgc, (gc->gcv.function == GXset ? (gc->depth == 1 ? 1 : WhitePixel(0,0)) : (gc->depth == 1 ? 0 : BlackPixel(0,0))), gc->depth, gc->gcv.alpha_allowed_p, YES); draw_rect (dpy, dst, 0, dst_x, dst_y, width, height, YES, YES); return 0; } CGRect src_frame, dst_frame; // Sizes and origins of the two drawables CGRect src_rect, dst_rect; // The two rects to draw, clipped to the // bounds of their drawables. BOOL clipped = NO; // Whether we did any clipping of the rects. src_frame = src->frame; dst_frame = dst->frame; // Initialize src_rect... // src_rect.origin.x = src_frame.origin.x + src_x; src_rect.origin.y = src_frame.origin.y + src_frame.size.height - height - src_y; if (src_rect.origin.y < -65535) Assert(0, "src.origin.y went nuts"); src_rect.size.width = width; src_rect.size.height = height; // Initialize dst_rect... // dst_rect.origin.x = dst_frame.origin.x + dst_x; dst_rect.origin.y = dst_frame.origin.y + dst_frame.size.height - height - dst_y; if (dst_rect.origin.y < -65535) Assert(0, "dst.origin.y went nuts"); dst_rect.size.width = width; dst_rect.size.height = height; // Clip rects to frames... // # define CLIP(THIS,THAT,VAL,SIZE) do { \ float off = THIS##_rect.origin.VAL; \ if (off < 0) { \ clipped = YES; \ THIS##_rect.size.SIZE += off; \ THAT##_rect.size.SIZE += off; \ THIS##_rect.origin.VAL -= off; \ THAT##_rect.origin.VAL -= off; \ } \ off = (( THIS##_rect.origin.VAL + THIS##_rect.size.SIZE) - \ (THIS##_frame.origin.VAL + THIS##_frame.size.SIZE)); \ if (off > 0) { \ clipped = YES; \ THIS##_rect.size.SIZE -= off; \ THAT##_rect.size.SIZE -= off; \ }} while(0) CLIP (dst, src, x, width); CLIP (dst, src, y, height); // Not actually the original dst_rect, just the one before it's clipped to // the src_frame. CGRect orig_dst_rect = dst_rect; CLIP (src, dst, x, width); CLIP (src, dst, y, height); # undef CLIP if (orig_dst_rect.size.width <= 0 || orig_dst_rect.size.height <= 0) return 0; // Sort-of-special case where no pixels can be grabbed from the source, // and the whole destination is filled with the background color. if (src_rect.size.width < 0 || src_rect.size.height < 0) { src_rect.size.width = 0; src_rect.size.height = 0; } NSObject *releaseme = 0; CGImageRef cgi; BOOL mask_p = NO; BOOL free_cgi_p = NO; /* If we're copying from a bitmap to a bitmap, and there's nothing funny going on with clipping masks or depths or anything, optimize it by just doing a memcpy instead of going through a CGI. */ if (bitmap_context_p (src)) { if (bitmap_context_p (dst) && gc->gcv.function == GXcopy && !gc->gcv.clip_mask && drawable_depth (src) == drawable_depth (dst)) { Assert(!(int)src_frame.origin.x && !(int)src_frame.origin.y && !(int)dst_frame.origin.x && !(int)dst_frame.origin.y, "unexpected non-zero origin"); char *src_data = CGBitmapContextGetData(src->cgc); char *dst_data = CGBitmapContextGetData(dst->cgc); size_t src_pitch = CGBitmapContextGetBytesPerRow(src->cgc); size_t dst_pitch = CGBitmapContextGetBytesPerRow(dst->cgc); // Int to float and back again. It's not very safe, but it seems to work. int src_x0 = src_rect.origin.x; int dst_x0 = dst_rect.origin.x; // Flip the Y-axis a second time. int src_y0 = (src_frame.origin.y + src_frame.size.height - src_rect.size.height - src_rect.origin.y); int dst_y0 = (dst_frame.origin.y + dst_frame.size.height - dst_rect.size.height - dst_rect.origin.y); unsigned width0 = (int) src_rect.size.width; unsigned height0 = (int) src_rect.size.height; Assert((int)src_rect.size.width == (int)dst_rect.size.width || (int)src_rect.size.height == (int)dst_rect.size.height, "size mismatch"); { char *src_data0 = seek_xy(src_data, src_pitch, src_x0, src_y0); char *dst_data0 = seek_xy(dst_data, dst_pitch, dst_x0, dst_y0); size_t src_pitch0 = src_pitch; size_t dst_pitch0 = dst_pitch; size_t bytes = width0 * 4; if (src == dst && dst_y0 > src_y0) { // Copy upwards if the areas might overlap. src_data0 += src_pitch0 * (height0 - 1); dst_data0 += dst_pitch0 * (height0 - 1); src_pitch0 = -src_pitch0; dst_pitch0 = -dst_pitch0; } size_t lines0 = height0; while (lines0) { // memcpy is an alias for memmove on OS X. memmove(dst_data0, src_data0, bytes); src_data0 += src_pitch0; dst_data0 += dst_pitch0; --lines0; } } if (clipped) { int orig_dst_x = orig_dst_rect.origin.x; int orig_dst_y = (dst_frame.origin.y + dst_frame.size.height - orig_dst_rect.origin.y - orig_dst_rect.size.height); int orig_width = orig_dst_rect.size.width; int orig_height = orig_dst_rect.size.height; Assert (orig_dst_x >= 0 && orig_dst_x + orig_width <= (int) dst_frame.size.width && orig_dst_y >= 0 && orig_dst_y + orig_height <= (int) dst_frame.size.height, "wrong dimensions"); if (orig_dst_y < dst_y0) { fill_rect_memset (seek_xy (dst_data, dst_pitch, orig_dst_x, orig_dst_y), dst_pitch, gc->gcv.background, orig_width, dst_y0 - orig_dst_y); } if (orig_dst_y + orig_height > dst_y0 + height0) { fill_rect_memset (seek_xy (dst_data, dst_pitch, orig_dst_x, dst_y0 + height0), dst_pitch, gc->gcv.background, orig_width, orig_dst_y + orig_height - dst_y0 - height0); } if (orig_dst_x < dst_x0) { fill_rect_memset (seek_xy (dst_data, dst_pitch, orig_dst_x, dst_y0), dst_pitch, gc->gcv.background, dst_x0 - orig_dst_x, height0); } if (dst_x0 + width0 < orig_dst_x + orig_width) { fill_rect_memset (seek_xy (dst_data, dst_pitch, dst_x0 + width0, dst_y0), dst_pitch, gc->gcv.background, orig_dst_x + orig_width - dst_x0 - width0, height0); } } invalidate_drawable_cache (dst); return 0; } // If we are copying from a Pixmap to a Pixmap or Window, we must first // copy the bits to an intermediary CGImage object, then copy that to the // destination drawable's CGContext. // // (It doesn't seem to be possible to use NSCopyBits() to optimize the // case of copying from a Pixmap back to itself, but I don't think that // happens very often anyway.) // // First we get a CGImage out of the pixmap CGContext -- it's the whole // pixmap, but it presumably shares the data pointer instead of copying // it. We then cache that CGImage it inside the Pixmap object. Note: // invalidate_drawable_cache() must be called to discard this any time a // modification is made to the pixmap, or we'll end up re-using old bits. // if (!src->cgi) src->cgi = CGBitmapContextCreateImage (src->cgc); cgi = src->cgi; // if doing a sub-rect, trim it down. if (src_rect.origin.x != src_frame.origin.x || src_rect.origin.y != src_frame.origin.y || src_rect.size.width != src_frame.size.width || src_rect.size.height != src_frame.size.height) { // #### I don't understand why this is needed... src_rect.origin.y = (src_frame.size.height - src_rect.size.height - src_rect.origin.y); // This does not copy image data, so it should be fast. cgi = CGImageCreateWithImageInRect (cgi, src_rect); free_cgi_p = YES; } if (src->type == PIXMAP && src->pixmap.depth == 1) mask_p = YES; # ifndef USE_BACKBUFFER } else { /* (src->type == WINDOW) */ NSRect nsfrom; // NSRect != CGRect on 10.4 nsfrom.origin.x = src_rect.origin.x; nsfrom.origin.y = src_rect.origin.y; nsfrom.size.width = src_rect.size.width; nsfrom.size.height = src_rect.size.height; if (src == dst) { // If we are copying from a window to itself, we can use NSCopyBits() // without first copying the rectangle to an intermediary CGImage. // This is ~28% faster (but I *expected* it to be twice as fast...) // (kumppa, bsod, decayscreen, memscroller, slidescreen, slip, xjack) // cgi = 0; } else { // If we are copying from a Window to a Pixmap, we must first copy // the bits to an intermediary CGImage object, then copy that to the // Pixmap's CGContext. // NSBitmapImageRep *bm = [[NSBitmapImageRep alloc] initWithFocusedViewRect:nsfrom]; unsigned char *data = [bm bitmapData]; int bps = [bm bitsPerSample]; int bpp = [bm bitsPerPixel]; int bpl = [bm bytesPerRow]; releaseme = bm; // create a CGImage from those bits. // (As of 10.5, we could just do cgi = [bm CGImage] (it is autoreleased) // but that method didn't exist in 10.4.) CGDataProviderRef prov = CGDataProviderCreateWithData (NULL, data, bpl * nsfrom.size.height, NULL); cgi = CGImageCreate (src_rect.size.width, src_rect.size.height, bps, bpp, bpl, dpy->colorspace, /* Use whatever default bit ordering we got from initWithFocusedViewRect. I would have assumed that it was (kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host), but on Intel, it's not! */ 0, prov, NULL, /* decode[] */ NO, /* interpolate */ kCGRenderingIntentDefault); free_cgi_p = YES; //Assert(CGImageGetColorSpace(cgi) == dpy->colorspace,"bad colorspace"); CGDataProviderRelease (prov); } # endif // !USE_BACKBUFFER } CGContextRef cgc = dst->cgc; if (mask_p) { // src depth == 1 push_bg_gc (dst, gc, YES); // fill the destination rectangle with solid background... CGContextFillRect (cgc, orig_dst_rect); Assert (cgc, "no CGC with 1-bit XCopyArea"); // then fill in a solid rectangle of the fg color, using the image as an // alpha mask. (the image has only values of BlackPixel or WhitePixel.) set_color (cgc, gc->gcv.foreground, gc->depth, gc->gcv.alpha_allowed_p, YES); CGContextClipToMask (cgc, dst_rect, cgi); CGContextFillRect (cgc, dst_rect); pop_gc (dst, gc); } else { // src depth > 1 push_gc (dst, gc); // If either the src or dst rects did not lie within their drawables, // then we have adjusted both the src and dst rects to account for // the clipping; that means we need to first clear to the background, // so that clipped bits end up in the bg color instead of simply not // being copied. // if (clipped) { set_color (cgc, gc->gcv.background, gc->depth, gc->gcv.alpha_allowed_p, YES); CGContextFillRect (cgc, orig_dst_rect); } if (cgi) { // copy the CGImage onto the destination CGContext //Assert(CGImageGetColorSpace(cgi) == dpy->colorspace, "bad colorspace"); CGContextDrawImage (cgc, dst_rect, cgi); } else { // No cgi means src == dst, and both are Windows. # 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; nsfrom.size.width = src_rect.size.width; nsfrom.size.height = src_rect.size.height; NSPoint nsto; nsto.x = dst_rect.origin.x; nsto.y = dst_rect.origin.y; NSCopyBits (0, nsfrom, nsto); # endif // !USE_BACKBUFFER } pop_gc (dst, gc); } if (free_cgi_p) CGImageRelease (cgi); if (releaseme) [releaseme release]; invalidate_drawable_cache (dst); return 0; } int XCopyPlane (Display *dpy, Drawable src, Drawable dest, GC gc, int src_x, int src_y, unsigned width, int height, int dest_x, int dest_y, unsigned long plane) { Assert ((gc->depth == 1 || plane == 1), "hairy plane mask!"); // This isn't right: XCopyPlane() is supposed to map 1/0 to fg/bg, // not to white/black. return XCopyArea (dpy, src, dest, gc, src_x, src_y, width, height, dest_x, dest_y); } int XDrawLine (Display *dpy, Drawable d, GC gc, int x1, int y1, int x2, int y2) { // when drawing a zero-length line, obey line-width and cap-style. if (x1 == x2 && y1 == y2) { int w = gc->gcv.line_width; x1 -= w/2; y1 -= w/2; if (gc->gcv.line_width > 1 && gc->gcv.cap_style == CapRound) return XFillArc (dpy, d, gc, x1, y1, w, w, 0, 360*64); else return XFillRectangle (dpy, d, gc, x1, y1, w, w); } CGRect wr = d->frame; NSPoint p; p.x = wr.origin.x + x1; p.y = wr.origin.y + wr.size.height - y1; push_fg_gc (d, gc, NO); CGContextRef cgc = d->cgc; set_line_mode (cgc, &gc->gcv); CGContextBeginPath (cgc); CGContextMoveToPoint (cgc, p.x, p.y); p.x = wr.origin.x + x2; p.y = wr.origin.y + wr.size.height - y2; CGContextAddLineToPoint (cgc, p.x, p.y); CGContextStrokePath (cgc); pop_gc (d, gc); invalidate_drawable_cache (d); return 0; } int XDrawLines (Display *dpy, Drawable d, GC gc, XPoint *points, int count, int mode) { int i; NSPoint p; CGRect wr = d->frame; push_fg_gc (d, gc, NO); CGContextRef cgc = d->cgc; set_line_mode (cgc, &gc->gcv); // if the first and last points coincide, use closepath to get // the proper line-joining. BOOL closed_p = (points[0].x == points[count-1].x && points[0].y == points[count-1].y); if (closed_p) count--; p.x = wr.origin.x + points->x; p.y = wr.origin.y + wr.size.height - points->y; points++; CGContextBeginPath (cgc); CGContextMoveToPoint (cgc, p.x, p.y); for (i = 1; i < count; i++) { if (mode == CoordModePrevious) { p.x += points->x; p.y -= points->y; } else { p.x = wr.origin.x + points->x; p.y = wr.origin.y + wr.size.height - points->y; } CGContextAddLineToPoint (cgc, p.x, p.y); points++; } if (closed_p) CGContextClosePath (cgc); CGContextStrokePath (cgc); pop_gc (d, gc); invalidate_drawable_cache (d); return 0; } int XDrawSegments (Display *dpy, Drawable d, GC gc, XSegment *segments, int count) { int i; CGRect wr = d->frame; CGContextRef cgc = d->cgc; push_fg_gc (d, gc, NO); set_line_mode (cgc, &gc->gcv); CGContextBeginPath (cgc); for (i = 0; i < count; i++) { CGContextMoveToPoint (cgc, wr.origin.x + segments->x1, wr.origin.y + wr.size.height - segments->y1); CGContextAddLineToPoint (cgc, wr.origin.x + segments->x2, wr.origin.y + wr.size.height - segments->y2); segments++; } CGContextStrokePath (cgc); pop_gc (d, gc); invalidate_drawable_cache (d); return 0; } int XClearWindow (Display *dpy, Window win) { 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); } int XSetWindowBackground (Display *dpy, Window w, unsigned long pixel) { Assert (w && w->type == WINDOW, "not a window"); validate_pixel (pixel, 32, NO); w->window.background = pixel; return 0; } static void draw_rect (Display *dpy, Drawable d, GC gc, int x, int y, unsigned int width, unsigned int height, BOOL foreground_p, BOOL fill_p) { CGRect wr = d->frame; CGRect r; r.origin.x = wr.origin.x + x; r.origin.y = wr.origin.y + wr.size.height - y - height; r.size.width = width; r.size.height = height; if (gc) { if (foreground_p) push_fg_gc (d, gc, fill_p); else push_bg_gc (d, gc, fill_p); } CGContextRef cgc = d->cgc; if (fill_p) CGContextFillRect (cgc, r); else { if (gc) set_line_mode (cgc, &gc->gcv); CGContextStrokeRect (cgc, r); } if (gc) pop_gc (d, gc); invalidate_drawable_cache (d); } int XFillRectangle (Display *dpy, Drawable d, GC gc, int x, int y, unsigned int width, unsigned int height) { draw_rect (dpy, d, gc, x, y, width, height, YES, YES); return 0; } int XDrawRectangle (Display *dpy, Drawable d, GC gc, int x, int y, unsigned int width, unsigned int height) { draw_rect (dpy, d, gc, x, y, width, height, YES, NO); return 0; } int XFillRectangles (Display *dpy, Drawable d, GC gc, XRectangle *rects, int n) { CGRect wr = d->frame; int i; CGContextRef cgc = d->cgc; push_fg_gc (d, gc, YES); for (i = 0; i < n; i++) { CGRect r; r.origin.x = wr.origin.x + rects->x; r.origin.y = wr.origin.y + wr.size.height - rects->y - rects->height; r.size.width = rects->width; r.size.height = rects->height; CGContextFillRect (cgc, r); rects++; } pop_gc (d, gc); invalidate_drawable_cache (d); return 0; } int XClearArea (Display *dpy, Window win, int x, int y, int w, int h, Bool exp) { 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); return 0; } int XFillPolygon (Display *dpy, Drawable d, GC gc, XPoint *points, int npoints, int shape, int mode) { CGRect wr = d->frame; int i; push_fg_gc (d, gc, YES); CGContextRef cgc = d->cgc; CGContextBeginPath (cgc); float x = 0, y = 0; for (i = 0; i < npoints; i++) { if (i > 0 && mode == CoordModePrevious) { x += points[i].x; y -= points[i].y; } else { x = wr.origin.x + points[i].x; y = wr.origin.y + wr.size.height - points[i].y; } if (i == 0) CGContextMoveToPoint (cgc, x, y); else CGContextAddLineToPoint (cgc, x, y); } CGContextClosePath (cgc); if (gc->gcv.fill_rule == EvenOddRule) CGContextEOFillPath (cgc); else CGContextFillPath (cgc); pop_gc (d, gc); invalidate_drawable_cache (d); return 0; } #define radians(DEG) ((DEG) * M_PI / 180.0) #define degrees(RAD) ((RAD) * 180.0 / M_PI) static int draw_arc (Display *dpy, Drawable d, GC gc, int x, int y, unsigned int width, unsigned int height, int angle1, int angle2, BOOL fill_p) { CGRect wr = d->frame; CGRect bound; bound.origin.x = wr.origin.x + x; bound.origin.y = wr.origin.y + wr.size.height - y - height; bound.size.width = width; bound.size.height = height; CGPoint ctr; ctr.x = bound.origin.x + bound.size.width /2; ctr.y = bound.origin.y + bound.size.height/2; float r1 = radians (angle1/64.0); float r2 = radians (angle2/64.0) + r1; BOOL clockwise = angle2 < 0; BOOL closed_p = (angle2 >= 360*64 || angle2 <= -360*64); push_fg_gc (d, gc, fill_p); CGContextRef cgc = d->cgc; CGContextBeginPath (cgc); CGContextSaveGState(cgc); CGContextTranslateCTM (cgc, ctr.x, ctr.y); CGContextScaleCTM (cgc, width/2.0, height/2.0); if (fill_p) CGContextMoveToPoint (cgc, 0, 0); CGContextAddArc (cgc, 0.0, 0.0, 1, r1, r2, clockwise); CGContextRestoreGState (cgc); // restore before stroke, for line width if (closed_p) CGContextClosePath (cgc); // for proper line joining if (fill_p) { CGContextFillPath (cgc); } else { set_line_mode (cgc, &gc->gcv); CGContextStrokePath (cgc); } pop_gc (d, gc); invalidate_drawable_cache (d); return 0; } int XDrawArc (Display *dpy, Drawable d, GC gc, int x, int y, unsigned int width, unsigned int height, int angle1, int angle2) { return draw_arc (dpy, d, gc, x, y, width, height, angle1, angle2, NO); } int XFillArc (Display *dpy, Drawable d, GC gc, int x, int y, unsigned int width, unsigned int height, int angle1, int angle2) { return draw_arc (dpy, d, gc, x, y, width, height, angle1, angle2, YES); } int XDrawArcs (Display *dpy, Drawable d, GC gc, XArc *arcs, int narcs) { int i; for (i = 0; i < narcs; i++) draw_arc (dpy, d, gc, arcs[i].x, arcs[i].y, arcs[i].width, arcs[i].height, arcs[i].angle1, arcs[i].angle2, NO); return 0; } int XFillArcs (Display *dpy, Drawable d, GC gc, XArc *arcs, int narcs) { int i; for (i = 0; i < narcs; i++) draw_arc (dpy, d, gc, arcs[i].x, arcs[i].y, arcs[i].width, arcs[i].height, arcs[i].angle1, arcs[i].angle2, YES); return 0; } static void gcv_defaults (XGCValues *gcv, int depth) { memset (gcv, 0, sizeof(*gcv)); gcv->function = GXcopy; gcv->foreground = (depth == 1 ? 1 : WhitePixel(0,0)); gcv->background = (depth == 1 ? 0 : BlackPixel(0,0)); gcv->line_width = 1; gcv->cap_style = CapNotLast; gcv->join_style = JoinMiter; gcv->fill_rule = EvenOddRule; gcv->alpha_allowed_p = NO; gcv->antialias_p = YES; } static void set_gcv (GC gc, XGCValues *from, unsigned long mask) { if (! mask) return; 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; if (mask & GCBackground) gc->gcv.background = from->background; if (mask & GCLineWidth) gc->gcv.line_width = from->line_width; if (mask & GCCapStyle) gc->gcv.cap_style = from->cap_style; if (mask & GCJoinStyle) gc->gcv.join_style = from->join_style; if (mask & GCFillRule) gc->gcv.fill_rule = from->fill_rule; if (mask & GCClipXOrigin) gc->gcv.clip_x_origin = from->clip_x_origin; if (mask & GCClipYOrigin) gc->gcv.clip_y_origin = from->clip_y_origin; if (mask & GCSubwindowMode) gc->gcv.subwindow_mode = from->subwindow_mode; if (mask & GCClipMask) XSetClipMask (0, gc, from->clip_mask); if (mask & GCFont) XSetFont (0, gc, from->font); if (mask & GCForeground) validate_pixel (from->foreground, gc->depth, gc->gcv.alpha_allowed_p); if (mask & GCBackground) validate_pixel (from->background, gc->depth, gc->gcv.alpha_allowed_p); Assert ((! (mask & (GCLineStyle | GCPlaneMask | GCFillStyle | GCTile | GCStipple | GCTileStipXOrigin | GCTileStipYOrigin | GCGraphicsExposures | GCDashOffset | GCDashList | GCArcMode))), "unimplemented gcvalues mask"); } GC XCreateGC (Display *dpy, Drawable d, unsigned long mask, XGCValues *xgcv) { struct jwxyz_GC *gc = (struct jwxyz_GC *) calloc (1, sizeof(*gc)); if (d->type == WINDOW) { gc->depth = 32; } else { /* (d->type == PIXMAP) */ gc->depth = d->pixmap.depth; } gcv_defaults (&gc->gcv, gc->depth); set_gcv (gc, xgcv, mask); return gc; } int XChangeGC (Display *dpy, GC gc, unsigned long mask, XGCValues *gcv) { set_gcv (gc, gcv, mask); return 0; } int XFreeGC (Display *dpy, GC gc) { if (gc->gcv.font) XUnloadFont (dpy, gc->gcv.font); Assert (!!gc->gcv.clip_mask == !!gc->clip_mask, "GC clip mask mixup"); if (gc->gcv.clip_mask) { XFreePixmap (dpy, gc->gcv.clip_mask); CGImageRelease (gc->clip_mask); } free (gc); return 0; } Status XGetWindowAttributes (Display *dpy, Window w, XWindowAttributes *xgwa) { 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; xgwa->width = w->frame.size.width; xgwa->height = w->frame.size.height; xgwa->depth = 32; xgwa->screen = dpy->screen; xgwa->visual = dpy->screen->visual; return 0; } Status XGetGeometry (Display *dpy, Drawable d, Window *root_ret, int *x_ret, int *y_ret, unsigned int *w_ret, unsigned int *h_ret, unsigned int *bw_ret, unsigned int *d_ret) { *x_ret = d->frame.origin.x; *y_ret = d->frame.origin.y; *w_ret = d->frame.size.width; *h_ret = d->frame.size.height; *d_ret = (d->type == WINDOW ? 32 : d->pixmap.depth); *root_ret = RootWindow (dpy, 0); *bw_ret = 0; return True; } Status XAllocColor (Display *dpy, Colormap cmap, XColor *color) { // store 32 bit ARGB in the pixel field. // (The uint32_t is so that 0xFF000000 doesn't become 0xFFFFFFFFFF000000) color->pixel = (uint32_t) (( 0xFF << 24) | (((color->red >> 8) & 0xFF) << 16) | (((color->green >> 8) & 0xFF) << 8) | (((color->blue >> 8) & 0xFF) )); return 1; } Status XAllocColorCells (Display *dpy, Colormap cmap, Bool contig, unsigned long *pmret, unsigned int npl, unsigned long *pxret, unsigned int npx) { return 0; } int XStoreColors (Display *dpy, Colormap cmap, XColor *colors, int n) { Assert(0, "XStoreColors called"); return 0; } int XStoreColor (Display *dpy, Colormap cmap, XColor *c) { Assert(0, "XStoreColor called"); return 0; } int XFreeColors (Display *dpy, Colormap cmap, unsigned long *px, int npixels, unsigned long planes) { return 0; } Status XParseColor (Display *dpy, Colormap cmap, const char *spec, XColor *ret) { unsigned char r=0, g=0, b=0; if (*spec == '#' && strlen(spec) == 7) { static unsigned const char hex[] = { // yeah yeah, shoot me. 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,3,4,5,6,7,8,9,0,0,0,0,0,0, 0,10,11,12,13,14,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,10,11,12,13,14,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; r = (hex[spec[1]] << 4) | hex[spec[2]]; g = (hex[spec[3]] << 4) | hex[spec[4]]; b = (hex[spec[5]] << 4) | hex[spec[6]]; } else if (!strcasecmp(spec,"black")) { // r = g = b = 0; } else if (!strcasecmp(spec,"white")) { r = g = b = 255; } else if (!strcasecmp(spec,"red")) { r = 255; } else if (!strcasecmp(spec,"green")) { g = 255; } else if (!strcasecmp(spec,"blue")) { b = 255; } else if (!strcasecmp(spec,"cyan")) { g = b = 255; } else if (!strcasecmp(spec,"magenta")) { r = b = 255; } else if (!strcasecmp(spec,"yellow")) { r = g = 255; } else { return 0; } ret->red = (r << 8) | r; ret->green = (g << 8) | g; ret->blue = (b << 8) | b; ret->flags = DoRed|DoGreen|DoBlue; return 1; } Status XAllocNamedColor (Display *dpy, Colormap cmap, char *name, XColor *screen_ret, XColor *exact_ret) { if (! XParseColor (dpy, cmap, name, screen_ret)) return False; *exact_ret = *screen_ret; return XAllocColor (dpy, cmap, screen_ret); } int XQueryColor (Display *dpy, Colormap cmap, XColor *color) { validate_pixel (color->pixel, 32, NO); unsigned char r = ((color->pixel >> 16) & 0xFF); unsigned char g = ((color->pixel >> 8) & 0xFF); unsigned char b = ((color->pixel ) & 0xFF); color->red = (r << 8) | r; color->green = (g << 8) | g; color->blue = (b << 8) | b; color->flags = DoRed|DoGreen|DoBlue; return 0; } int XQueryColors (Display *dpy, Colormap cmap, XColor *c, int n) { int i; for (i = 0; i < n; i++) XQueryColor (dpy, cmap, &c[i]); return 0; } static unsigned long ximage_getpixel_1 (XImage *ximage, int x, int y) { return ((ximage->data [y * ximage->bytes_per_line + (x>>3)] >> (x & 7)) & 1); } static int ximage_putpixel_1 (XImage *ximage, int x, int y, unsigned long pixel) { if (pixel) ximage->data [y * ximage->bytes_per_line + (x>>3)] |= (1 << (x & 7)); else ximage->data [y * ximage->bytes_per_line + (x>>3)] &= ~(1 << (x & 7)); return 0; } static unsigned long ximage_getpixel_32 (XImage *ximage, int x, int y) { return ((unsigned long) *((uint32_t *) ximage->data + (y * (ximage->bytes_per_line >> 2)) + x)); } static int ximage_putpixel_32 (XImage *ximage, int x, int y, unsigned long pixel) { *((uint32_t *) ximage->data + (y * (ximage->bytes_per_line >> 2)) + x) = (uint32_t) pixel; return 0; } Status XInitImage (XImage *ximage) { if (!ximage->bytes_per_line) ximage->bytes_per_line = (ximage->depth == 1 ? (ximage->width + 7) / 8 : ximage->width * 4); if (ximage->depth == 1) { ximage->f.put_pixel = ximage_putpixel_1; ximage->f.get_pixel = ximage_getpixel_1; } else if (ximage->depth == 32 || ximage->depth == 24) { ximage->f.put_pixel = ximage_putpixel_32; ximage->f.get_pixel = ximage_getpixel_32; } else { Assert (0, "unknown depth"); } return 1; } XImage * XCreateImage (Display *dpy, Visual *visual, unsigned int depth, int format, int offset, char *data, unsigned int width, unsigned int height, int bitmap_pad, int bytes_per_line) { XImage *ximage = (XImage *) calloc (1, sizeof(*ximage)); ximage->width = width; ximage->height = height; ximage->format = format; ximage->data = data; ximage->bitmap_unit = 8; ximage->byte_order = MSBFirst; ximage->bitmap_bit_order = ximage->byte_order; ximage->bitmap_pad = bitmap_pad; ximage->depth = depth; ximage->red_mask = (depth == 1 ? 0 : 0x00FF0000); ximage->green_mask = (depth == 1 ? 0 : 0x0000FF00); ximage->blue_mask = (depth == 1 ? 0 : 0x000000FF); ximage->bits_per_pixel = (depth == 1 ? 1 : 32); ximage->bytes_per_line = bytes_per_line; XInitImage (ximage); return ximage; } XImage * XSubImage (XImage *from, int x, int y, unsigned int w, unsigned int h) { XImage *to = XCreateImage (0, 0, from->depth, from->format, 0, 0, w, h, from->bitmap_pad, 0); to->data = (char *) malloc (h * to->bytes_per_line); if (x >= from->width) w = 0; else if (x+w > from->width) w = from->width - x; if (y >= from->height) h = 0; else if (y+h > from->height) h = from->height - y; int tx, ty; for (ty = 0; ty < h; ty++) for (tx = 0; tx < w; tx++) XPutPixel (to, tx, ty, XGetPixel (from, x+tx, y+ty)); return to; } XPixmapFormatValues * XListPixmapFormats (Display *dpy, int *n_ret) { XPixmapFormatValues *ret = calloc (2, sizeof(*ret)); ret[0].depth = 32; ret[0].bits_per_pixel = 32; ret[0].scanline_pad = 8; ret[1].depth = 1; ret[1].bits_per_pixel = 1; ret[1].scanline_pad = 8; *n_ret = 2; return ret; } unsigned long XGetPixel (XImage *ximage, int x, int y) { return ximage->f.get_pixel (ximage, x, y); } int XPutPixel (XImage *ximage, int x, int y, unsigned long pixel) { return ximage->f.put_pixel (ximage, x, y, pixel); } int XDestroyImage (XImage *ximage) { if (ximage->data) free (ximage->data); free (ximage); return 0; } static void flipbits (unsigned const char *in, unsigned char *out, int length) { static const unsigned char table[256] = { 0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0, 0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8, 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8, 0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4, 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4, 0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC, 0x1C, 0x9C, 0x5C, 0xDC, 0x3C, 0xBC, 0x7C, 0xFC, 0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2, 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2, 0x0A, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA, 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA, 0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6, 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6, 0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE, 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE, 0x01, 0x81, 0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1, 0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1, 0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9, 0x19, 0x99, 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9, 0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5, 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5, 0x0D, 0x8D, 0x4D, 0xCD, 0x2D, 0xAD, 0x6D, 0xED, 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD, 0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3, 0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3, 0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB, 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB, 0x07, 0x87, 0x47, 0xC7, 0x27, 0xA7, 0x67, 0xE7, 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7, 0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF }; while (--length > 0) *out++ = table[*in++]; } int XPutImage (Display *dpy, Drawable d, GC gc, XImage *ximage, int src_x, int src_y, int dest_x, int dest_y, unsigned int w, unsigned int h) { CGRect wr = d->frame; Assert (gc, "no GC"); Assert ((w < 65535), "improbably large width"); Assert ((h < 65535), "improbably large height"); Assert ((src_x < 65535 && src_x > -65535), "improbably large src_x"); Assert ((src_y < 65535 && src_y > -65535), "improbably large src_y"); Assert ((dest_x < 65535 && dest_x > -65535), "improbably large dest_x"); Assert ((dest_y < 65535 && dest_y > -65535), "improbably large dest_y"); // Clip width and height to the bounds of the Drawable // if (dest_x + w > wr.size.width) { if (dest_x > wr.size.width) return 0; w = wr.size.width - dest_x; } if (dest_y + h > wr.size.height) { if (dest_y > wr.size.height) return 0; h = wr.size.height - dest_y; } if (w <= 0 || h <= 0) return 0; // Clip width and height to the bounds of the XImage // if (src_x + w > ximage->width) { if (src_x > ximage->width) return 0; w = ximage->width - src_x; } if (src_y + h > ximage->height) { if (src_y > ximage->height) return 0; h = ximage->height - src_y; } if (w <= 0 || h <= 0) return 0; CGContextRef cgc = d->cgc; if (gc->gcv.function == GXset || gc->gcv.function == GXclear) { // "set" and "clear" are dumb drawing modes that ignore the source // bits and just draw solid rectangles. set_color (cgc, (gc->gcv.function == GXset ? (gc->depth == 1 ? 1 : WhitePixel(0,0)) : (gc->depth == 1 ? 0 : BlackPixel(0,0))), gc->depth, gc->gcv.alpha_allowed_p, YES); draw_rect (dpy, d, 0, dest_x, dest_y, w, h, YES, YES); return 0; } int bpl = ximage->bytes_per_line; int bpp = ximage->bits_per_pixel; int bsize = bpl * h; char *data = ximage->data; CGRect r; r.origin.x = wr.origin.x + dest_x; r.origin.y = wr.origin.y + wr.size.height - dest_y - h; r.size.width = w; r.size.height = h; if (bpp == 32) { /* Take advantage of the fact that it's ok for (bpl != w * bpp) to create a CGImage from a sub-rectagle of the XImage. */ data += (src_y * bpl) + (src_x * 4); CGDataProviderRef prov = CGDataProviderCreateWithData (NULL, data, bsize, NULL); CGImageRef cgi = CGImageCreate (w, h, bpp/4, bpp, bpl, dpy->colorspace, /* Need this for XPMs to have the right colors, e.g. the logo in "maze". */ (kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host), prov, NULL, /* decode[] */ NO, /* interpolate */ kCGRenderingIntentDefault); CGDataProviderRelease (prov); //Assert (CGImageGetColorSpace (cgi) == dpy->colorspace, "bad colorspace"); CGContextDrawImage (cgc, r, cgi); CGImageRelease (cgi); } else { // (bpp == 1) /* To draw a 1bpp image, we use it as a mask and fill two rectangles. #### However, the bit order within a byte in a 1bpp XImage is the wrong way around from what Quartz expects, so first we have to copy the data to reverse it. Shit! Maybe it would be worthwhile to go through the hacks and #ifdef each one that diddles 1bpp XImage->data directly... */ Assert ((src_x % 8) == 0, "XPutImage with non-byte-aligned 1bpp X offset not implemented"); data += (src_y * bpl) + (src_x / 8); // move to x,y within the data unsigned char *flipped = (unsigned char *) malloc (bsize); flipbits ((unsigned char *) data, flipped, bsize); CGDataProviderRef prov = CGDataProviderCreateWithData (NULL, flipped, bsize, NULL); CGImageRef mask = CGImageMaskCreate (w, h, 1, bpp, bpl, prov, NULL, /* decode[] */ NO); /* interpolate */ push_fg_gc (d, gc, YES); CGContextFillRect (cgc, r); // foreground color CGContextClipToMask (cgc, r, mask); set_color (cgc, gc->gcv.background, gc->depth, NO, YES); CGContextFillRect (cgc, r); // background color pop_gc (d, gc); free (flipped); CGDataProviderRelease (prov); CGImageRelease (mask); } invalidate_drawable_cache (d); return 0; } XImage * XGetImage (Display *dpy, Drawable d, int x, int y, unsigned int width, unsigned int height, unsigned long plane_mask, int format) { const unsigned char *data = 0; int depth, ibpp, ibpl; enum { RGBA, ARGB, BGRA } src_format; // As bytes. # ifndef USE_BACKBUFFER NSBitmapImageRep *bm = 0; # endif Assert ((width < 65535), "improbably large width"); Assert ((height < 65535), "improbably large height"); Assert ((x < 65535 && x > -65535), "improbably large x"); Assert ((y < 65535 && y > -65535), "improbably large y"); CGContextRef cgc = d->cgc; #ifndef USE_BACKBUFFER // Because of the backbuffer, all iPhone Windows work like Pixmaps. if (d->type == PIXMAP) # endif { depth = (d->type == PIXMAP ? d->pixmap.depth : 32); // We create pixmaps and iPhone backbuffers with kCGImageAlphaNoneSkipFirst. src_format = BGRA; // #### Should this be ARGB on PPC? ibpp = CGBitmapContextGetBitsPerPixel (cgc); ibpl = CGBitmapContextGetBytesPerRow (cgc); data = CGBitmapContextGetData (cgc); Assert (data, "CGBitmapContextGetData failed"); # ifndef USE_BACKBUFFER } else { /* (d->type == WINDOW) */ // get the bits (desired sub-rectangle) out of the NSView NSRect nsfrom; nsfrom.origin.x = x; // nsfrom.origin.y = y; nsfrom.origin.y = d->frame.size.height - height - y; nsfrom.size.width = width; nsfrom.size.height = height; bm = [[NSBitmapImageRep alloc] initWithFocusedViewRect:nsfrom]; depth = 32; src_format = ([bm bitmapFormat] & NSAlphaFirstBitmapFormat) ? ARGB : RGBA; ibpp = [bm bitsPerPixel]; ibpl = [bm bytesPerRow]; data = [bm bitmapData]; Assert (data, "NSBitmapImageRep initWithFocusedViewRect failed"); # endif // !USE_BACKBUFFER } // data points at (x,y) with ibpl rowstride. ignore x,y from now on. data += (y * ibpl) + (x * (ibpp/8)); format = (depth == 1 ? XYPixmap : ZPixmap); XImage *image = XCreateImage (dpy, 0, depth, format, 0, 0, width, height, 0, 0); image->data = (char *) malloc (height * image->bytes_per_line); int obpl = image->bytes_per_line; /* both PPC and Intel use word-ordered ARGB frame buffers, which means that on Intel it is BGRA when viewed by bytes (And BGR when using 24bpp packing). BUT! Intel-64 stores alpha at the other end! 32bit=RGBA, 64bit=ARGB. The NSAlphaFirstBitmapFormat bit in bitmapFormat seems to be the indicator of this latest kink. */ int xx, yy; if (depth == 1) { const unsigned char *iline = data; for (yy = 0; yy < height; yy++) { const unsigned char *iline2 = iline; for (xx = 0; xx < width; xx++) { iline2++; // ignore R or A or A or B iline2++; // ignore G or B or R or G unsigned char r = *iline2++; // use B or G or G or R if (ibpp == 32) iline2++; // ignore A or R or B or A XPutPixel (image, xx, yy, (r ? 1 : 0)); } iline += ibpl; } } else { Assert (ibpp == 24 || ibpp == 32, "weird obpp"); const unsigned char *iline = data; unsigned char *oline = (unsigned char *) image->data; for (yy = 0; yy < height; yy++) { const unsigned char *iline2 = iline; unsigned char *oline2 = oline; switch (src_format) { case ARGB: for (xx = 0; xx < width; xx++) { unsigned char a = (ibpp == 32 ? (*iline2++) : 0xFF); unsigned char r = *iline2++; unsigned char g = *iline2++; unsigned char b = *iline2++; uint32_t pixel = ((a << 24) | (r << 16) | (g << 8) | (b << 0)); *((uint32_t *) oline2) = pixel; oline2 += 4; } break; case RGBA: for (xx = 0; xx < width; xx++) { unsigned char r = *iline2++; unsigned char g = *iline2++; unsigned char b = *iline2++; unsigned char a = (ibpp == 32 ? (*iline2++) : 0xFF); uint32_t pixel = ((a << 24) | (r << 16) | (g << 8) | (b << 0)); *((uint32_t *) oline2) = pixel; oline2 += 4; } break; case BGRA: for (xx = 0; xx < width; xx++) { unsigned char b = *iline2++; unsigned char g = *iline2++; unsigned char r = *iline2++; unsigned char a = (ibpp == 32 ? (*iline2++) : 0xFF); uint32_t pixel = ((a << 24) | (r << 16) | (g << 8) | (b << 0)); *((uint32_t *) oline2) = pixel; oline2 += 4; } break; default: abort(); break; } oline += obpl; iline += ibpl; } } # ifndef USE_BACKBUFFER if (bm) [bm release]; # endif return image; } /* Returns a transformation matrix to do rotation as per the provided EXIF "Orientation" value. */ static CGAffineTransform exif_rotate (int rot, CGSize rect) { CGAffineTransform trans = CGAffineTransformIdentity; switch (rot) { case 2: // flip horizontal trans = CGAffineTransformMakeTranslation (rect.width, 0); trans = CGAffineTransformScale (trans, -1, 1); break; case 3: // rotate 180 trans = CGAffineTransformMakeTranslation (rect.width, rect.height); trans = CGAffineTransformRotate (trans, M_PI); break; case 4: // flip vertical trans = CGAffineTransformMakeTranslation (0, rect.height); trans = CGAffineTransformScale (trans, 1, -1); break; case 5: // transpose (UL-to-LR axis) trans = CGAffineTransformMakeTranslation (rect.height, rect.width); trans = CGAffineTransformScale (trans, -1, 1); trans = CGAffineTransformRotate (trans, 3 * M_PI / 2); break; case 6: // rotate 90 trans = CGAffineTransformMakeTranslation (0, rect.width); trans = CGAffineTransformRotate (trans, 3 * M_PI / 2); break; case 7: // transverse (UR-to-LL axis) trans = CGAffineTransformMakeScale (-1, 1); trans = CGAffineTransformRotate (trans, M_PI / 2); break; case 8: // rotate 270 trans = CGAffineTransformMakeTranslation (rect.height, 0); trans = CGAffineTransformRotate (trans, M_PI / 2); break; default: break; } return trans; } void jwxyz_draw_NSImage_or_CGImage (Display *dpy, Drawable d, Bool nsimg_p, void *img_arg, XRectangle *geom_ret, int exif_rotation) { CGImageRef cgi; # ifndef USE_IPHONE CGImageSourceRef cgsrc; # endif // USE_IPHONE NSSize imgr; CGContextRef cgc = d->cgc; if (nsimg_p) { NSImage *nsimg = (NSImage *) img_arg; imgr = [nsimg size]; # ifndef USE_IPHONE // convert the NSImage to a CGImage via the toll-free-bridging // of NSData and CFData... // NSData *nsdata = [NSBitmapImageRep TIFFRepresentationOfImageRepsInArray: [nsimg representations]]; CFDataRef cfdata = (CFDataRef) nsdata; cgsrc = CGImageSourceCreateWithData (cfdata, NULL); cgi = CGImageSourceCreateImageAtIndex (cgsrc, 0, NULL); # else // USE_IPHONE cgi = nsimg.CGImage; # endif // USE_IPHONE } else { cgi = (CGImageRef) img_arg; imgr.width = CGImageGetWidth (cgi); imgr.height = CGImageGetHeight (cgi); } Bool rot_p = (exif_rotation >= 5); if (rot_p) imgr = NSMakeSize (imgr.height, imgr.width); CGRect winr = d->frame; float rw = winr.size.width / imgr.width; float rh = winr.size.height / imgr.height; float r = (rw < rh ? rw : rh); CGRect dst, dst2; dst.size.width = imgr.width * r; dst.size.height = imgr.height * r; dst.origin.x = (winr.size.width - dst.size.width) / 2; dst.origin.y = (winr.size.height - dst.size.height) / 2; dst2.origin.x = dst2.origin.y = 0; if (rot_p) { dst2.size.width = dst.size.height; dst2.size.height = dst.size.width; } else { dst2.size = dst.size; } // Clear the part not covered by the image to background or black. // if (d->type == WINDOW) XClearWindow (dpy, d); else { set_color (cgc, BlackPixel(dpy,0), 32, NO, YES); draw_rect (dpy, d, 0, 0, 0, winr.size.width, winr.size.height, NO, YES); } CGAffineTransform trans = exif_rotate (exif_rotation, rot_p ? dst2.size : dst.size); CGContextSaveGState (cgc); CGContextConcatCTM (cgc, CGAffineTransformMakeTranslation (dst.origin.x, dst.origin.y)); CGContextConcatCTM (cgc, trans); //Assert (CGImageGetColorSpace (cgi) == dpy->colorspace, "bad colorspace"); CGContextDrawImage (cgc, dst2, cgi); CGContextRestoreGState (cgc); # ifndef USE_IPHONE if (nsimg_p) { CFRelease (cgsrc); CGImageRelease (cgi); } # endif // USE_IPHONE if (geom_ret) { geom_ret->x = dst.origin.x; geom_ret->y = dst.origin.y; geom_ret->width = dst.size.width; geom_ret->height = dst.size.height; } invalidate_drawable_cache (d); } Pixmap XCreatePixmapFromBitmapData (Display *dpy, Drawable drawable, const char *data, unsigned int w, unsigned int h, unsigned long fg, unsigned int bg, unsigned int depth) { Pixmap p = XCreatePixmap (dpy, drawable, w, h, depth); XImage *image = XCreateImage (dpy, 0, 1, XYPixmap, 0, (char *) data, w, h, 0, 0); XGCValues gcv; gcv.foreground = fg; gcv.background = bg; GC gc = XCreateGC (dpy, p, GCForeground|GCBackground, &gcv); XPutImage (dpy, p, gc, image, 0, 0, 0, 0, w, h); XFreeGC (dpy, gc); image->data = 0; XDestroyImage (image); return p; } Pixmap XCreatePixmap (Display *dpy, Drawable d, unsigned int width, unsigned int height, unsigned int depth) { char *data = (char *) malloc (width * height * 4); if (! data) return 0; Pixmap p = (Pixmap) calloc (1, sizeof(*p)); p->type = PIXMAP; p->frame.size.width = width; p->frame.size.height = height; p->pixmap.depth = depth; p->pixmap.cgc_buffer = data; /* Quartz doesn't have a 1bpp image type. Used to use 8bpp gray images instead of 1bpp, but some Mac video cards don't support that! So we always use 32bpp, regardless of depth. */ p->cgc = CGBitmapContextCreate (data, width, height, 8, /* bits per component */ width * 4, /* bpl */ dpy->colorspace, // Without this, it returns 0... (kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host) ); Assert (p->cgc, "could not create CGBitmapContext"); return p; } int XFreePixmap (Display *d, Pixmap p) { Assert (p && p->type == PIXMAP, "not a pixmap"); invalidate_drawable_cache (p); CGContextRelease (p->cgc); if (p->pixmap.cgc_buffer) free (p->pixmap.cgc_buffer); free (p); return 0; } static Pixmap copy_pixmap (Display *dpy, Pixmap p) { if (!p) return 0; Assert (p->type == PIXMAP, "not a pixmap"); int width = p->frame.size.width; int height = p->frame.size.height; char *data = (char *) malloc (width * height * 4); if (! data) return 0; memcpy (data, p->pixmap.cgc_buffer, width * height * 4); Pixmap p2 = (Pixmap) malloc (sizeof (*p2)); *p2 = *p; p2->cgi = 0; p2->pixmap.cgc_buffer = data; p2->cgc = CGBitmapContextCreate (data, width, height, 8, /* bits per component */ width * 4, /* bpl */ dpy->colorspace, // Without this, it returns 0... (kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host) ); Assert (p2->cgc, "could not create CGBitmapContext"); return p2; } /* Font metric terminology, as used by X11: "lbearing" is the distance from the logical origin to the leftmost pixel. If a character's ink extends to the left of the origin, it is negative. "rbearing" is the distance from the logical origin to the rightmost pixel. "descent" is the distance from the logical origin to the bottommost pixel. For characters with descenders, it is negative. "ascent" is the distance from the logical origin to the topmost pixel. It is the number of pixels above the baseline. "width" is the distance from the logical origin to the position where the logical origin of the next character should be placed. If "rbearing" is greater than "width", then this character overlaps the following character. If smaller, then there is trailing blank space. */ // This is XQueryFont, but for the XFontStruct embedded in 'Font' // static void query_font (Font fid) { if (!fid || !fid->nsfont) { Assert (0, "no NSFont in fid"); return; } if (![fid->nsfont fontName]) { Assert(0, @"broken NSFont in fid"); return; } int first = 32; int last = 255; XFontStruct *f = &fid->metrics; XCharStruct *min = &f->min_bounds; XCharStruct *max = &f->max_bounds; #define CEIL(F) ((F) < 0 ? floor(F) : ceil(F)) f->fid = fid; f->min_char_or_byte2 = first; f->max_char_or_byte2 = last; f->default_char = 'M'; f->ascent = CEIL ([fid->nsfont ascender]); f->descent = -CEIL ([fid->nsfont descender]); min->width = 255; // set to smaller values in the loop min->ascent = 255; min->descent = 255; min->lbearing = 255; min->rbearing = 255; f->per_char = (XCharStruct *) calloc (last-first+2, sizeof (XCharStruct)); int i; # ifndef USE_IPHONE NSBezierPath *bpath = [NSBezierPath bezierPath]; # 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]; str[0] = i; str[1] = 0; NSString *nsstr = [NSString stringWithCString:(char *) str encoding:NSISOLatin1StringEncoding]; NSPoint advancement = { 0, }; NSRect bbox = {{ 0, }, }; # ifndef USE_IPHONE /* I can't believe we have to go through this bullshit just to convert a 'char' to an NSGlyph!! You might think that we could do NSGlyph glyph = [fid->nsfont glyphWithName:nsstr]; but that doesn't work; my guess is that glyphWithName expects full Unicrud names like "LATIN CAPITAL LETTER A WITH ACUTE". */ NSGlyph glyph; { NSTextStorage *ts = [[NSTextStorage alloc] initWithString:nsstr]; [ts setFont:fid->nsfont]; NSLayoutManager *lm = [[NSLayoutManager alloc] init]; /* Without this, the layout manager ends up on a queue somewhere and is referenced again after we return to the command loop. Since we don't use this layout manager again, by that time it may have been garbage collected, and we crash. Setting this seems to cause `lm' to no longer be referenced once we exit this block. */ [lm setBackgroundLayoutEnabled:NO]; NSTextContainer *tc = [[NSTextContainer alloc] init]; [lm addTextContainer:tc]; [tc release]; // lm retains tc [ts addLayoutManager:lm]; [lm release]; // ts retains lm glyph = [lm glyphAtIndex:0]; [ts release]; } /* Compute the bounding box and advancement by converting the glyph to a bezier path. There appears to be *no other way* to find out the bounding box of a character: [NSFont boundingRectForGlyph] and [NSString sizeWithAttributes] both return an advancement-sized rectangle, not a rectangle completely enclosing the glyph's ink. */ advancement.x = advancement.y = 0; [bpath removeAllPoints]; [bpath moveToPoint:advancement]; [bpath appendBezierPathWithGlyph:glyph inFont:fid->nsfont]; advancement = [bpath currentPoint]; bbox = [bpath bounds]; # else // USE_IPHONE /* 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. 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. */ 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; /* A bug that existed was that the GL FPS display was truncating characters slightly: commas looked like periods. At one point, I believed the bounding box was being rounded wrong and we needed to add padding to it here. I think what was actually going on was, I was computing rbearing wrong. Also there was an off-by-one error in texfont.c, displaying too little of the bitmap. Adding arbitrarily large padding to the bbox is fine in fontglide and FPS display, but screws up BSOD. Increasing bbox width makes inverted text print too wide; decreasing origin makes characters clip. I think that all 3 states are correct now with the new lbearing computation plus the texfont fix. */ # if 0 double kludge = 2; bbox.origin.x -= kludge; bbox.origin.y -= kludge; bbox.size.width += kludge; bbox.size.height += kludge; # endif } # endif // USE_IPHONE /* Now that we know the advancement and bounding box, we can compute the lbearing and rbearing. */ XCharStruct *cs = &f->per_char[i-first]; cs->ascent = CEIL (bbox.origin.y) + CEIL (bbox.size.height); cs->descent = CEIL(-bbox.origin.y); cs->lbearing = floor (bbox.origin.x); // cs->rbearing = CEIL (bbox.origin.x) + CEIL (bbox.size.width); cs->rbearing = CEIL (bbox.origin.x + bbox.size.width) - cs->lbearing; cs->width = CEIL (advancement.x); // Assert (cs->rbearing - cs->lbearing == CEIL(bbox.size.width), // "bbox w wrong"); Assert (cs->ascent + cs->descent == CEIL(bbox.size.height), "bbox h wrong"); max->width = MAX (max->width, cs->width); max->ascent = MAX (max->ascent, cs->ascent); max->descent = MAX (max->descent, cs->descent); max->lbearing = MAX (max->lbearing, cs->lbearing); max->rbearing = MAX (max->rbearing, cs->rbearing); min->width = MIN (min->width, cs->width); min->ascent = MIN (min->ascent, cs->ascent); min->descent = MIN (min->descent, cs->descent); min->lbearing = MIN (min->lbearing, cs->lbearing); min->rbearing = MIN (min->rbearing, cs->rbearing); # undef CEIL #if 0 fprintf(stderr, " %3d %c: w=%3d lb=%3d rb=%3d as=%3d ds=%3d " " bb=%5.1f x %5.1f @ %5.1f %5.1f adv=%5.1f %5.1f\n", i, i, cs->width, cs->lbearing, cs->rbearing, cs->ascent, cs->descent, bbox.size.width, bbox.size.height, bbox.origin.x, bbox.origin.y, advancement.x, advancement.y); #endif } # ifdef USE_IPHONE CFRelease (ctfont); # endif } // Since 'Font' includes the metrics, this just makes a copy of that. // XFontStruct * XQueryFont (Display *dpy, Font fid) { // copy XFontStruct XFontStruct *f = (XFontStruct *) calloc (1, sizeof(*f)); *f = fid->metrics; // copy XCharStruct array int size = f->max_char_or_byte2 - f->min_char_or_byte2; f->per_char = (XCharStruct *) calloc (size + 2, sizeof (XCharStruct)); memcpy (f->per_char, fid->metrics.per_char, size * sizeof (XCharStruct)); return f; } static Font copy_font (Font fid) { // copy 'Font' struct Font fid2 = (Font) malloc (sizeof(*fid2)); *fid2 = *fid; // copy XCharStruct array int size = fid->metrics.max_char_or_byte2 - fid->metrics.min_char_or_byte2; fid2->metrics.per_char = (XCharStruct *) malloc ((size + 2) * sizeof (XCharStruct)); memcpy (fid2->metrics.per_char, fid->metrics.per_char, size * sizeof (XCharStruct)); // copy the other pointers fid2->ps_name = strdup (fid->ps_name); // [fid2->nsfont retain]; fid2->metrics.fid = fid2; return fid2; } static NSFont * try_font (BOOL fixed, BOOL bold, BOOL ital, BOOL serif, float size, char **name_ret) { Assert (size > 0, "zero font size"); const char *name; if (fixed) { // // "Monaco" only exists in plain. // "LucidaSansTypewriterStd" gets an AGL bad value error. // if (bold && ital) name = "Courier-BoldOblique"; else if (bold) name = "Courier-Bold"; else if (ital) name = "Courier-Oblique"; else name = "Courier"; } else if (serif) { // // "Georgia" looks better than "Times". // if (bold && ital) name = "Georgia-BoldItalic"; else if (bold) name = "Georgia-Bold"; else if (ital) name = "Georgia-Italic"; else name = "Georgia"; } else { // // "Geneva" only exists in plain. // "LucidaSansStd-BoldItalic" gets an AGL bad value error. // "Verdana" renders smoother than "Helvetica" for some reason. // if (bold && ital) name = "Verdana-BoldItalic"; else if (bold) name = "Verdana-Bold"; else if (ital) name = "Verdana-Italic"; else name = "Verdana"; } NSString *nsname = [NSString stringWithCString:name encoding:NSUTF8StringEncoding]; NSFont *f = [NSFont fontWithName:nsname size:size]; if (f) *name_ret = strdup(name); return f; } static NSFont * try_native_font (const char *name, float scale, char **name_ret, float *size_ret) { if (!name) return 0; const char *spc = strrchr (name, ' '); if (!spc) return 0; int dsize = 0; if (1 != sscanf (spc, " %d ", &dsize)) return 0; float size = dsize; if (size <= 4) return 0; size *= scale; char *name2 = strdup (name); name2[strlen(name2) - strlen(spc)] = 0; NSString *nsname = [NSString stringWithCString:name2 encoding:NSUTF8StringEncoding]; NSFont *f = [NSFont fontWithName:nsname size:size]; if (f) { *name_ret = name2; *size_ret = size; return f; } else { free (name2); return 0; } } /* Returns a random font in the given size and face. */ static NSFont * random_font (BOOL bold, BOOL ital, float size, char **name_ret) { # ifndef USE_IPHONE NSFontTraitMask mask = ((bold ? NSBoldFontMask : NSUnboldFontMask) | (ital ? NSItalicFontMask : NSUnitalicFontMask)); NSArray *fonts = [[NSFontManager sharedFontManager] availableFontNamesWithTraits:mask]; if (!fonts) return 0; int n = [fonts count]; if (n <= 0) return 0; int j; for (j = 0; j < n; j++) { int i = random() % n; NSString *name = [fonts objectAtIndex:i]; NSFont *f = [NSFont fontWithName:name size:size]; if (!f) continue; /* Don't use this font if it (probably) doesn't include ASCII characters. */ NSStringEncoding enc = [f mostCompatibleStringEncoding]; if (! (enc == NSUTF8StringEncoding || enc == NSISOLatin1StringEncoding || enc == NSNonLossyASCIIStringEncoding || enc == NSISOLatin2StringEncoding || enc == NSUnicodeStringEncoding || enc == NSWindowsCP1250StringEncoding || enc == NSWindowsCP1252StringEncoding || enc == NSMacOSRomanStringEncoding)) { // NSLog(@"skipping \"%@\": encoding = %d", name, enc); continue; } // NSLog(@"using \"%@\": %d", name, enc); *name_ret = strdup ([name cStringUsingEncoding:NSUTF8StringEncoding]); return f; } // None of the fonts support ASCII? return 0; # else // USE_IPHONE NSMutableArray *fonts = [NSMutableArray arrayWithCapacity:100]; NSArray *families = [UIFont familyNames]; NSMutableDictionary *famdict = [NSMutableDictionary dictionaryWithCapacity:100]; NSObject *y = [NSNumber numberWithBool:YES]; for (NSString *name in families) { // There are many dups in the families array -- uniquify it. [famdict setValue:y forKey:name]; } for (NSString *name in famdict) { for (NSString *fn in [UIFont fontNamesForFamilyName:name]) { # define MATCH(X) \ ([fn rangeOfString:X options:NSCaseInsensitiveSearch].location \ != NSNotFound) BOOL bb = MATCH(@"Bold"); BOOL ii = MATCH(@"Italic") || MATCH(@"Oblique"); if (!bold != !bb) continue; if (!ital != !ii) continue; /* Check if it can do ASCII. No good way to accomplish this! These are fonts present in iPhone Simulator as of June 2012 that don't include ASCII. */ if (MATCH(@"AppleGothic") || // Korean MATCH(@"Dingbats") || // Dingbats MATCH(@"Emoji") || // Emoticons MATCH(@"Geeza") || // Arabic MATCH(@"Hebrew") || // Hebrew MATCH(@"HiraKaku") || // Japanese MATCH(@"HiraMin") || // Japanese MATCH(@"Kailasa") || // Tibetan MATCH(@"Ornaments") || // Dingbats MATCH(@"STHeiti") // Chinese ) continue; [fonts addObject:fn]; # undef MATCH } } if (! [fonts count]) return 0; // Nothing suitable? int i = random() % [fonts count]; NSString *name = [fonts objectAtIndex:i]; UIFont *ff = [UIFont fontWithName:name size:size]; *name_ret = strdup ([name cStringUsingEncoding:NSUTF8StringEncoding]); return ff; # endif // USE_IPHONE } static NSFont * try_xlfd_font (const char *name, float scale, char **name_ret, float *size_ret) { NSFont *nsfont = 0; BOOL bold = NO; BOOL ital = NO; BOOL fixed = NO; BOOL serif = NO; BOOL rand = NO; float size = 0; char *ps_name = 0; const char *s = (name ? name : ""); while (*s) { while (*s && (*s == '*' || *s == '-')) s++; const char *s2 = s; while (*s2 && (*s2 != '*' && *s2 != '-')) s2++; int L = s2-s; if (s == s2) ; # define CMP(STR) (L == strlen(STR) && !strncasecmp (s, (STR), L)) else if (CMP ("random")) rand = YES; else if (CMP ("bold")) bold = YES; else if (CMP ("i")) ital = YES; else if (CMP ("o")) ital = YES; else if (CMP ("courier")) fixed = YES; else if (CMP ("fixed")) fixed = YES; else if (CMP ("m")) fixed = YES; else if (CMP ("times")) serif = YES; else if (CMP ("6x10")) fixed = YES, size = 8; else if (CMP ("6x10bold")) fixed = YES, size = 8, bold = YES; else if (CMP ("9x15")) fixed = YES, size = 12; else if (CMP ("9x15bold")) fixed = YES, size = 12, bold = YES; else if (CMP ("vga")) fixed = YES, size = 12; else if (CMP ("console")) fixed = YES, size = 12; else if (CMP ("gallant")) fixed = YES, size = 12; # undef CMP else if (size == 0) { int n = 0; if (1 == sscanf (s, " %d ", &n)) size = n / 10.0; } s = s2; } if (size < 6 || size > 1000) size = 12; size *= scale; if (rand) nsfont = random_font (bold, ital, size, &ps_name); if (!nsfont) nsfont = try_font (fixed, bold, ital, serif, size, &ps_name); // if that didn't work, turn off attibutes until it does // (e.g., there is no "Monaco-Bold".) // if (!nsfont && serif) { serif = NO; nsfont = try_font (fixed, bold, ital, serif, size, &ps_name); } if (!nsfont && ital) { ital = NO; nsfont = try_font (fixed, bold, ital, serif, size, &ps_name); } if (!nsfont && bold) { bold = NO; nsfont = try_font (fixed, bold, ital, serif, size, &ps_name); } if (!nsfont && fixed) { fixed = NO; nsfont = try_font (fixed, bold, ital, serif, size, &ps_name); } if (nsfont) { *name_ret = ps_name; *size_ret = size; return nsfont; } else { return 0; } } Font XLoadFont (Display *dpy, const char *name) { Font fid = (Font) calloc (1, sizeof(*fid)); float scale = 1; # ifdef USE_IPHONE // Scale up fonts on Retina displays. scale = dpy->main_window->window.view.contentScaleFactor; # endif fid->nsfont = try_native_font (name, scale, &fid->ps_name, &fid->size); if (!fid->nsfont && name && strchr (name, ' ') && !strchr (name, '*')) { // If name contains a space but no stars, it is a native font spec -- // return NULL so that we know it really didn't exist. Else, it is an // XLFD font, so keep trying. XUnloadFont (dpy, fid); return 0; } if (! fid->nsfont) fid->nsfont = try_xlfd_font (name, scale, &fid->ps_name, &fid->size); // We should never return NULL for XLFD fonts. if (!fid->nsfont) { Assert (0, "no font"); return 0; } CFRetain (fid->nsfont); // needed for garbage collection? //NSLog(@"parsed \"%s\" to %s %.1f", name, fid->ps_name, fid->size); query_font (fid); return fid; } XFontStruct * XLoadQueryFont (Display *dpy, const char *name) { Font fid = XLoadFont (dpy, name); if (!fid) return 0; return XQueryFont (dpy, fid); } int XUnloadFont (Display *dpy, Font fid) { if (fid->ps_name) free (fid->ps_name); if (fid->metrics.per_char) free (fid->metrics.per_char); // #### DAMMIT! I can't tell what's going wrong here, but I keep getting // crashes in [NSFont ascender] <- query_font, and it seems to go away // if I never release the nsfont. So, fuck it, we'll just leak fonts. // They're probably not very big... // // [fid->nsfont release]; // CFRelease (fid->nsfont); free (fid); return 0; } int XFreeFontInfo (char **names, XFontStruct *info, int n) { int i; if (names) { for (i = 0; i < n; i++) if (names[i]) free (names[i]); free (names); } if (info) { for (i = 0; i < n; i++) if (info[i].per_char) free (info[i].per_char); free (info); } return 0; } int XFreeFont (Display *dpy, XFontStruct *f) { Font fid = f->fid; XFreeFontInfo (0, f, 1); XUnloadFont (dpy, fid); return 0; } int XSetFont (Display *dpy, GC gc, Font fid) { if (gc->gcv.font) XUnloadFont (dpy, gc->gcv.font); gc->gcv.font = copy_font (fid); [gc->gcv.font->nsfont retain]; CFRetain (gc->gcv.font->nsfont); // needed for garbage collection? return 0; } int XTextExtents (XFontStruct *f, const char *s, int length, int *dir_ret, int *ascent_ret, int *descent_ret, XCharStruct *cs) { memset (cs, 0, sizeof(*cs)); int i; for (i = 0; i < length; i++) { unsigned char c = (unsigned char) s[i]; if (c < f->min_char_or_byte2 || c > f->max_char_or_byte2) c = f->default_char; const XCharStruct *cc = &f->per_char[c - f->min_char_or_byte2]; if (i == 0) { *cs = *cc; } else { cs->ascent = MAX (cs->ascent, cc->ascent); cs->descent = MAX (cs->descent, cc->descent); cs->lbearing = MIN (cs->lbearing, cs->width + cc->lbearing); cs->rbearing = MAX (cs->rbearing, cs->width + cc->rbearing); cs->width += cc->width; } } *dir_ret = 0; *ascent_ret = f->ascent; *descent_ret = f->descent; return 0; } int XTextWidth (XFontStruct *f, const char *s, int length) { int ascent, descent, dir; XCharStruct cs; XTextExtents (f, s, length, &dir, &ascent, &descent, &cs); return cs.width; } static void set_font (Display *dpy, CGContextRef cgc, GC gc) { Font font = gc->gcv.font; if (! font) { font = XLoadFont (dpy, 0); gc->gcv.font = font; [gc->gcv.font->nsfont retain]; CFRetain (gc->gcv.font->nsfont); // needed for garbage collection? } CGContextSelectFont (cgc, font->ps_name, font->size, kCGEncodingMacRoman); CGContextSetTextMatrix (cgc, CGAffineTransformIdentity); } static int draw_string (Display *dpy, Drawable d, GC gc, int x, int y, const char *str, int len, BOOL clear_background_p) { if (clear_background_p) { int ascent, descent, dir; XCharStruct cs; XTextExtents (&gc->gcv.font->metrics, str, len, &dir, &ascent, &descent, &cs); draw_rect (dpy, d, gc, x + MIN (0, cs.lbearing), y - MAX (0, ascent), MAX (MAX (0, cs.rbearing) - MIN (0, cs.lbearing), cs.width), MAX (0, ascent) + MAX (0, descent), NO, YES); } CGRect wr = d->frame; # if 1 /* The Quartz way is probably faster, but doesn't draw Latin1 properly. But the Cocoa way only works on NSView, not on CGContextRef (pixmaps)! */ CGContextRef cgc = d->cgc; push_fg_gc (d, gc, YES); set_font (dpy, cgc, gc); CGContextSetTextDrawingMode (cgc, kCGTextFill); if (gc->gcv.antialias_p) CGContextSetShouldAntialias (cgc, YES); CGContextShowTextAtPoint (cgc, wr.origin.x + x, wr.origin.y + wr.size.height - y, str, len); pop_gc (d, gc); # else /* !0 */ /* The Cocoa way... */ unsigned long argb = gc->gcv.foreground; if (gc->depth == 1) argb = (argb ? WhitePixel(dpy,0) : BlackPixel(dpy,0)); float a = ((argb >> 24) & 0xFF) / 255.0; float r = ((argb >> 16) & 0xFF) / 255.0; float g = ((argb >> 8) & 0xFF) / 255.0; float b = ((argb ) & 0xFF) / 255.0; NSColor *fg = [NSColor colorWithDeviceRed:r green:g blue:b alpha:a]; NSDictionary *attr = [NSDictionary dictionaryWithObjectsAndKeys: gc->gcv.font->nsfont, NSFontAttributeName, fg, NSForegroundColorAttributeName, nil]; char *s2 = (char *) malloc (len + 1); strncpy (s2, str, len); s2[len] = 0; NSString *nsstr = [NSString stringWithCString:s2 encoding:NSISOLatin1StringEncoding]; free (s2); NSPoint pos; pos.x = wr.origin.x + x; pos.y = wr.origin.y + wr.size.height - y - gc->gcv.font->metrics.descent; [nsstr drawAtPoint:pos withAttributes:attr]; # endif /* 0 */ invalidate_drawable_cache (d); return 0; } int XDrawString (Display *dpy, Drawable d, GC gc, int x, int y, const char *str, int len) { return draw_string (dpy, d, gc, x, y, str, len, NO); } int XDrawImageString (Display *dpy, Drawable d, GC gc, int x, int y, const char *str, int len) { return draw_string (dpy, d, gc, x, y, str, len, YES); } int XSetForeground (Display *dpy, GC gc, unsigned long fg) { validate_pixel (fg, gc->depth, gc->gcv.alpha_allowed_p); gc->gcv.foreground = fg; return 0; } int XSetBackground (Display *dpy, GC gc, unsigned long bg) { validate_pixel (bg, gc->depth, gc->gcv.alpha_allowed_p); gc->gcv.background = bg; return 0; } int jwxyz_XSetAlphaAllowed (Display *dpy, GC gc, Bool allowed) { gc->gcv.alpha_allowed_p = allowed; return 0; } int jwxyz_XSetAntiAliasing (Display *dpy, GC gc, Bool antialias_p) { gc->gcv.antialias_p = antialias_p; return 0; } int XSetLineAttributes (Display *dpy, GC gc, unsigned int line_width, int line_style, int cap_style, int join_style) { gc->gcv.line_width = line_width; Assert (line_style == LineSolid, "only LineSolid implemented"); // gc->gcv.line_style = line_style; gc->gcv.cap_style = cap_style; gc->gcv.join_style = join_style; return 0; } int XSetGraphicsExposures (Display *dpy, GC gc, Bool which) { return 0; } int XSetFunction (Display *dpy, GC gc, int which) { gc->gcv.function = which; return 0; } int XSetSubwindowMode (Display *dpy, GC gc, int which) { gc->gcv.subwindow_mode = which; return 0; } int XSetClipMask (Display *dpy, GC gc, Pixmap m) { Assert (!!gc->gcv.clip_mask == !!gc->clip_mask, "GC clip mask mixup"); if (gc->gcv.clip_mask) { XFreePixmap (dpy, gc->gcv.clip_mask); CGImageRelease (gc->clip_mask); } gc->gcv.clip_mask = copy_pixmap (dpy, m); if (gc->gcv.clip_mask) gc->clip_mask = CGBitmapContextCreateImage (gc->gcv.clip_mask->cgc); else gc->clip_mask = 0; return 0; } int XSetClipOrigin (Display *dpy, GC gc, int x, int y) { gc->gcv.clip_x_origin = x; gc->gcv.clip_y_origin = y; return 0; } Bool 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 && w->type == WINDOW, "not a window"); # ifdef USE_IPHONE int x = w->window.last_mouse_x; int y = w->window.last_mouse_y; if (root_x_ret) *root_x_ret = x; if (root_y_ret) *root_y_ret = y; if (win_x_ret) *win_x_ret = x; if (win_y_ret) *win_y_ret = y; # else // !USE_IPHONE NSWindow *nsw = [w->window.view window]; NSPoint wpos; // get bottom left of window on screen, from bottom left wpos.x = wpos.y = 0; wpos = [nsw convertBaseToScreen:wpos]; NSPoint vpos; // get bottom left of view on window, from bottom left vpos.x = vpos.y = 0; vpos = [w->window.view convertPoint:vpos toView:[nsw contentView]]; // get bottom left of view on screen, from bottom left vpos.x += wpos.x; vpos.y += wpos.y; // get top left of view on screen, from bottom left vpos.y += w->frame.size.height; // get top left of view on screen, from top left NSArray *screens = [NSScreen screens]; NSScreen *screen = (screens && [screens count] > 0 ? [screens objectAtIndex:0] : [NSScreen mainScreen]); #ifdef USE_IPHONE double s = w->window.view.contentScaleFactor; #else int s = 1; #endif NSRect srect = [screen frame]; vpos.y = (s * srect.size.height) - vpos.y; // get the mouse position on window, from bottom left NSEvent *e = [NSApp currentEvent]; NSPoint p = [e locationInWindow]; // get mouse position on screen, from bottom left p.x += wpos.x; p.y += wpos.y; // get mouse position on screen, from top left p.y = srect.size.height - p.y; if (root_x_ret) *root_x_ret = (int) p.x; if (root_y_ret) *root_y_ret = (int) p.y; if (win_x_ret) *win_x_ret = (int) (p.x - vpos.x); if (win_y_ret) *win_y_ret = (int) (p.y - vpos.y); # endif // !USE_IPHONE if (mask_ret) *mask_ret = 0; // #### poll the keyboard modifiers? if (root_ret) *root_ret = 0; if (child_ret) *child_ret = 0; return True; } Bool XTranslateCoordinates (Display *dpy, Window w, Window dest_w, int src_x, int src_y, int *dest_x_ret, int *dest_y_ret, Window *child_ret) { Assert (w && w->type == WINDOW, "not a window"); # ifdef USE_IPHONE NSPoint p; p.x = src_x; p.y = src_y; # else // !USE_IPHONE NSWindow *nsw = [w->window.view window]; NSPoint wpos; // get bottom left of window on screen, from bottom left wpos.x = wpos.y = 0; wpos = [nsw convertBaseToScreen:wpos]; NSPoint vpos; // get bottom left of view on window, from bottom left vpos.x = vpos.y = 0; vpos = [w->window.view convertPoint:vpos toView:[nsw contentView]]; // get bottom left of view on screen, from bottom left vpos.x += wpos.x; vpos.y += wpos.y; // get top left of view on screen, from bottom left vpos.y += w->frame.size.height; // get top left of view on screen, from top left NSArray *screens = [NSScreen screens]; NSScreen *screen = (screens && [screens count] > 0 ? [screens objectAtIndex:0] : [NSScreen mainScreen]); # ifdef USE_IPHONE double s = w->window.view.contentScaleFactor; # else int s = 1; # endif NSRect srect = [screen frame]; vpos.y = (s * srect.size.height) - vpos.y; // point starts out relative to top left of view NSPoint p; p.x = src_x; p.y = src_y; // get point relative to top left of screen p.x += vpos.x; p.y += vpos.y; # endif // !USE_IPHONE *dest_x_ret = p.x; *dest_y_ret = p.y; if (child_ret) *child_ret = w; return True; } KeySym XKeycodeToKeysym (Display *dpy, KeyCode code, int index) { return code; } int XLookupString (XKeyEvent *e, char *buf, int size, KeySym *k_ret, XComposeStatus *xc) { KeySym ks = XKeycodeToKeysym (0, e->keycode, 0); char c = 0; // Do not put non-ASCII KeySyms like XK_Shift_L and XK_Page_Up in the string. if ((unsigned int) ks <= 255) c = (char) ks; // Put control characters in the string. Not meta. if (e->state & ControlMask) { if (c >= 'a' && c <= 'z') // Upcase control. c -= 'a'-'A'; if (c >= '@' && c <= '_') // Shift to control page. c -= '@'; if (c == ' ') // C-SPC is NULL. c = 0; } if (k_ret) *k_ret = ks; if (size > 0) buf[0] = c; if (size > 1) buf[1] = 0; return (size > 0 ? 1 : 0); } int XFlush (Display *dpy) { // Just let the event loop take care of this on its own schedule. return 0; } int XSync (Display *dpy, Bool flush) { return XFlush (dpy); } // declared in utils/visual.h int has_writable_cells (Screen *s, Visual *v) { return 0; } int visual_depth (Screen *s, Visual *v) { return 32; } int visual_cells (Screen *s, Visual *v) { return 0xFFFFFF; } int visual_class (Screen *s, Visual *v) { return TrueColor; } // declared in utils/grabclient.h Bool use_subwindow_mode_p (Screen *screen, Window window) { return False; }