/*
    SDL - Simple DirectMedia Layer
    Copyright (C) 1997-2012  Sam Lantinga

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public
    License along with this library; if not, write to the Free
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

    Sam Lantinga
    slouken@libsdl.org
*/
#include "SDL_config.h"

#include "SDL_QuartzVideo.h"
#include "SDL_QuartzWindow.h"

/* These APIs aren't just deprecated; they're gone from the headers in the
   10.7 SDK. If we're using a >= 10.7 SDK, but targeting < 10.7, then we
   force these function declarations. */
#if ((MAC_OS_X_VERSION_MIN_REQUIRED < 1070) && (MAC_OS_X_VERSION_MAX_ALLOWED >= 1070))
CG_EXTERN void *CGDisplayBaseAddress(CGDirectDisplayID display)
  CG_AVAILABLE_BUT_DEPRECATED(__MAC_10_0, __MAC_10_6,
    __IPHONE_NA, __IPHONE_NA);
CG_EXTERN size_t CGDisplayBytesPerRow(CGDirectDisplayID display)
  CG_AVAILABLE_BUT_DEPRECATED(__MAC_10_0, __MAC_10_6,
    __IPHONE_NA, __IPHONE_NA);
#endif


static inline BOOL IS_LION_OR_LATER(_THIS)
{
    return (system_version >= 0x1070);
}

static inline BOOL IS_SNOW_LEOPARD_OR_LATER(_THIS)
{
    return (system_version >= 0x1060);
}

#if (MAC_OS_X_VERSION_MAX_ALLOWED < 1060) && !defined(__LP64__)  /* Fixed in Snow Leopard */
/*
    Add methods to get at private members of NSScreen. 
    Since there is a bug in Apple's screen switching code
    that does not update this variable when switching
    to fullscreen, we'll set it manually (but only for the
    main screen).
*/
@interface NSScreen (NSScreenAccess)
- (void) setFrame:(NSRect)frame;
@end

@implementation NSScreen (NSScreenAccess)
- (void) setFrame:(NSRect)frame;
{
    _frame = frame;
}
@end
static inline void QZ_SetFrame(_THIS, NSScreen *nsscreen, NSRect frame)
{
    if (!IS_SNOW_LEOPARD_OR_LATER(this)) {
        [nsscreen setFrame:frame];
    }
}
#else
static inline void QZ_SetFrame(_THIS, NSScreen *nsscreen, NSRect frame)
{
}
#endif

@interface SDLTranslatorResponder : NSTextView
{
}
- (void) doCommandBySelector:(SEL)myselector;
@end

@implementation SDLTranslatorResponder
- (void) doCommandBySelector:(SEL) myselector {}
@end

/* absent in 10.3.9.  */
CG_EXTERN CGImageRef CGBitmapContextCreateImage (CGContextRef);

/* Bootstrap functions */
static int              QZ_Available ();
static SDL_VideoDevice* QZ_CreateDevice (int device_index);
static void             QZ_DeleteDevice (SDL_VideoDevice *device);

/* Initialization, Query, Setup, and Redrawing functions */
static int          QZ_VideoInit        (_THIS, SDL_PixelFormat *video_format);

static SDL_Rect**   QZ_ListModes        (_THIS, SDL_PixelFormat *format,
                                         Uint32 flags);
static void         QZ_UnsetVideoMode   (_THIS, BOOL to_desktop, BOOL save_gl);

static SDL_Surface* QZ_SetVideoMode     (_THIS, SDL_Surface *current,
                                         int width, int height, int bpp,
                                         Uint32 flags);
static int          QZ_ToggleFullScreen (_THIS, int on);
static int          QZ_SetColors        (_THIS, int first_color,
                                         int num_colors, SDL_Color *colors);

#if (MAC_OS_X_VERSION_MIN_REQUIRED < 1070)
static int          QZ_LockDoubleBuffer   (_THIS, SDL_Surface *surface);
static void         QZ_UnlockDoubleBuffer (_THIS, SDL_Surface *surface);
static int          QZ_ThreadFlip         (_THIS);
static int          QZ_FlipDoubleBuffer   (_THIS, SDL_Surface *surface);
static void         QZ_DoubleBufferUpdate (_THIS, int num_rects, SDL_Rect *rects);
static void         QZ_DirectUpdate     (_THIS, int num_rects, SDL_Rect *rects);
#endif

static void         QZ_UpdateRects      (_THIS, int num_rects, SDL_Rect *rects);
static void         QZ_VideoQuit        (_THIS);

static int  QZ_LockHWSurface(_THIS, SDL_Surface *surface);
static void QZ_UnlockHWSurface(_THIS, SDL_Surface *surface);
static int QZ_AllocHWSurface(_THIS, SDL_Surface *surface);
static void QZ_FreeHWSurface (_THIS, SDL_Surface *surface);

/* Bootstrap binding, enables entry point into the driver */
VideoBootStrap QZ_bootstrap = {
    "Quartz", "Mac OS X CoreGraphics", QZ_Available, QZ_CreateDevice
};

/* Disable compiler warnings we can't avoid. */
#if (defined(__GNUC__) && (__GNUC__ >= 4))
#  if (MAC_OS_X_VERSION_MAX_ALLOWED <= 1070)
#    pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#  endif
#endif

static void QZ_ReleaseDisplayMode(_THIS, const void *moderef)
{
    /* we only own these references in the 10.6+ API. */
#if (MAC_OS_X_VERSION_MAX_ALLOWED >= 1060)
    if (use_new_mode_apis) {
        CGDisplayModeRelease((CGDisplayModeRef) moderef);
    }
#endif
}

static void QZ_ReleaseDisplayModeList(_THIS, CFArrayRef mode_list)
{
    /* we only own these references in the 10.6+ API. */
#if (MAC_OS_X_VERSION_MAX_ALLOWED >= 1060)
    if (use_new_mode_apis) {
        CFRelease(mode_list);
    }
#endif
}


/* Bootstrap functions */
static int QZ_Available ()
{
    return 1;
}

static SDL_VideoDevice* QZ_CreateDevice (int device_index)
{
#pragma unused (device_index)

    SDL_VideoDevice *device;
    SDL_PrivateVideoData *hidden;

    device = (SDL_VideoDevice*) SDL_malloc (sizeof (*device) );
    hidden = (SDL_PrivateVideoData*) SDL_malloc (sizeof (*hidden) );

    if (device == NULL || hidden == NULL)
        SDL_OutOfMemory ();

    SDL_memset (device, 0, sizeof (*device) );
    SDL_memset (hidden, 0, sizeof (*hidden) );

    device->hidden = hidden;

    device->VideoInit        = QZ_VideoInit;
    device->ListModes        = QZ_ListModes;
    device->SetVideoMode     = QZ_SetVideoMode;
    device->ToggleFullScreen = QZ_ToggleFullScreen;
    device->UpdateMouse      = QZ_UpdateMouse;
    device->SetColors        = QZ_SetColors;
    /* device->UpdateRects      = QZ_UpdateRects; this is determined by SetVideoMode() */
    device->VideoQuit        = QZ_VideoQuit;

    device->LockHWSurface   = QZ_LockHWSurface;
    device->UnlockHWSurface = QZ_UnlockHWSurface;
    device->AllocHWSurface   = QZ_AllocHWSurface;
    device->FreeHWSurface   = QZ_FreeHWSurface;

    device->SetGamma     = QZ_SetGamma;
    device->GetGamma     = QZ_GetGamma;
    device->SetGammaRamp = QZ_SetGammaRamp;
    device->GetGammaRamp = QZ_GetGammaRamp;

    device->GL_GetProcAddress = QZ_GL_GetProcAddress;
    device->GL_GetAttribute   = QZ_GL_GetAttribute;
    device->GL_MakeCurrent    = QZ_GL_MakeCurrent;
    device->GL_SwapBuffers    = QZ_GL_SwapBuffers;
    device->GL_LoadLibrary    = QZ_GL_LoadLibrary;

    device->FreeWMCursor   = QZ_FreeWMCursor;
    device->CreateWMCursor = QZ_CreateWMCursor;
    device->ShowWMCursor   = QZ_ShowWMCursor;
    device->WarpWMCursor   = QZ_WarpWMCursor;
    device->MoveWMCursor   = QZ_MoveWMCursor;
    device->CheckMouseMode = QZ_CheckMouseMode;
    device->InitOSKeymap   = QZ_InitOSKeymap;
    device->PumpEvents     = QZ_PumpEvents;

    device->SetCaption    = QZ_SetCaption;
    device->SetIcon       = QZ_SetIcon;
    device->IconifyWindow = QZ_IconifyWindow;
    /*device->GetWMInfo     = QZ_GetWMInfo;*/
    device->GrabInput     = QZ_GrabInput;

    /*
     * This is a big hassle, needing QuickDraw and QuickTime on older
     *  systems, and god knows what on 10.6, so we immediately fail here,
     *  which causes SDL to make an RGB surface and manage the YUV overlay
     *  in software. Sorry. Use SDL 1.3 if you want YUV rendering in a pixel
     *  shader.  :)
     */
    /*device->CreateYUVOverlay = QZ_CreateYUVOverlay;*/

    device->free             = QZ_DeleteDevice;

    return device;
}

