summaryrefslogtreecommitdiffstats
path: root/jwxyz/jwxyz-cocoa.m
diff options
context:
space:
mode:
Diffstat (limited to 'jwxyz/jwxyz-cocoa.m')
-rw-r--r--jwxyz/jwxyz-cocoa.m1043
1 files changed, 1043 insertions, 0 deletions
diff --git a/jwxyz/jwxyz-cocoa.m b/jwxyz/jwxyz-cocoa.m
new file mode 100644
index 0000000..96d54b8
--- /dev/null
+++ b/jwxyz/jwxyz-cocoa.m
@@ -0,0 +1,1043 @@
+/* xscreensaver, Copyright (c) 1991-2018 Jamie Zawinski <jwz@jwz.org>
+ *
+ * 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 or OpenGL-ish things that bear some resemblance
+ to the things that Xlib might have done.
+
+ This code is used by both the original jwxyz.m and the new jwxyz-gl.c.
+ */
+
+#import "jwxyzI.h"
+#import "jwxyz-cocoa.h"
+#import "utf8wc.h"
+
+#include <stdarg.h>
+
+#ifdef USE_IPHONE
+# import <OpenGLES/ES1/gl.h>
+# import <OpenGLES/ES1/glext.h>
+# define NSOpenGLContext EAGLContext
+# define NSFont UIFont
+
+# define NSFontTraitMask UIFontDescriptorSymbolicTraits
+// The values for the flags for NSFontTraitMask and
+// UIFontDescriptorSymbolicTraits match up, not that it really matters here.
+# define NSBoldFontMask UIFontDescriptorTraitBold
+# define NSFixedPitchFontMask UIFontDescriptorTraitMonoSpace
+# define NSItalicFontMask UIFontDescriptorTraitItalic
+#endif
+
+#import <CoreText/CTLine.h>
+#import <CoreText/CTRun.h>
+
+#define VTBL JWXYZ_VTBL(dpy)
+
+/* OS X/iOS-specific JWXYZ implementation. */
+
+void
+jwxyz_logv (Bool error, const char *fmt, va_list args)
+{
+ vfprintf (stderr, fmt, args);
+ fputc ('\n', stderr);
+}
+
+/* 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];
+# undef abort
+ abort(); // not reached
+}
+
+
+const XRectangle *
+jwxyz_frame (Drawable d)
+{
+ return &d->frame;
+}
+
+
+unsigned int
+jwxyz_drawable_depth (Drawable d)
+{
+ return (d->type == WINDOW
+ ? visual_depth (NULL, NULL)
+ : d->pixmap.depth);
+}
+
+
+float
+jwxyz_scale (Window main_window)
+{
+ float scale = 1;
+
+# ifdef USE_IPHONE
+ /* Since iOS screens are physically smaller than desktop screens, scale up
+ the fonts to make them more readable.
+
+ Note that X11 apps on iOS also have the backbuffer sized in points
+ instead of pixels, resulting in an effective X11 screen size of 768x1024
+ or so, even if the display has significantly higher resolution. That is
+ unrelated to this hack, which is really about DPI.
+ */
+ scale = main_window->window.view.hackedContentScaleFactor;
+ if (scale < 1) // iPad Pro magnifies the backbuffer by 3x, which makes text
+ scale = 1; // excessively blurry in BSOD.
+
+# else // !USE_IPHONE
+
+ /* Desktop retina displays also need fonts doubled. */
+ scale = main_window->window.view.hackedContentScaleFactor;
+
+# endif // !USE_IPHONE
+
+ return scale;
+}
+
+
+/* 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 positive. For superscripts, 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.
+ */
+static void
+utf8_metrics (Display *dpy, NSFont *nsfont, NSString *nsstr, XCharStruct *cs)
+{
+ // Returns the metrics of the multi-character, single-line UTF8 string.
+
+ Drawable d = XRootWindow (dpy, 0);
+
+ CGContextRef cgc = d->cgc;
+ NSDictionary *attr =
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ nsfont, NSFontAttributeName,
+ nil];
+ NSAttributedString *astr = [[NSAttributedString alloc]
+ initWithString:nsstr
+ attributes:attr];
+ CTLineRef ctline = CTLineCreateWithAttributedString (
+ (__bridge CFAttributedStringRef) astr);
+ CGContextSetTextPosition (cgc, 0, 0);
+ CGContextSetShouldAntialias (cgc, True); // #### Guess?
+
+ memset (cs, 0, sizeof(*cs));
+
+ // "CTRun represents set of consecutive glyphs sharing the same
+ // attributes and direction".
+ //
+ // We also get multiple runs any time font subsitution happens:
+ // E.g., if the current font is Verdana-Bold, a &larr; character
+ // in the NSString will actually be rendered in LucidaGrande-Bold.
+ //
+ int count = 0;
+ for (id runid in (NSArray *)CTLineGetGlyphRuns(ctline)) {
+ CTRunRef run = (CTRunRef) runid;
+ CFRange r = { 0, };
+ CGRect bbox = CTRunGetImageBounds (run, cgc, r);
+ CGFloat ascent, descent, leading;
+ CGFloat advancement =
+ CTRunGetTypographicBounds (run, r, &ascent, &descent, &leading);
+
+# ifndef USE_IPHONE
+ // Only necessary for when LCD smoothing is enabled, which iOS doesn't do.
+ bbox.origin.x -= 2.0/3.0;
+ bbox.size.width += 4.0/3.0;
+ bbox.size.height += 1.0/2.0;
+# endif
+
+ // Create the metrics for this run:
+ XCharStruct cc;
+ cc.ascent = ceil (bbox.origin.y + bbox.size.height);
+ cc.descent = ceil (-bbox.origin.y);
+ cc.lbearing = floor (bbox.origin.x);
+ cc.rbearing = ceil (bbox.origin.x + bbox.size.width);
+ cc.width = floor (advancement + 0.5);
+
+ // Add those metrics into the cumulative metrics:
+ if (count == 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 = MAX (cs->width, cs->width + cc.width);
+ }
+
+ // Why no y? What about vertical text?
+ // XCharStruct doesn't encapsulate that but XGlyphInfo does.
+
+ count++;
+ }
+
+ [astr release];
+ CFRelease (ctline);
+}
+
+
+static NSArray *
+font_family_members (NSString *family_name)
+{
+# ifndef USE_IPHONE
+ return [[NSFontManager sharedFontManager]
+ availableMembersOfFontFamily:family_name];
+# else
+ return [UIFont fontNamesForFamilyName:family_name];
+# endif
+}
+
+
+const char *
+jwxyz_default_font_family (int require)
+{
+ return require & JWXYZ_STYLE_MONOSPACE ? "Courier" : "Verdana";
+}
+
+
+static NSFontTraitMask
+nsfonttraitmask_for (int font_style)
+{
+ NSFontTraitMask result = 0;
+ if (font_style & JWXYZ_STYLE_BOLD)
+ result |= NSBoldFontMask;
+ if (font_style & JWXYZ_STYLE_ITALIC)
+ result |= NSItalicFontMask;
+ if (font_style & JWXYZ_STYLE_MONOSPACE)
+ result |= NSFixedPitchFontMask;
+ return result;
+}
+
+
+static NSFont *
+try_font (NSFontTraitMask traits, NSFontTraitMask mask,
+ NSString *family_name, float size)
+{
+ NSArray *family_members = font_family_members (family_name);
+ if (!family_members.count) {
+ family_members = font_family_members (
+ [NSString stringWithUTF8String:jwxyz_default_font_family (
+ traits & NSFixedPitchFontMask ? JWXYZ_STYLE_MONOSPACE : 0)]);
+ }
+
+# ifndef USE_IPHONE
+ for (unsigned k = 0; k != family_members.count; ++k) {
+
+ NSArray *member = [family_members objectAtIndex:k];
+ NSFontTraitMask font_mask =
+ [(NSNumber *)[member objectAtIndex:3] unsignedIntValue];
+
+ if ((font_mask & mask) == traits) {
+
+ NSString *name = [member objectAtIndex:0];
+ NSFont *f = [NSFont fontWithName:name size:size];
+ if (!f)
+ break;
+
+ /* 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);
+ break;
+ }
+ // NSLog(@"using \"%@\": %d", name, enc);
+
+ return f;
+ }
+ }
+# else // USE_IPHONE
+
+ // This trick needs iOS 3.1, see "Using SDK-Based Development".
+ Class has_font_descriptor = [UIFontDescriptor class];
+
+ for (NSString *fn in family_members) {
+# define MATCH(X) \
+ ([fn rangeOfString:X options:NSCaseInsensitiveSearch].location \
+ != NSNotFound)
+
+ NSFontTraitMask font_mask;
+ if (has_font_descriptor) {
+ // This only works on iOS 7 and later.
+ font_mask = [[UIFontDescriptor
+ fontDescriptorWithFontAttributes:
+ @{UIFontDescriptorNameAttribute:fn}]
+ symbolicTraits];
+ } else {
+ font_mask = 0;
+ if (MATCH(@"Bold"))
+ font_mask |= NSBoldFontMask;
+ if (MATCH(@"Italic") || MATCH(@"Oblique"))
+ font_mask |= NSItalicFontMask;
+ if (MATCH(@"Courier"))
+ font_mask |= NSFixedPitchFontMask;
+ }
+
+ if ((font_mask & mask) == traits) {
+
+ /* 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
+ )
+ break;
+
+ return [UIFont fontWithName:fn size:size];
+ }
+# undef MATCH
+ }
+# endif
+
+ return NULL;
+}
+
+
+/* Returns a random font in the given size and face.
+ */
+static NSFont *
+random_font (NSFontTraitMask traits, NSFontTraitMask mask, float size)
+{
+
+# ifndef USE_IPHONE
+ // Providing Unbold or Unitalic in the mask for availableFontNamesWithTraits
+ // returns an empty list, at least on a system with default fonts only.
+ NSArray *families = [[NSFontManager sharedFontManager]
+ availableFontFamilies];
+ if (!families) return 0;
+# else
+ NSArray *families = [UIFont familyNames];
+
+ // There are many dups in the families array -- uniquify it.
+ {
+ NSArray *sorted_families =
+ [families sortedArrayUsingSelector:@selector(compare:)];
+ NSMutableArray *new_families =
+ [NSMutableArray arrayWithCapacity:sorted_families.count];
+
+ NSString *prev_family = @"";
+ for (NSString *family in sorted_families) {
+ if ([family compare:prev_family])
+ [new_families addObject:family];
+ prev_family = family;
+ }
+
+ families = new_families;
+ }
+# endif // USE_IPHONE
+
+ long n = [families count];
+ if (n <= 0) return 0;
+
+ int j;
+ for (j = 0; j < n; j++) {
+ int i = random() % n;
+ NSString *family_name = [families objectAtIndex:i];
+
+ NSFont *result = try_font (traits, mask, family_name, size);
+ if (result)
+ return result;
+ }
+
+ // None of the fonts support ASCII?
+ return 0;
+}
+
+void *
+jwxyz_load_native_font (Window main_window, int traits_jwxyz, int mask_jwxyz,
+ const char *font_name_ptr, size_t font_name_length,
+ int font_name_type, float size,
+ char **family_name_ret,
+ int *ascent_ret, int *descent_ret)
+{
+ NSFont *nsfont = NULL;
+
+ NSFontTraitMask
+ traits = nsfonttraitmask_for (traits_jwxyz),
+ mask = nsfonttraitmask_for (mask_jwxyz);
+
+ NSString *font_name = font_name_type != JWXYZ_FONT_RANDOM ?
+ [[NSString alloc] initWithBytes:font_name_ptr
+ length:font_name_length
+ encoding:NSUTF8StringEncoding] :
+ nil;
+
+ size *= jwxyz_scale (main_window);
+
+ if (font_name_type == JWXYZ_FONT_RANDOM) {
+
+ nsfont = random_font (traits, mask, size);
+ [font_name release];
+
+ } else if (font_name_type == JWXYZ_FONT_FACE) {
+
+ nsfont = [NSFont fontWithName:font_name size:size];
+
+ } else if (font_name_type == JWXYZ_FONT_FAMILY) {
+
+ Assert (size > 0, "zero font size");
+
+ if (!nsfont)
+ nsfont = try_font (traits, mask, font_name, size);
+
+ // if that didn't work, turn off attibutes until it does
+ // (e.g., there is no "Monaco-Bold".)
+ //
+ if (!nsfont && (mask & NSItalicFontMask)) {
+ traits &= ~NSItalicFontMask;
+ mask &= ~NSItalicFontMask;
+ nsfont = try_font (traits, mask, font_name, size);
+ }
+ if (!nsfont && (mask & NSBoldFontMask)) {
+ traits &= ~NSBoldFontMask;
+ mask &= ~NSBoldFontMask;
+ nsfont = try_font (traits, mask, font_name, size);
+ }
+ if (!nsfont && (mask & NSFixedPitchFontMask)) {
+ traits &= ~NSFixedPitchFontMask;
+ mask &= ~NSFixedPitchFontMask;
+ nsfont = try_font (traits, mask, font_name, size);
+ }
+ }
+
+ [font_name release];
+
+ if (nsfont)
+ {
+ if (family_name_ret)
+ *family_name_ret = strdup (nsfont.familyName.UTF8String);
+
+ CFRetain (nsfont); // needed for garbage collection?
+
+ *ascent_ret = ceil ([nsfont ascender]);
+ *descent_ret = -floor ([nsfont descender]);
+
+ Assert([nsfont fontName], "broken NSFont in fid");
+ }
+
+ return nsfont;
+}
+
+
+void
+jwxyz_release_native_font (Display *dpy, void *native_font)
+{
+ // #### 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);
+}
+
+
+// Given a UTF8 string, return an NSString. Bogus UTF8 characters are ignored.
+// We have to do this because stringWithCString returns NULL if there are
+// any invalid characters at all.
+//
+static NSString *
+sanitize_utf8 (const char *in, size_t in_len, Bool *latin1_pP)
+{
+ size_t out_len = in_len * 4; // length of string might increase
+ char *s2 = (char *) malloc (out_len);
+ char *out = s2;
+ const char *in_end = in + in_len;
+ const char *out_end = out + out_len;
+ Bool latin1_p = True;
+
+ while (in < in_end)
+ {
+ unsigned long uc;
+ long L1 = utf8_decode ((const unsigned char *) in, in_end - in, &uc);
+ long L2 = utf8_encode (uc, out, out_end - out);
+ in += L1;
+ out += L2;
+ if (uc > 255) latin1_p = False;
+ }
+ *out = 0;
+ NSString *nsstr =
+ [NSString stringWithCString:s2 encoding:NSUTF8StringEncoding];
+ free (s2);
+ if (latin1_pP) *latin1_pP = latin1_p;
+ return (nsstr ? nsstr : @"");
+}
+
+
+NSString *
+nsstring_from(const char *str, size_t len, int utf8_p)
+{
+ Bool latin1_p;
+ NSString *nsstr = utf8_p ?
+ sanitize_utf8 (str, len, &latin1_p) :
+ [[[NSString alloc] initWithBytes:str
+ length:len
+ encoding:NSISOLatin1StringEncoding]
+ autorelease];
+ return nsstr;
+}
+
+void
+jwxyz_render_text (Display *dpy, void *native_font,
+ const char *str, size_t len, Bool utf8_p, Bool antialias_p,
+ XCharStruct *cs_ret, char **pixmap_ret)
+{
+ utf8_metrics (dpy, (NSFont *)native_font, nsstring_from (str, len, utf8_p),
+ cs_ret);
+
+ Assert (!pixmap_ret, "TODO");
+}
+
+
+void
+jwxyz_get_pos (Window w, XPoint *xvpos, XPoint *xp)
+{
+# ifdef USE_IPHONE
+
+ xvpos->x = 0;
+ xvpos->y = 0;
+
+ if (xp) {
+ xp->x = w->window.last_mouse_x;
+ xp->y = w->window.last_mouse_y;
+ }
+
+# else // !USE_IPHONE
+
+ NSWindow *nsw = [w->window.view window];
+
+ // get bottom left of window on screen, from bottom left
+
+# if (MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_6)
+ NSRect rr1 = [w->window.view convertRect: NSMakeRect(0,0,0,0) toView:nil];
+ NSRect rr2 = [nsw convertRectToScreen: rr1];
+
+ NSPoint wpos = NSMakePoint (rr2.origin.x - rr1.origin.x,
+ rr2.origin.y - rr1.origin.y);
+# else
+ // deprecated as of 10.7
+ NSPoint wpos = [nsw convertBaseToScreen: NSMakePoint(0,0)];
+# endif
+
+
+ // get bottom left of view on window, from bottom left
+ NSPoint vpos;
+ 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
+ double s = [w->window.view hackedContentScaleFactor];
+ vpos.y += w->frame.height / s;
+
+ // 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]);
+ NSRect srect = [screen frame];
+ vpos.y = srect.size.height - vpos.y;
+
+ xvpos->x = vpos.x;
+ xvpos->y = vpos.y;
+
+ if (xp) {
+ // 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;
+
+ xp->x = (int) p.x;
+ xp->y = (int) p.y;
+ }
+
+# endif // !USE_IPHONE
+}
+
+
+#ifdef USE_IPHONE
+
+void
+check_framebuffer_status (void)
+{
+ int err = glCheckFramebufferStatusOES (GL_FRAMEBUFFER_OES);
+ switch (err) {
+ case GL_FRAMEBUFFER_COMPLETE_OES:
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_OES:
+ Assert (0, "framebuffer incomplete attachment");
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_OES:
+ Assert (0, "framebuffer incomplete missing attachment");
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_OES:
+ Assert (0, "framebuffer incomplete dimensions");
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_OES:
+ Assert (0, "framebuffer incomplete formats");
+ break;
+ case GL_FRAMEBUFFER_UNSUPPORTED_OES:
+ Assert (0, "framebuffer unsupported");
+ break;
+/*
+ case GL_FRAMEBUFFER_UNDEFINED:
+ Assert (0, "framebuffer undefined");
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
+ Assert (0, "framebuffer incomplete draw buffer");
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
+ Assert (0, "framebuffer incomplete read buffer");
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
+ Assert (0, "framebuffer incomplete multisample");
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS:
+ Assert (0, "framebuffer incomplete layer targets");
+ break;
+ */
+ default:
+ NSCAssert (0, @"framebuffer incomplete, unknown error 0x%04X", err);
+ break;
+ }
+}
+
+
+void
+create_framebuffer (GLuint *gl_framebuffer, GLuint *gl_renderbuffer)
+{
+ glGenFramebuffersOES (1, gl_framebuffer);
+ glBindFramebufferOES (GL_FRAMEBUFFER_OES, *gl_framebuffer);
+
+ glGenRenderbuffersOES (1, gl_renderbuffer);
+ glBindRenderbufferOES (GL_RENDERBUFFER_OES, *gl_renderbuffer);
+}
+
+#endif // USE_IPHONE
+
+
+#if defined JWXYZ_QUARTZ
+
+/* Pushes a GC context; sets Fill and Stroke colors to the background color.
+ */
+static void
+push_bg_gc (Display *dpy, Drawable d, GC gc, Bool fill_p)
+{
+ XGCValues *gcv = VTBL->gc_gcv (gc);
+ push_color_gc (dpy, d, gc, gcv->background, gcv->antialias_p, fill_p);
+}
+
+
+void
+jwxyz_quartz_copy_area (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)
+{
+ XRectangle src_frame = src->frame, dst_frame = dst->frame;
+ XGCValues *gcv = VTBL->gc_gcv (gc);
+
+ BOOL mask_p = src->type == PIXMAP && src->pixmap.depth == 1;
+
+
+ /* 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 (gcv->function == GXcopy &&
+ !gcv->clip_mask &&
+ jwxyz_drawable_depth (src) == jwxyz_drawable_depth (dst)) {
+
+ Assert(!src_frame.x &&
+ !src_frame.y &&
+ !dst_frame.x &&
+ !dst_frame.y,
+ "unexpected non-zero origin");
+
+ jwxyz_blit (CGBitmapContextGetData (src->cgc),
+ CGBitmapContextGetBytesPerRow (src->cgc), src_x, src_y,
+ CGBitmapContextGetData (dst->cgc),
+ CGBitmapContextGetBytesPerRow (dst->cgc), dst_x, dst_y,
+ width, height);
+
+ } else {
+ CGRect
+ src_rect = CGRectMake(src_x, src_y, width, height),
+ dst_rect = CGRectMake(dst_x, dst_y, width, height);
+
+ src_rect.origin = map_point (src, src_rect.origin.x,
+ src_rect.origin.y + src_rect.size.height);
+ dst_rect.origin = map_point (dst, dst_rect.origin.x,
+ dst_rect.origin.y + src_rect.size.height);
+
+ NSObject *releaseme = 0;
+ CGImageRef cgi;
+ BOOL free_cgi_p = NO;
+
+ // We must first copy the bits to an intermediary CGImage object, then
+ // copy that to the destination drawable's CGContext.
+ //
+ // 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.x ||
+ src_rect.origin.y != src_frame.y ||
+ src_rect.size.width != src_frame.width ||
+ src_rect.size.height != src_frame.height) {
+ // #### I don't understand why this is needed...
+ src_rect.origin.y = (src_frame.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;
+ }
+
+ CGContextRef cgc = dst->cgc;
+
+ if (mask_p) { // src depth == 1
+
+ push_bg_gc (dpy, dst, gc, YES);
+
+ // fill the destination rectangle with solid background...
+ CGContextFillRect (cgc, 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 (dpy, cgc, gcv->foreground, VTBL->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);
+
+ // copy the CGImage onto the destination CGContext
+ //Assert(CGImageGetColorSpace(cgi) == dpy->colorspace, "bad colorspace");
+ CGContextDrawImage (cgc, dst_rect, cgi);
+
+ pop_gc (dst, gc);
+ }
+
+ if (free_cgi_p) CGImageRelease (cgi);
+
+ if (releaseme) [releaseme release];
+ }
+
+ invalidate_drawable_cache (dst);
+}
+
+#elif defined JWXYZ_GL
+
+/* Warning! The JWXYZ_GL code here is experimental and provisional and not at
+ all ready for prime time. Please be careful.
+ */
+
+void
+jwxyz_copy_area (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)
+{
+ /* TODO:
+ - glCopyPixels if src == dst
+ - Pixel buffer copying
+ - APPLE_framebuffer_multisample has ResolveMultisampleFramebufferAPPLE,
+ which is like a blit.
+ */
+
+ /* Strange and ugly flickering when going the glCopyTexImage2D route on
+ OS X. (Early 2009 Mac mini, OS X 10.10)
+ */
+# ifdef USE_IPHONE
+
+ /* TODO: This might not still work. */
+ jwxyz_bind_drawable (dpy, dpy->main_window, src);
+ jwxyz_gl_copy_area_read_tex_image (dpy, jwxyz_frame (src)->height,
+ src_x, src_y, width, height, dst_x, dst_y);
+ jwxyz_bind_drawable (dpy, dpy->main_window, dst);
+ jwxyz_gl_copy_area_write_tex_image (dpy, gc, src_x, src_y,
+ width, height, dst_x, dst_y);
+# else // !USE_IPHONE
+ jwxyz_gl_copy_area_read_pixels (dpy, src, dst, gc,
+ src_x, src_y, width, height, dst_x, dst_y);
+# endif // !USE_IPHONE
+ jwxyz_assert_gl ();
+}
+
+
+void
+jwxyz_assert_gl ()
+{
+ // This is like check_gl_error, except this happens for debug builds only.
+#ifndef __OPTIMIZE__
+ if([NSOpenGLContext currentContext])
+ {
+ // glFinish here drops FPS into the toilet. It might need to be on if
+ // something goes wrong.
+ // glFinish();
+ GLenum error = glGetError();
+ Assert (!error, "jwxyz_assert_gl: OpenGL error");
+ }
+#endif // !__OPTIMIZE__
+}
+
+void
+jwxyz_assert_drawable (Window main_window, Drawable d)
+{
+#if !defined USE_IPHONE && !defined __OPTIMIZE__
+ XScreenSaverView *view = main_window->window.view;
+ NSOpenGLContext *ogl_ctx = [view oglContext];
+
+ if (d->type == WINDOW) {
+ Assert([ogl_ctx view] == view,
+ "jwxyz_assert_display: ogl_ctx view not set!");
+ }
+
+ @try {
+ /* Assert([d->ctx isKindOfClass:[NSOpenGLContext class]], "Not a context."); */
+ Class c = [ogl_ctx class];
+ Assert([c isSubclassOfClass:[NSOpenGLContext class]], "Not a context.");
+ // [d->ctx makeCurrentContext];
+ }
+ @catch (NSException *exception) {
+ perror([[exception reason] UTF8String]);
+ jwxyz_abort();
+ }
+#endif // !USE_IPHONE && !__OPTIMIZE__
+}
+
+
+void
+jwxyz_bind_drawable (Window main_window, Drawable d)
+{
+ /* Windows and Pixmaps need to use different contexts with OpenGL
+ screenhacks, because an OpenGL screenhack sets state in an arbitrary
+ fashion, but jwxyz-gl.c has its own ideas w.r.t. OpenGL state.
+
+ On iOS, all pixmaps can use the same context with different FBOs. Which
+ is nice.
+ */
+
+ /* OpenGL screenhacks in general won't be drawing on the Window, but they
+ can and will draw on a Pixmap -- but an OpenGL call following an Xlib
+ call won't be able to fix the fact that it's drawing offscreen.
+ */
+
+ /* EXT_direct_state_access might be appropriate, but it's apparently not
+ available on Mac OS X.
+ */
+
+ // jwxyz_assert_display (dpy);
+ jwxyz_assert_drawable (main_window, main_window);
+ jwxyz_assert_gl ();
+ jwxyz_assert_drawable (main_window, d);
+
+#if defined USE_IPHONE && !defined __OPTIMIZE__
+ Drawable current_drawable = main_window->window.current_drawable;
+ Assert (!current_drawable
+ || current_drawable->ogl_ctx == [EAGLContext currentContext],
+ "bind_drawable: Wrong context.");
+ if (current_drawable) {
+ GLint framebuffer;
+ glGetIntegerv (GL_FRAMEBUFFER_BINDING_OES, &framebuffer);
+ Assert (framebuffer == current_drawable->gl_framebuffer,
+ "bind_drawable: Wrong framebuffer.");
+ }
+#endif
+
+ if (main_window->window.current_drawable != d) {
+ main_window->window.current_drawable = d;
+
+ /* Doing this repeatedly is probably not OK performance-wise. Probably. */
+#ifndef USE_IPHONE
+ [d->ogl_ctx makeCurrentContext];
+#else
+ [EAGLContext setCurrentContext:d->ogl_ctx];
+ glBindFramebufferOES(GL_FRAMEBUFFER_OES, d->gl_framebuffer);
+ if (d->type == PIXMAP) {
+ glViewport (0, 0, d->frame.width, d->frame.height);
+ jwxyz_set_matrices (d->frame.width, d->frame.height);
+ }
+#endif
+ }
+
+ jwxyz_assert_gl ();
+}
+
+
+Pixmap
+XCreatePixmap (Display *dpy, Drawable d,
+ unsigned int width, unsigned int height, unsigned int depth)
+{
+ Pixmap p = (Pixmap) calloc (1, sizeof(*p));
+ p->type = PIXMAP;
+ p->frame.width = width;
+ p->frame.height = height;
+ p->pixmap.depth = depth;
+
+ Assert (depth == 1 || depth == 32, "XCreatePixmap: depth must be 32");
+
+ /* TODO: If Pixel buffers are not supported, do something about it. */
+ Window w = XRootWindow (dpy, 0);
+
+# ifndef USE_IPHONE
+
+ p->ogl_ctx = [[NSOpenGLContext alloc]
+ initWithFormat:w->window.pixfmt
+ shareContext:w->ogl_ctx];
+ CFRetain (p->ogl_ctx);
+
+ [p->ogl_ctx makeCurrentContext]; // This is indeed necessary.
+
+ p->pixmap.gl_pbuffer = [[NSOpenGLPixelBuffer alloc]
+ /* TODO: Only if there are rectangluar textures. */
+ initWithTextureTarget:GL_TEXTURE_RECTANGLE_EXT
+ /* TODO: Make sure GL_RGBA isn't better. */
+ textureInternalFormat:GL_RGB
+ textureMaxMipMapLevel:0
+ pixelsWide:width
+ pixelsHigh:height];
+ CFRetain (p->pixmap.gl_pbuffer);
+
+ [p->ogl_ctx setPixelBuffer:p->pixmap.gl_pbuffer
+ cubeMapFace:0
+ mipMapLevel:0
+ currentVirtualScreen:w->window.virtual_screen];
+
+# else // USE_IPHONE
+
+ p->ogl_ctx = w->window.ogl_ctx_pixmap;
+
+ [EAGLContext setCurrentContext:p->ogl_ctx];
+ create_framebuffer (&p->gl_framebuffer, &p->gl_renderbuffer);
+
+ glRenderbufferStorageOES (GL_RENDERBUFFER_OES, GL_RGBA8_OES, width, height);
+ glFramebufferRenderbufferOES (GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES,
+ GL_RENDERBUFFER_OES, p->gl_renderbuffer);
+
+ check_framebuffer_status ();
+
+ glBindFramebufferOES(GL_FRAMEBUFFER_OES, p->gl_framebuffer);
+
+# endif // USE_IPHONE
+
+ w->window.current_drawable = p;
+ glViewport (0, 0, width, height);
+ jwxyz_set_matrices (width, height);
+
+# ifndef __OPTIMIZE__
+ glClearColor (frand(1), frand(1), frand(1), 0);
+ glClear (GL_COLOR_BUFFER_BIT);
+# endif
+
+ return p;
+}
+
+int
+XFreePixmap (Display *d, Pixmap p)
+{
+ Assert (p && p->type == PIXMAP, "not a pixmap");
+
+ Window w = RootWindow (d, 0);
+
+# ifndef USE_IPHONE
+ CFRelease (p->ogl_ctx);
+ [p->ogl_ctx release];
+
+ CFRelease (p->pixmap.gl_pbuffer);
+ [p->pixmap.gl_pbuffer release];
+# else // USE_IPHONE
+ glDeleteRenderbuffersOES (1, &p->gl_renderbuffer);
+ glDeleteFramebuffersOES (1, &p->gl_framebuffer);
+# endif // USE_IPHONE
+
+ if (w->window.current_drawable == p) {
+ w->window.current_drawable = NULL;
+ }
+
+ free (p);
+ return 0;
+}
+
+#endif // JWXYZ_GL