static void QZ_DeleteDevice (SDL_VideoDevice *device)
{
    _THIS = device;
    QZ_ReleaseDisplayMode(this, save_mode);
    QZ_ReleaseDisplayMode(this, mode);
    SDL_free (device->hidden);
    SDL_free (device);
}

static void QZ_GetModeInfo(_THIS, const void *_mode, Uint32 *w, Uint32 *h, Uint32 *bpp)
{
    *w = *h = *bpp = 0;
    if (_mode == NULL) {
        return;
    }

#if (MAC_OS_X_VERSION_MAX_ALLOWED >= 1060)
    if (use_new_mode_apis) {
        CGDisplayModeRef vidmode = (CGDisplayModeRef) _mode;
        CFStringRef fmt = CGDisplayModeCopyPixelEncoding(vidmode);

        *w = (Uint32) CGDisplayModeGetWidth(vidmode);
        *h = (Uint32) CGDisplayModeGetHeight(vidmode);

        /* we only care about the 32-bit modes... */
        if (CFStringCompare(fmt, CFSTR(IO32BitDirectPixels),
                            kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
            *bpp = 32;
        }

        CFRelease(fmt);
    }
#endif

#if (MAC_OS_X_VERSION_MIN_REQUIRED < 1060)
    if (!use_new_mode_apis) {
        CFDictionaryRef vidmode = (CFDictionaryRef) _mode;
        CFNumberGetValue (
            CFDictionaryGetValue (vidmode, kCGDisplayBitsPerPixel),
            kCFNumberSInt32Type, bpp);

        CFNumberGetValue (
            CFDictionaryGetValue (vidmode, kCGDisplayWidth),
            kCFNumberSInt32Type, w);

        CFNumberGetValue (
            CFDictionaryGetValue (vidmode, kCGDisplayHeight),
            kCFNumberSInt32Type, h);
    }
#endif

    /* we only care about the 32-bit modes... */
    if (*bpp != 32) {
        *bpp = 0;
    }
}

static int QZ_VideoInit (_THIS, SDL_PixelFormat *video_format)
{
    NSRect r = NSMakeRect(0.0, 0.0, 0.0, 0.0);
    const char *env = NULL;

    if ( Gestalt(gestaltSystemVersion, &system_version) != noErr )
        system_version = 0;

    use_new_mode_apis = NO;

#if (MAC_OS_X_VERSION_MAX_ALLOWED >= 1060)
    use_new_mode_apis = IS_SNOW_LEOPARD_OR_LATER(this);
#endif

    /* Initialize the video settings; this data persists between mode switches */
    display_id = kCGDirectMainDisplay;

#if 0 /* The mouse event code needs to take this into account... */
    env = getenv("SDL_VIDEO_FULLSCREEN_DISPLAY");
    if ( env ) {
        int monitor = SDL_atoi(env);
        CGDirectDisplayID activeDspys [3];
        CGDisplayCount dspyCnt;
        CGGetActiveDisplayList (3, activeDspys, &dspyCnt);
        if ( monitor >= 0 && monitor < dspyCnt ) {
            display_id = activeDspys[monitor];
        }
    }
#endif

#if (MAC_OS_X_VERSION_MAX_ALLOWED >= 1060)
    if (use_new_mode_apis) {
        save_mode = CGDisplayCopyDisplayMode(display_id);
    }
#endif

#if (MAC_OS_X_VERSION_MIN_REQUIRED < 1060)
    if (!use_new_mode_apis) {
        save_mode = CGDisplayCurrentMode(display_id);
    }
#endif

#if (MAC_OS_X_VERSION_MIN_REQUIRED < 1070)
    if (!IS_LION_OR_LATER(this)) {
        palette = CGPaletteCreateDefaultColorPalette();
    }
#endif

    if (save_mode == NULL) {
        SDL_SetError("Couldn't figure out current display mode.");
        return -1;
    }

    /* Allow environment override of screensaver disable. */
    env = SDL_getenv("SDL_VIDEO_ALLOW_SCREENSAVER");
    if ( env ) {
        allow_screensaver = SDL_atoi(env);
    } else {
#ifdef SDL_VIDEO_DISABLE_SCREENSAVER
        allow_screensaver = 0;
#else
        allow_screensaver = 1;
#endif
    }

    /* Gather some information that is useful to know about the display */
    QZ_GetModeInfo(this, save_mode, &device_width, &device_height, &device_bpp);
    if (device_bpp == 0) {
        QZ_ReleaseDisplayMode(this, save_mode);
        save_mode = NULL;
        SDL_SetError("Unsupported display mode");
        return -1;
    }

    /* Determine the current screen size */
    this->info.current_w = device_width;
    this->info.current_h = device_height;

    /* Determine the default screen depth */
    video_format->BitsPerPixel = device_bpp;

    /* Set misc globals */
    current_grab_mode = SDL_GRAB_OFF;
    cursor_should_be_visible    = YES;
    cursor_visible              = YES;
    current_mods = 0;
    field_edit =  [[SDLTranslatorResponder alloc] initWithFrame:r];

    /* register for sleep notifications so wake from sleep generates SDL_VIDEOEXPOSE */
    QZ_RegisterForSleepNotifications (this);

    /* Fill in some window manager capabilities */
    this->info.wm_available = 1;

    return 0;
}

static SDL_Rect** QZ_ListModes (_THIS, SDL_PixelFormat *format, Uint32 flags)
{
    CFArrayRef mode_list = NULL;          /* list of available fullscreen modes */
    CFIndex num_modes;
    CFIndex i;

    int list_size = 0;

    /* Any windowed mode is acceptable */
    if ( (flags & SDL_FULLSCREEN) == 0 )
        return (SDL_Rect**)-1;

    /* Free memory from previous call, if any */
    if ( client_mode_list != NULL ) {
        int i;

        for (i = 0; client_mode_list[i] != NULL; i++)
            SDL_free (client_mode_list[i]);

        SDL_free (client_mode_list);
        client_mode_list = NULL;
    }

#if (MAC_OS_X_VERSION_MAX_ALLOWED >= 1060)
    if (use_new_mode_apis) {
        mode_list = CGDisplayCopyAllDisplayModes(display_id, NULL);
    }
#endif

#if (MAC_OS_X_VERSION_MIN_REQUIRED < 1060)
    if (!use_new_mode_apis) {
        mode_list = CGDisplayAvailableModes(display_id);
    }
#endif

    num_modes = CFArrayGetCount (mode_list);

    /* Build list of modes with the requested bpp */
    for (i = 0; i < num_modes; i++) {
        Uint32 width, height, bpp;
        const void *onemode = CFArrayGetValueAtIndex(mode_list, i);

        QZ_GetModeInfo(this, onemode, &width, &height, &bpp);

        if (bpp && (bpp == format->BitsPerPixel)) {
            int hasMode = SDL_FALSE;
            int i;

            /* Check if mode is already in the list */
            for (i = 0; i < list_size; i++) {
                if (client_mode_list[i]->w == width &&
                    client_mode_list[i]->h == height) {
                        hasMode = SDL_TRUE;
                        break;
                }
            }

            /* Grow the list and add mode to the list */
            if ( ! hasMode ) {
                SDL_Rect *rect;

                list_size++;

                if (client_mode_list == NULL)
                    client_mode_list = (SDL_Rect**) 
                        SDL_malloc (sizeof(*client_mode_list) * (list_size+1) );
                else {
                    /* !!! FIXME: this leaks memory if SDL_realloc() fails! */
                    client_mode_list = (SDL_Rect**)
                        SDL_realloc (client_mode_list, sizeof(*client_mode_list) * (list_size+1));
                }

                rect = (SDL_Rect*) SDL_malloc (sizeof(**client_mode_list));

                if (client_mode_list == NULL || rect == NULL) {
                    QZ_ReleaseDisplayModeList(this, mode_list);
                    SDL_OutOfMemory ();
                    return NULL;
                }

                rect->x = rect->y = 0;
                rect->w = width;
                rect->h = height;

                client_mode_list[list_size-1] = rect;
                client_mode_list[list_size]   = NULL;
            }
        }
    }

    QZ_ReleaseDisplayModeList(this, mode_list);

    /* Sort list largest to smallest (by area) */
    {
        int i, j;
        for (i = 0; i < list_size; i++) {
            for (j = 0; j < list_size-1; j++) {

                int area1, area2;
                area1 = client_mode_list[j]->w * client_mode_list[j]->h;
                area2 = client_mode_list[j+1]->w * client_mode_list[j+1]->h;

                if (area1 < area2) {
                    SDL_Rect *tmp = client_mode_list[j];
                    client_mode_list[j] = client_mode_list[j+1];
                    client_mode_list[j+1] = tmp;
                }
            }
        }
    }

    return client_mode_list;
}

static SDL_bool QZ_WindowPosition(_THIS, int *x, int *y)
{
    const char *window = getenv("SDL_VIDEO_WINDOW_POS");
    if ( window ) {
        if ( sscanf(window, "%d,%d", x, y) == 2 ) {
            return SDL_TRUE;
        }
    }
    return SDL_FALSE;
}

static CGError QZ_SetDisplayMode(_THIS, const void *vidmode)
{
#if (MAC_OS_X_VERSION_MAX_ALLOWED >= 1060)
    if (use_new_mode_apis) {
        return CGDisplaySetDisplayMode(display_id, (CGDisplayModeRef) vidmode, NULL);
    }
#endif

#if (MAC_OS_X_VERSION_MIN_REQUIRED < 1060)
    if (!use_new_mode_apis) {
        return CGDisplaySwitchToMode(display_id, (CFDictionaryRef) vidmode);
    }
#endif

    return kCGErrorFailure;
}

static inline CGError QZ_RestoreDisplayMode(_THIS)
{
    return QZ_SetDisplayMode(this, save_mode);
}

static void QZ_UnsetVideoMode (_THIS, BOOL to_desktop, BOOL save_gl)
{
    /* Reset values that may change between switches */
    this->info.blit_fill  = 0;
    this->FillHWRect      = NULL;
    this->UpdateRects     = NULL;
    this->LockHWSurface   = NULL;
    this->UnlockHWSurface = NULL;

    if (cg_context) {
        CGContextFlush (cg_context);
        CGContextRelease (cg_context);
        cg_context = nil;
    }

    /* Release fullscreen resources */
    if ( mode_flags & SDL_FULLSCREEN ) {

        NSRect screen_rect;

        /*  Release double buffer stuff */
#if (MAC_OS_X_VERSION_MIN_REQUIRED < 1070)
        if ( !IS_LION_OR_LATER(this) && (mode_flags & SDL_DOUBLEBUF) ) {
            quit_thread = YES;
            SDL_SemPost (sem1);
            SDL_WaitThread (thread, NULL);
            SDL_DestroySemaphore (sem1);
            SDL_DestroySemaphore (sem2);
            SDL_free (sw_buffers[0]);
        }
#endif

        /* If we still have a valid window, close it. */
        if ( qz_window ) {
            NSCAssert([ qz_window delegate ] == nil, @"full screen window shouldn't have a delegate"); /* if that should ever change, we'd have to release it here */
            [ qz_window close ]; /* includes release because [qz_window isReleasedWhenClosed] */
            qz_window = nil;
            window_view = nil;
        }
        /* 
            Release the OpenGL context
            Do this first to avoid trash on the display before fade
        */
        if ( mode_flags & SDL_OPENGL ) {
            if (!save_gl) {
                QZ_TearDownOpenGL (this);
            }

            #ifdef __powerpc__  /* we only use this for pre-10.3 compatibility. */
            CGLSetFullScreen (NULL);
            #endif
        }
        if (to_desktop) {
            /* !!! FIXME: keep an eye on this.
             * This API is officially unavailable for 64-bit binaries.
             *  It happens to work, as of 10.7, but we're going to see if
             *  we can just simply do without it on newer OSes...
             */
            #if (MAC_OS_X_VERSION_MIN_REQUIRED < 1070) && !defined(__LP64__)
            if ( !IS_LION_OR_LATER(this) ) {
                ShowMenuBar ();
            }
            #endif

            /* Restore original screen resolution/bpp */
            QZ_RestoreDisplayMode (this);
            CGReleaseAllDisplays ();
            /* 
                Reset the main screen's rectangle
                See comment in QZ_SetVideoFullscreen for why we do this
            */
            screen_rect = NSMakeRect(0,0,device_width,device_height);
            QZ_SetFrame(this, [ NSScreen mainScreen ], screen_rect);
        }
    }
    /* Release window mode resources */
    else {
        id delegate = [ qz_window delegate ];
        [ qz_window close ]; /* includes release because [qz_window isReleasedWhenClosed] */
        if (delegate != nil) [ delegate release ];
        qz_window = nil;
        window_view = nil;

        /* Release the OpenGL context */
        if ( mode_flags & SDL_OPENGL ) {
            if (!save_gl) {
                QZ_TearDownOpenGL (this);
            }
        }
    }

    /* Signal successful teardown */
    video_set = SDL_FALSE;
}

static const void *QZ_BestMode(_THIS, const int bpp, const int w, const int h)
{
    const void *best = NULL;

    if (bpp == 0) {
        return NULL;
    }

#if (MAC_OS_X_VERSION_MAX_ALLOWED >= 1060)
    if (use_new_mode_apis) {
        /* apparently, we have to roll our own now. :/ */
        CFArrayRef mode_list = CGDisplayCopyAllDisplayModes(display_id, NULL);
        if (mode_list != NULL) {
            const CFIndex num_modes = CFArrayGetCount(mode_list);
            CFIndex i;
            for (i = 0; i < num_modes; i++) {
                const void *vidmode = CFArrayGetValueAtIndex(mode_list, i);
                Uint32 thisw, thish, thisbpp;
                QZ_GetModeInfo(this, vidmode, &thisw, &thish, &thisbpp);

                /* We only care about exact matches, apparently. */
                if ((thisbpp == bpp) && (thisw == w) && (thish == h)) {
                    best = vidmode;
                    break;  /* got it! */
                }
            }
            CGDisplayModeRetain((CGDisplayModeRef) best);  /* NULL is ok */
            CFRelease(mode_list);
        }
    }
#endif

#if (MAC_OS_X_VERSION_MIN_REQUIRED < 1060)
    if (!use_new_mode_apis) {
        boolean_t exact = 0;
        best = CGDisplayBestModeForParameters(display_id, bpp, w, h, &exact);
        if (!exact) {
            best = NULL;
        }
    }
#endif

    return best;
}

static SDL_Surface* QZ_SetVideoFullScreen (_THIS, SDL_Surface *current, int width,
                                           int height, int bpp, Uint32 flags,
                                           const BOOL save_gl)
{
    const BOOL isLion = IS_LION_OR_LATER(this);
    NSRect screen_rect;
    CGError error;
    NSRect contentRect;
    CGDisplayFadeReservationToken fade_token = kCGDisplayFadeReservationInvalidToken;

    current->flags = SDL_FULLSCREEN;
    current->w = width;
    current->h = height;

    contentRect = NSMakeRect (0, 0, width, height);

    /* Fade to black to hide resolution-switching flicker (and garbage
       that is displayed by a destroyed OpenGL context, if applicable) */
    if ( CGAcquireDisplayFadeReservation (5, &fade_token) == kCGErrorSuccess ) {
        CGDisplayFade (fade_token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, TRUE);
    }

    /* Destroy any previous mode */
    if (video_set == SDL_TRUE)
        QZ_UnsetVideoMode (this, FALSE, save_gl);

    /* Sorry, QuickDraw was ripped out. */
    if (getenv("SDL_NSWindowPointer") || getenv("SDL_NSQuickDrawViewPointer")) {
        SDL_SetError ("Embedded QuickDraw windows are no longer supported");
        goto ERR_NO_MATCH;
    }

    QZ_ReleaseDisplayMode(this, mode);  /* NULL is okay. */

    /* See if requested mode exists */
    mode = QZ_BestMode(this, bpp, width, height);

    /* Require an exact match to the requested mode */
    if ( mode == NULL ) {
        SDL_SetError ("Failed to find display resolution: %dx%dx%d", width, height, bpp);
        goto ERR_NO_MATCH;
    }

    /* Put up the blanking window (a window above all other windows) */
    if (getenv ("SDL_SINGLEDISPLAY"))
        error = CGDisplayCapture (display_id);
    else
        error = CGCaptureAllDisplays ();

    if ( CGDisplayNoErr != error ) {
        SDL_SetError ("Failed capturing display");
        goto ERR_NO_CAPTURE;
    }

    /* Do the physical switch */
    if ( CGDisplayNoErr != QZ_SetDisplayMode(this, mode) ) {
        SDL_SetError ("Failed switching display resolution");
        goto ERR_NO_SWITCH;
    }

#if (MAC_OS_X_VERSION_MIN_REQUIRED < 1070)
    if ( !isLion ) {
        current->pixels = (Uint32*) CGDisplayBaseAddress (display_id);
        current->pitch  = CGDisplayBytesPerRow (display_id);

        current->flags |= SDL_HWSURFACE;
        current->flags |= SDL_PREALLOC;
        /* current->hwdata = (void *) CGDisplayGetDrawingContext (display_id); */

        this->UpdateRects     = QZ_DirectUpdate;
        this->LockHWSurface   = QZ_LockHWSurface;
        this->UnlockHWSurface = QZ_UnlockHWSurface;

        /* Setup double-buffer emulation */
        if ( flags & SDL_DOUBLEBUF ) {
            /*
            Setup a software backing store for reasonable results when
            double buffering is requested (since a single-buffered hardware
            surface looks hideous).

            The actual screen blit occurs in a separate thread to allow 
            other blitting while waiting on the VBL (and hence results in higher framerates).
            */
            this->LockHWSurface = NULL;
            this->UnlockHWSurface = NULL;
            this->UpdateRects = NULL;

            current->flags |= (SDL_HWSURFACE|SDL_DOUBLEBUF);
            this->UpdateRects = QZ_DoubleBufferUpdate;
            this->LockHWSurface = QZ_LockDoubleBuffer;
            this->UnlockHWSurface = QZ_UnlockDoubleBuffer;
            this->FlipHWSurface = QZ_FlipDoubleBuffer;

            current->pixels = SDL_malloc (current->pitch * current->h * 2);
            if (current->pixels == NULL) {
                SDL_OutOfMemory ();
                goto ERR_DOUBLEBUF;
            }

            sw_buffers[0] = current->pixels;
            sw_buffers[1] = (Uint8*)current->pixels + current->pitch * current->h;

            quit_thread = NO;
            sem1 = SDL_CreateSemaphore (0);
            sem2 = SDL_CreateSemaphore (1);
            thread = SDL_CreateThread ((int (*)(void *))QZ_ThreadFlip, this);
        }

        if ( CGDisplayCanSetPalette (display_id) )
            current->flags |= SDL_HWPALETTE;
    }
#endif

    /* Check if we should recreate the window */
    if (qz_window == nil) {
        /* Manually create a window, avoids having a nib file resource */
        qz_window = [ [ SDL_QuartzWindow alloc ] 
            initWithContentRect:contentRect
                styleMask:(isLion ? NSBorderlessWindowMask : 0)
                    backing:NSBackingStoreBuffered
                        defer:NO ];

        if (qz_window != nil) {
            [ qz_window setAcceptsMouseMovedEvents:YES ];
            [ qz_window setViewsNeedDisplay:NO ];
            if (isLion) {
                [ qz_window setContentView: [ [ [ SDL_QuartzView alloc ] init ] autorelease ] ];
            }
        }
    }
    /* We already have a window, just change its size */
    else {
        [ qz_window setContentSize:contentRect.size ];
        current->flags |= (SDL_NOFRAME|SDL_RESIZABLE) & mode_flags;
        [ window_view setFrameSize:contentRect.size ];
    }

    /* Setup OpenGL for a fullscreen context */
    if (flags & SDL_OPENGL) {

        if ( ! save_gl ) {
            if ( ! QZ_SetupOpenGL (this, bpp, flags) ) {
                goto ERR_NO_GL;
            }
        }

        /* Initialize the NSView and add it to our window.  The presence of a valid window and
           view allow the cursor to be changed whilst in fullscreen.*/
        window_view = [ [ NSView alloc ] initWithFrame:contentRect ];

        if ( isLion ) {
            [ window_view setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable ];
        }

        [ [ qz_window contentView ] addSubview:window_view ];

        /* Apparently Lion checks some version flag set by the linker
           and changes API behavior. Annoying. */
        if ( isLion ) {
            [ qz_window setLevel:CGShieldingWindowLevel() ];
            [ gl_context setView: window_view ];
            //[ gl_context setFullScreen ];
            [ gl_context update ];
        }

#if (MAC_OS_X_VERSION_MIN_REQUIRED < 1070)
        if ( !isLion ) {
            CGLError err;
            CGLContextObj ctx;

            [ qz_window setLevel:NSNormalWindowLevel ];
            ctx = QZ_GetCGLContextObj (gl_context);
            err = CGLSetFullScreen (ctx);

            if (err) {
                SDL_SetError ("Error setting OpenGL fullscreen: %s", CGLErrorString(err));
                goto ERR_NO_GL;
            }
        }
#endif

        [ window_view release ];
        [ gl_context makeCurrentContext];

        glClear (GL_COLOR_BUFFER_BIT);

        [ gl_context flushBuffer ];

        current->flags |= SDL_OPENGL;
    } else if (isLion) {  /* For 2D, we build a CGBitmapContext */
        CGColorSpaceRef cgColorspace;

        /* Only recreate the view if it doesn't already exist */
        if (window_view == nil) {
            window_view = [ [ NSView alloc ] initWithFrame:contentRect ];
            [ window_view setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable ];
            [ [ qz_window contentView ] addSubview:window_view ];
            [ window_view release ];
        }

        cgColorspace = CGColorSpaceCreateDeviceRGB();
        current->pitch = 4 * current->w;
        current->pixels = SDL_malloc (current->h * current->pitch);

        cg_context = CGBitmapContextCreate (current->pixels, current->w, current->h,
                        8, current->pitch, cgColorspace,
                        kCGImageAlphaNoneSkipFirst);
        CGColorSpaceRelease (cgColorspace);

        current->flags |= SDL_SWSURFACE;
        current->flags |= SDL_ASYNCBLIT;
        current->hwdata = (void *) cg_context;

        /* Force this window to draw above _everything_. */
        [ qz_window setLevel:CGShieldingWindowLevel() ];

        this->UpdateRects     = QZ_UpdateRects;
        this->LockHWSurface   = QZ_LockHWSurface;
        this->UnlockHWSurface = QZ_UnlockHWSurface;
    }

    if (isLion) {
        [ qz_window setHasShadow:NO];
        [ qz_window setOpaque:YES];
        [ qz_window makeKeyAndOrderFront:nil ];
    }

    /* !!! FIXME: keep an eye on this.
     * This API is officially unavailable for 64-bit binaries.
     *  It happens to work, as of 10.7, but we're going to see if
     *  we can just simply do without it on newer OSes...
     */
    #if (MAC_OS_X_VERSION_MIN_REQUIRED < 1070) && !defined(__LP64__)
    if ( !isLion ) {
        /* If we don't hide menu bar, it will get events and interrupt the program */
        HideMenuBar ();
    }
    #endif

    /* Fade in again (asynchronously) */
    if ( fade_token != kCGDisplayFadeReservationInvalidToken ) {
        CGDisplayFade (fade_token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0.0, 0.0, 0.0, FALSE);
        CGReleaseDisplayFadeReservation(fade_token);
    }

    /* 
        There is a bug in Cocoa where NSScreen doesn't synchronize
        with CGDirectDisplay, so the main screen's frame is wrong.
        As a result, coordinate translation produces incorrect results.
        We can hack around this bug by setting the screen rect
        ourselves. This hack should be removed if/when the bug is fixed.
    */
    screen_rect = NSMakeRect(0,0,width,height);
    QZ_SetFrame(this, [ NSScreen mainScreen ], screen_rect);

    /* Save the flags to ensure correct tear-down */
    mode_flags = current->flags;

    /* Set app state, hide cursor if necessary, ... */
    QZ_DoActivate(this);

    [ window_view setNeedsDisplay:YES ];
    [ [ qz_window contentView ] setNeedsDisplay:YES ];
    [ qz_window displayIfNeeded ];

    return current;

    /* Since the blanking window covers *all* windows (even force quit) correct recovery is crucial */
ERR_NO_GL:      goto ERR_DOUBLEBUF;  /* this goto is to stop a compiler warning on newer SDKs. */
ERR_DOUBLEBUF:  QZ_RestoreDisplayMode(this);
ERR_NO_SWITCH:  CGReleaseAllDisplays ();
ERR_NO_CAPTURE:
ERR_NO_MATCH:   if ( fade_token != kCGDisplayFadeReservationInvalidToken ) {
                    CGDisplayFade (fade_token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0.0, 0.0, 0.0, FALSE);
                    CGReleaseDisplayFadeReservation (fade_token);
                }
                return NULL;
}

static SDL_Surface* QZ_SetVideoWindowed (_THIS, SDL_Surface *current, int width,
                                         int height, int *bpp, Uint32 flags,
                                         const BOOL save_gl)
{
    unsigned int style;
    NSRect contentRect;
    int center_window = 1;
    int origin_x, origin_y;
    CGDisplayFadeReservationToken fade_token = kCGDisplayFadeReservationInvalidToken;

    current->flags = 0;
    current->w = width;
    current->h = height;

    contentRect = NSMakeRect (0, 0, width, height);

    /*
        Check if we should completely destroy the previous mode 
        - If it is fullscreen
        - If it has different noframe or resizable attribute
        - If it is OpenGL (since gl attributes could be different)
        - If new mode is OpenGL, but previous mode wasn't
    */
    if (video_set == SDL_TRUE) {
        if (mode_flags & SDL_FULLSCREEN) {
            /* Fade to black to hide resolution-switching flicker (and garbage
               that is displayed by a destroyed OpenGL context, if applicable) */
            if (CGAcquireDisplayFadeReservation (5, &fade_token) == kCGErrorSuccess) {
                CGDisplayFade (fade_token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, TRUE);
            }
            QZ_UnsetVideoMode (this, TRUE, save_gl);
        }
        else if ( ((mode_flags ^ flags) & (SDL_NOFRAME|SDL_RESIZABLE)) ||
                  (mode_flags & SDL_OPENGL) || 
                  (flags & SDL_OPENGL) ) {
            QZ_UnsetVideoMode (this, TRUE, save_gl);
        }
    }

    /* Sorry, QuickDraw was ripped out. */
    if (getenv("SDL_NSWindowPointer") || getenv("SDL_NSQuickDrawViewPointer")) {
        SDL_SetError ("Embedded QuickDraw windows are no longer supported");
        if (fade_token != kCGDisplayFadeReservationInvalidToken) {
            CGDisplayFade (fade_token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0.0, 0.0, 0.0, FALSE);
            CGReleaseDisplayFadeReservation (fade_token);
        }
        return NULL;
    }

    /* Check if we should recreate the window */
    if (qz_window == nil) {
        /* Set the window style based on input flags */
        if ( flags & SDL_NOFRAME ) {
            style = NSBorderlessWindowMask;
            current->flags |= SDL_NOFRAME;
        } else {
            style = NSTitledWindowMask;
            style |= (NSMiniaturizableWindowMask | NSClosableWindowMask);
            if ( flags & SDL_RESIZABLE ) {
                style |= NSResizableWindowMask;
                current->flags |= SDL_RESIZABLE;
            }
        }

        /* Manually create a window, avoids having a nib file resource */
        qz_window = [ [ SDL_QuartzWindow alloc ] 
            initWithContentRect:contentRect
                styleMask:style 
                    backing:NSBackingStoreBuffered
                        defer:NO ];

        if (qz_window == nil) {
            SDL_SetError ("Could not create the Cocoa window");
            if (fade_token != kCGDisplayFadeReservationInvalidToken) {
                CGDisplayFade (fade_token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0.0, 0.0, 0.0, FALSE);
                CGReleaseDisplayFadeReservation (fade_token);
            }
            return NULL;
        }

        /*[ qz_window setReleasedWhenClosed:YES ];*/ /* no need to set this as it's the default for NSWindows */
        QZ_SetCaption(this, this->wm_title, this->wm_icon);
        [ qz_window setAcceptsMouseMovedEvents:YES ];
        [ qz_window setViewsNeedDisplay:NO ];

        if ( QZ_WindowPosition(this, &origin_x, &origin_y) ) {
            /* have to flip the Y value (NSPoint is lower left corner origin) */
            [ qz_window setFrameTopLeftPoint:NSMakePoint((float) origin_x, (float) (this->info.current_h - origin_y))];
            center_window = 0;
        } else if ( center_window ) {
            [ qz_window center ];
        }

        [ qz_window setDelegate:
            [ [ SDL_QuartzWindowDelegate alloc ] init ] ];
        [ qz_window setContentView: [ [ [ SDL_QuartzView alloc ] init ] autorelease ] ];
    }
    /* We already have a window, just change its size */
    else {
        [ qz_window setContentSize:contentRect.size ];
        current->flags |= (SDL_NOFRAME|SDL_RESIZABLE) & mode_flags;
        [ window_view setFrameSize:contentRect.size ];
    }

    /* For OpenGL, we bind the context to a subview */
    if ( flags & SDL_OPENGL ) {

        if ( ! save_gl ) {
            if ( ! QZ_SetupOpenGL (this, *bpp, flags) ) {
                if (fade_token != kCGDisplayFadeReservationInvalidToken) {
                    CGDisplayFade (fade_token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0.0, 0.0, 0.0, FALSE);
                    CGReleaseDisplayFadeReservation (fade_token);
                }
                return NULL;
            }
        }

        window_view = [ [ NSView alloc ] initWithFrame:contentRect ];
        [ window_view setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable ];

        NSRect contentRectOrig = NSMakeRect (0, 0, width, height);

#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101500
        /* OpenGL in Catalina apparently wants to match pixels on Retina displays. Without this the OpenGL
           display will only use 1/4th the display in the lower left corner. This seems to be an API issue
           with XCode as binaries previously compiled on Mojave don't have this issue on Catalina. */
        contentRectOrig = [ window_view convertRectFromBacking:contentRectOrig ];
#endif

        [ window_view setBoundsSize: contentRectOrig.size ];

        [ [ qz_window contentView ] addSubview:window_view ];
        [ gl_context setView: window_view ];
        [ window_view release ];
        [ gl_context makeCurrentContext];
        [ qz_window makeKeyAndOrderFront:nil ];
        current->flags |= SDL_OPENGL;
    }
    /* For 2D, we build a CGBitmapContext */
    else {
        CGColorSpaceRef cgColorspace;

        /* Only recreate the view if it doesn't already exist */
        if (window_view == nil) {
            window_view = [ [ NSView alloc ] initWithFrame:contentRect ];
            [ window_view setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable ];
            [ [ qz_window contentView ] addSubview:window_view ];
            [ window_view release ];
            [ qz_window makeKeyAndOrderFront:nil ];
        }

        cgColorspace = CGColorSpaceCreateDeviceRGB();
        current->pitch = 4 * current->w;
        current->pixels = SDL_malloc (current->h * current->pitch);

        cg_context = CGBitmapContextCreate (current->pixels, current->w, current->h,
                        8, current->pitch, cgColorspace,
                        kCGImageAlphaNoneSkipFirst);
        CGColorSpaceRelease (cgColorspace);

        current->flags |= SDL_SWSURFACE;
        current->flags |= SDL_ASYNCBLIT;
        current->hwdata = (void *) cg_context;

        this->UpdateRects     = QZ_UpdateRects;
        this->LockHWSurface   = QZ_LockHWSurface;
        this->UnlockHWSurface = QZ_UnlockHWSurface;
    }

    /* Save flags to ensure correct teardown */
    mode_flags = current->flags;

    [ window_view setNeedsDisplay:YES ];
    [ [ qz_window contentView ] setNeedsDisplay:YES ];
    [ qz_window displayIfNeeded ];

    /* Fade in again (asynchronously) if we came from a fullscreen mode and faded to black */
    if (fade_token != kCGDisplayFadeReservationInvalidToken) {
        CGDisplayFade (fade_token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0.0, 0.0, 0.0, FALSE);
        CGReleaseDisplayFadeReservation (fade_token);
    }

    return current;
}


static SDL_Surface* QZ_SetVideoModeInternal (_THIS, SDL_Surface *current,
                                             int width, int height, int bpp,
                                             Uint32 flags, BOOL save_gl)
{
    const BOOL isLion = IS_LION_OR_LATER(this);

    current->flags = 0;
    current->pixels = NULL;

    /* Setup full screen video */
    if ( flags & SDL_FULLSCREEN ) {
        if ( isLion ) {
            bpp = 32;
        }
        current = QZ_SetVideoFullScreen (this, current, width, height, bpp, flags, save_gl );
        if (current == NULL)
            return NULL;
    }
    /* Setup windowed video */
    else {
        /* Force bpp to 32 */
        bpp = 32;
        current = QZ_SetVideoWindowed (this, current, width, height, &bpp, flags, save_gl );
        if (current == NULL)
            return NULL;
    }

    if (qz_window != nil) {
        nsgfx_context = [NSGraphicsContext graphicsContextWithWindow:qz_window];
        if (nsgfx_context != NULL) {
            [NSGraphicsContext setCurrentContext:nsgfx_context];
        }
        else {
            /* Whoops, looks like Mojave doesn't support this anymore */
            fprintf(stderr,"Unable to obtain graphics context for NSWindow (Mojave behavior)\n");
        }
    }

    /* Setup the new pixel format */
    {
        int amask = 0,
        rmask = 0,
        gmask = 0,
        bmask = 0;

        switch (bpp) {
            case 16:   /* (1)-5-5-5 RGB */
                amask = 0;
                rmask = 0x7C00;
                gmask = 0x03E0;
                bmask = 0x001F;
                break;
            case 24:
                SDL_SetError ("24bpp is not available");
                return NULL;
            case 32:   /* (8)-8-8-8 ARGB */
                amask = 0x00000000;
                if ( (!isLion) && (flags & SDL_FULLSCREEN) ) {
                    rmask = 0x00FF0000;
                    gmask = 0x0000FF00;
                    bmask = 0x000000FF;
                } else {
#if SDL_BYTEORDER == SDL_LIL_ENDIAN
                    rmask = 0x0000FF00;
                    gmask = 0x00FF0000;
                    bmask = 0xFF000000;
#else
                    rmask = 0x00FF0000;
                    gmask = 0x0000FF00;
                    bmask = 0x000000FF;
#endif
                    break;
                }
        }

        if ( ! SDL_ReallocFormat (current, bpp,
                                  rmask, gmask, bmask, amask ) ) {
            SDL_SetError ("Couldn't reallocate pixel format");
            return NULL;
        }
    }

    /* Signal successful completion (used internally) */
    video_set = SDL_TRUE;

    return current;
}

static SDL_Surface* QZ_SetVideoMode(_THIS, SDL_Surface *current,
                                    int width, int height, int bpp,
                                    Uint32 flags)
{
    /* Don't throw away the GL context if we can just resize the current one. */
#if 0  /* !!! FIXME: half-finished side project. Reenable this if you ever debug the corner cases. */
    const BOOL save_gl = ( (video_set == SDL_TRUE) && ((flags & SDL_OPENGL) == (current->flags & SDL_OPENGL)) && (bpp == current->format->BitsPerPixel) );
#else
    const BOOL save_gl = NO;
#endif

    NSOpenGLContext *glctx = gl_context;
    SDL_Surface* retval = NULL;

    if (save_gl) {
        [glctx retain];  /* just so we don't lose this when killing old views, etc */
    }

    retval = QZ_SetVideoModeInternal (this, current, width, height, bpp, flags, save_gl);

    if (save_gl) {
        [glctx release];  /* something else should own this now, or we legitimately release it. */
    }

    return retval;
}


static int QZ_ToggleFullScreen (_THIS, int on)
{
    return 0;
}

static int QZ_SetColors (_THIS, int first_color, int num_colors,
                         SDL_Color *colors)
{
#if (MAC_OS_X_VERSION_MIN_REQUIRED < 1070)
    /* we shouldn't have an 8-bit mode on Lion! */
    if (!IS_LION_OR_LATER(this)) {
        CGTableCount  index;
        CGDeviceColor color;

        for (index = first_color; index < first_color+num_colors; index++) {

            /* Clamp colors between 0.0 and 1.0 */
            color.red   = colors->r / 255.0;
            color.blue  = colors->b / 255.0;
            color.green = colors->g / 255.0;

            colors++;

            CGPaletteSetColorAtIndex (palette, color, index);
        }

        return ( CGDisplayNoErr == CGDisplaySetPalette (display_id, palette) );
    }
#endif

    return 0;
}

#if (MAC_OS_X_VERSION_MIN_REQUIRED < 1070)
static int QZ_LockDoubleBuffer (_THIS, SDL_Surface *surface)
{
    return 1;
}

static void QZ_UnlockDoubleBuffer (_THIS, SDL_Surface *surface)
{
}

/* The VBL delay is based on code by Ian R Ollmann's RezLib <iano@cco.caltech.edu> */
static AbsoluteTime QZ_SecondsToAbsolute ( double seconds )
{
    union
    {
        UInt64 i;
        Nanoseconds ns;
    } temp;

    temp.i = seconds * 1000000000.0;

    return NanosecondsToAbsolute ( temp.ns );
}

static int QZ_ThreadFlip (_THIS)
{
    Uint8 *src, *dst;
    int skip, len, h;

    /*
        Give this thread the highest scheduling priority possible,
        in the hopes that it will immediately run after the VBL delay
    */
    {
        pthread_t current_thread;
        int policy;
        struct sched_param param;

        current_thread = pthread_self ();
        pthread_getschedparam (current_thread, &policy, &param);
        policy = SCHED_RR;
        param.sched_priority = sched_get_priority_max (policy);
        pthread_setschedparam (current_thread, policy, &param);
    }

    while (1) {
        SDL_SemWait (sem1);
        if (quit_thread)
            return 0;

        /*
         * We have to add SDL_VideoSurface->offset here, since we might be a
         *  smaller surface in the center of the framebuffer (you asked for
         *  a fullscreen resolution smaller than the hardware could supply
         *  so SDL is centering it in a bigger resolution)...
         */
        dst = (unsigned char *)CGDisplayBaseAddress(display_id) + SDL_VideoSurface->offset;
        src = current_buffer + SDL_VideoSurface->offset;
        len = SDL_VideoSurface->w * SDL_VideoSurface->format->BytesPerPixel;
        h = SDL_VideoSurface->h;
        skip = SDL_VideoSurface->pitch;

        /* Wait for the VBL to occur (estimated since we don't have a hardware interrupt) */
        {
            /* The VBL delay is based on Ian Ollmann's RezLib <iano@cco.caltech.edu> */
            double refreshRate;
            double linesPerSecond;
            double target;
            double position;
            double adjustment;
            AbsoluteTime nextTime;
            CFNumberRef refreshRateCFNumber;

            refreshRateCFNumber = CFDictionaryGetValue (mode, kCGDisplayRefreshRate);
            if ( NULL == refreshRateCFNumber ) {
                SDL_SetError ("Mode has no refresh rate");
                goto ERROR;
            }

            if ( 0 == CFNumberGetValue (refreshRateCFNumber, kCFNumberDoubleType, &refreshRate) ) {
                SDL_SetError ("Error getting refresh rate");
                goto ERROR;
            }

            if ( 0 == refreshRate ) {
               SDL_SetError ("Display has no refresh rate, using 60hz");

                /* ok, for LCD's we'll emulate a 60hz refresh, which may or may not look right */
                refreshRate = 60.0;
            }

            linesPerSecond = refreshRate * h;
            target = h;

            /* Figure out the first delay so we start off about right */
            position = CGDisplayBeamPosition (display_id);
            if (position > target)
                position = 0;

            adjustment = (target - position) / linesPerSecond; 

            nextTime = AddAbsoluteToAbsolute (UpTime (), QZ_SecondsToAbsolute (adjustment));

            MPDelayUntil (&nextTime);
        }

        /* On error, skip VBL delay */
        ERROR:

        /* TODO: use CGContextDrawImage here too!  Create two CGContextRefs the same way we
           create two buffers, replace current_buffer with current_context and set it
           appropriately in QZ_FlipDoubleBuffer.  */
        while ( h-- ) {
            SDL_memcpy (dst, src, len);
            src += skip;
            dst += skip;
        }

        /* signal flip completion */
        SDL_SemPost (sem2);
    }

    return 0;
}

static int QZ_FlipDoubleBuffer (_THIS, SDL_Surface *surface)
{
    /* wait for previous flip to complete */
    SDL_SemWait (sem2);

    current_buffer = surface->pixels;

    if (surface->pixels == sw_buffers[0])
        surface->pixels = sw_buffers[1];
    else
        surface->pixels = sw_buffers[0];

    /* signal worker thread to do the flip */
    SDL_SemPost (sem1);

    return 0;
}

static void QZ_DoubleBufferUpdate (_THIS, int num_rects, SDL_Rect *rects)
{
    /* perform a flip if someone calls updaterects on a doublebuferred surface */
    this->FlipHWSurface (this, SDL_VideoSurface);
}

static void QZ_DirectUpdate (_THIS, int num_rects, SDL_Rect *rects)
{
#pragma unused(this,num_rects,rects)
}
#endif

/* Resize icon, BMP format */
static const unsigned char QZ_ResizeIcon[] = {
    0x42,0x4d,0x31,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x36,0x00,0x00,0x00,0x28,0x00,
    0x00,0x00,0x0d,0x00,0x00,0x00,0x0d,0x00,0x00,0x00,0x01,0x00,0x18,0x00,0x00,0x00,
    0x00,0x00,0xfb,0x01,0x00,0x00,0x13,0x0b,0x00,0x00,0x13,0x0b,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0b,0xff,0xff,
    0xff,0xda,0xda,0xda,0x87,0x87,0x87,0xe8,0xe8,0xe8,0xff,0xff,0xff,0xda,0xda,0xda,
    0x87,0x87,0x87,0xe8,0xe8,0xe8,0xff,0xff,0xff,0xda,0xda,0xda,0x87,0x87,0x87,0xe8,
    0xe8,0xe8,0xff,0xff,0xff,0x0b,0xff,0xff,0xff,0xff,0xff,0xff,0xda,0xda,0xda,0x87,
    0x87,0x87,0xe8,0xe8,0xe8,0xff,0xff,0xff,0xda,0xda,0xda,0x87,0x87,0x87,0xe8,0xe8,
    0xe8,0xff,0xff,0xff,0xda,0xda,0xda,0x87,0x87,0x87,0xff,0xff,0xff,0x0b,0xff,0xff,
    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xd5,0xd5,0xd5,0x87,0x87,0x87,0xe8,0xe8,0xe8,
    0xff,0xff,0xff,0xda,0xda,0xda,0x87,0x87,0x87,0xe8,0xe8,0xe8,0xff,0xff,0xff,0xda,
    0xda,0xda,0xff,0xff,0xff,0x0b,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
    0xff,0xff,0xd7,0xd7,0xd7,0x87,0x87,0x87,0xe8,0xe8,0xe8,0xff,0xff,0xff,0xda,0xda,
    0xda,0x87,0x87,0x87,0xe8,0xe8,0xe8,0xff,0xff,0xff,0xff,0xff,0xff,0x0b,0xff,0xff,
    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xd7,0xd7,0xd7,
    0x87,0x87,0x87,0xe8,0xe8,0xe8,0xff,0xff,0xff,0xda,0xda,0xda,0x87,0x87,0x87,0xe8,
    0xe8,0xe8,0xff,0xff,0xff,0x0b,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xd7,0xd7,0xd7,0x87,0x87,0x87,0xe8,0xe8,
    0xe8,0xff,0xff,0xff,0xdc,0xdc,0xdc,0x87,0x87,0x87,0xff,0xff,0xff,0x0b,0xff,0xff,
    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
    0xff,0xff,0xff,0xd9,0xd9,0xd9,0x87,0x87,0x87,0xe8,0xe8,0xe8,0xff,0xff,0xff,0xdc,
    0xdc,0xdc,0xff,0xff,0xff,0x0b,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xdb,0xdb,
    0xdb,0x87,0x87,0x87,0xe8,0xe8,0xe8,0xff,0xff,0xff,0xff,0xff,0xff,0x0b,0xff,0xff,
    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xdb,0xdb,0xdb,0x87,0x87,0x87,0xe8,
    0xe8,0xe8,0xff,0xff,0xff,0x0b,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
    0xff,0xff,0xff,0xff,0xdc,0xdc,0xdc,0x87,0x87,0x87,0xff,0xff,0xff,0x0b,0xff,0xff,
    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xdc,
    0xdc,0xdc,0xff,0xff,0xff,0x0b,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0b
};

static void QZ_DrawResizeIcon (_THIS)
{
    /* Check if we should draw the resize icon */
    if (SDL_VideoSurface->flags & SDL_RESIZABLE) {
        SDL_Rect icon_rect;

        /* Create the icon image */
        if (resize_icon == NULL) {
            SDL_RWops *rw;
            SDL_Surface *tmp;

            rw = SDL_RWFromConstMem (QZ_ResizeIcon, sizeof(QZ_ResizeIcon));
            tmp = SDL_LoadBMP_RW (rw, SDL_TRUE);

            resize_icon = SDL_ConvertSurface (tmp, SDL_VideoSurface->format, SDL_SRCCOLORKEY);
            SDL_SetColorKey (resize_icon, SDL_SRCCOLORKEY, 0xFFFFFF);

            SDL_FreeSurface (tmp);
        }

        icon_rect.x = SDL_VideoSurface->w - 13;
        icon_rect.y = SDL_VideoSurface->h - 13;
        icon_rect.w = 13;
        icon_rect.h = 13;

        SDL_BlitSurface (resize_icon, NULL, SDL_VideoSurface, &icon_rect);
    }
}

static SDL_VideoDevice *last_this = NULL;

void QZ_UpdateRectsOnDrawRect (/*TODO: NSRect from drawRect*/)
{
    SDL_VideoDevice *this = last_this; /* HACK */

    if (this == NULL) return;
    if (SDL_VideoSurface == NULL)
        return;

    if (SDL_VideoSurface->flags & SDL_OPENGLBLIT) {
        /* TODO? */
    }
    else if ( [ qz_window isMiniaturized ] ) {
        /* Do nothing if miniaturized */
    }
    else {
        NSGraphicsContext *ctx = [NSGraphicsContext currentContext];
        /* NTS: nsgfx_context == NULL will occur on Mojave, may be non-NULL on older versions of OS X */
        if (nsgfx_context != NULL && ctx != nsgfx_context) { /* uhoh, you might be rendering from another thread... */
            [NSGraphicsContext setCurrentContext:nsgfx_context];
            ctx = nsgfx_context;
        }
        CGContextRef cgc = (CGContextRef) [ctx graphicsPort];
        QZ_DrawResizeIcon (this);
        CGContextFlush (cg_context);
        CGImageRef image = CGBitmapContextCreateImage (cg_context);
        CGRect rectangle = CGRectMake (0,0,[window_view frame].size.width,[window_view frame].size.height);

        CGContextDrawImage (cgc, rectangle, image);
        CGImageRelease(image);
        CGContextFlush (cgc);
    }
}

static void QZ_UpdateRects (_THIS, int numRects, SDL_Rect *rects)
{
    last_this = this; /* HACK */

    if (SDL_VideoSurface->flags & SDL_OPENGLBLIT) {
        QZ_GL_SwapBuffers (this);
        /* TODO? */
    }
    else if ( [ qz_window isMiniaturized ] ) {
        /* Do nothing if miniaturized */
    }
    else {
        [ window_view setNeedsDisplay:YES ];
        [ [ qz_window contentView ] setNeedsDisplay:YES ];
        [ qz_window displayIfNeeded ];
    }
}

static void QZ_VideoQuit (_THIS)
{
    CGDisplayFadeReservationToken fade_token = kCGDisplayFadeReservationInvalidToken;

    /* Restore gamma settings */
    CGDisplayRestoreColorSyncSettings ();

    /* Ensure the cursor will be visible and working when we quit */
    CGDisplayShowCursor (display_id);
    CGAssociateMouseAndMouseCursorPosition (1);

    if (mode_flags & SDL_FULLSCREEN) {
        /* Fade to black to hide resolution-switching flicker (and garbage
           that is displayed by a destroyed OpenGL context, if applicable) */
        if (CGAcquireDisplayFadeReservation (5, &fade_token) == kCGErrorSuccess) {
            CGDisplayFade (fade_token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, TRUE);
        }
        QZ_UnsetVideoMode (this, TRUE, FALSE);
        if (fade_token != kCGDisplayFadeReservationInvalidToken) {
            CGDisplayFade (fade_token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0.0, 0.0, 0.0, FALSE);
            CGReleaseDisplayFadeReservation (fade_token);
        }
    }
    else
        QZ_UnsetVideoMode (this, TRUE, FALSE);

#if (MAC_OS_X_VERSION_MIN_REQUIRED < 1070)
    if (!IS_LION_OR_LATER(this)) {
        CGPaletteRelease(palette);
    }
#endif

    if (opengl_library) {
        SDL_UnloadObject(opengl_library);
        opengl_library = NULL;
    }
    this->gl_config.driver_loaded = 0;

    if (field_edit) {
        [field_edit release];
        field_edit = NULL;
    }
}

static int  QZ_LockHWSurface(_THIS, SDL_Surface *surface)
{
    return 1;
}

static void QZ_UnlockHWSurface(_THIS, SDL_Surface *surface)
{
}

static int QZ_AllocHWSurface(_THIS, SDL_Surface *surface)
{
    return(-1); /* unallowed (no HWSURFACE support here). */
}

static void QZ_FreeHWSurface (_THIS, SDL_Surface *surface)
{
}

/* Gamma functions */
int QZ_SetGamma (_THIS, float red, float green, float blue)
{
    const CGGammaValue min = 0.0, max = 1.0;

    if (red == 0.0)
        red = FLT_MAX;
    else
        red = 1.0 / red;

    if (green == 0.0)
        green = FLT_MAX;
    else
        green = 1.0 / green;

    if (blue == 0.0)
        blue = FLT_MAX;
    else
        blue  = 1.0 / blue;

    if ( CGDisplayNoErr == CGSetDisplayTransferByFormula
         (display_id, min, max, red, min, max, green, min, max, blue) ) {

        return 0;
    }
    else {

        return -1;
    }
}

int QZ_GetGamma (_THIS, float *red, float *green, float *blue)
{
    CGGammaValue dummy;
    if ( CGDisplayNoErr == CGGetDisplayTransferByFormula
         (display_id, &dummy, &dummy, red,
          &dummy, &dummy, green, &dummy, &dummy, blue) )

        return 0;
    else
        return -1;
}

int QZ_SetGammaRamp (_THIS, Uint16 *ramp)
{
    /* These no longer work on Mac OS X, and crash on some drivers (Intel HD
        4000 on Mac OS X 10.9, for example). */
    return -1;
#if 0
    const uint32_t tableSize = 256;
    CGGammaValue redTable[tableSize];
    CGGammaValue greenTable[tableSize];
    CGGammaValue blueTable[tableSize];

    int i;

    /* Extract gamma values into separate tables, convert to floats between 0.0 and 1.0 */
    for (i = 0; i < 256; i++)
        redTable[i % tableSize] = ramp[i] / 65535.0;

    for (i=256; i < 512; i++)
        greenTable[i % tableSize] = ramp[i] / 65535.0;

    for (i=512; i < 768; i++)
        blueTable[i % tableSize] = ramp[i] / 65535.0;

    if ( CGDisplayNoErr == CGSetDisplayTransferByTable
         (display_id, tableSize, redTable, greenTable, blueTable) )
        return 0;
    else
        return -1;
#endif
}

int QZ_GetGammaRamp (_THIS, Uint16 *ramp)
{
    /* These no longer work on Mac OS X, and crash on some drivers (Intel HD
        4000 on Mac OS X 10.9, for example). */
    return -1;
#if 0
    const uint32_t tableSize = 256;
    CGGammaValue redTable[tableSize];
    CGGammaValue greenTable[tableSize];
    CGGammaValue blueTable[tableSize];
    uint32_t actual;
    int i;

    if ( CGDisplayNoErr != CGGetDisplayTransferByTable
         (display_id, tableSize, redTable, greenTable, blueTable, &actual) ||
         actual != tableSize)

        return -1;

    /* Pack tables into one array, with values from 0 to 65535 */
    for (i = 0; i < 256; i++)
        ramp[i] = redTable[i % tableSize] * 65535.0;

    for (i=256; i < 512; i++)
        ramp[i] = greenTable[i % tableSize] * 65535.0;

    for (i=512; i < 768; i++)
        ramp[i] = blueTable[i % tableSize] * 65535.0;

    return 0;
#endif
}

