1/*
2    SDL - Simple DirectMedia Layer
3    Copyright (C) 1997-2012  Sam Lantinga
4
5    This library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Library General Public
7    License as published by the Free Software Foundation; either
8    version 2 of the License, or (at your option) any later version.
9
10    This library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Library General Public License for more details.
14
15    You should have received a copy of the GNU Library General Public
16    License along with this library; if not, write to the Free
17    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18
19    Sam Lantinga
20    slouken@libsdl.org
21*/
22#include "SDL_config.h"
23
24#include "SDL_QuartzVideo.h"
25#include "SDL_QuartzWindow.h"
26#include "SDL_QuartzWM.h"
27
28/* These APIs aren't just deprecated; they're gone from the headers in the
29   10.7 SDK. If we're using a >= 10.7 SDK, but targeting < 10.7, then we
30   force these function declarations. */
31#if ((MAC_OS_X_VERSION_MIN_REQUIRED < 1070) && (MAC_OS_X_VERSION_MAX_ALLOWED >= 1070))
32CG_EXTERN void *CGDisplayBaseAddress(CGDirectDisplayID display)
33  CG_AVAILABLE_BUT_DEPRECATED(__MAC_10_0, __MAC_10_6,
34    __IPHONE_NA, __IPHONE_NA);
35CG_EXTERN size_t CGDisplayBytesPerRow(CGDirectDisplayID display)
36  CG_AVAILABLE_BUT_DEPRECATED(__MAC_10_0, __MAC_10_6,
37    __IPHONE_NA, __IPHONE_NA);
38#endif
39
40
41static inline BOOL IS_LION_OR_LATER(_THIS)
42{
43    return (system_version >= 0x1070);
44}
45
46static inline BOOL IS_SNOW_LEOPARD_OR_LATER(_THIS)
47{
48    return (system_version >= 0x1060);
49}
50
51#if (MAC_OS_X_VERSION_MAX_ALLOWED < 1060) && !defined(__LP64__)  /* Fixed in Snow Leopard */
52/*
53    Add methods to get at private members of NSScreen.
54    Since there is a bug in Apple's screen switching code
55    that does not update this variable when switching
56    to fullscreen, we'll set it manually (but only for the
57    main screen).
58*/
59@interface NSScreen (NSScreenAccess)
60- (void) setFrame:(NSRect)frame;
61@end
62
63@implementation NSScreen (NSScreenAccess)
64- (void) setFrame:(NSRect)frame;
65{
66    _frame = frame;
67}
68@end
69static inline void QZ_SetFrame(_THIS, NSScreen *nsscreen, NSRect frame)
70{
71    if (!IS_SNOW_LEOPARD_OR_LATER(this)) {
72        [nsscreen setFrame:frame];
73    }
74}
75#else
76static inline void QZ_SetFrame(_THIS, NSScreen *nsscreen, NSRect frame)
77{
78}
79#endif
80
81@interface SDLTranslatorResponder : NSTextView
82{
83}
84- (void) doCommandBySelector:(SEL)myselector;
85@end
86
87@implementation SDLTranslatorResponder
88- (void) doCommandBySelector:(SEL) myselector {}
89@end
90
91/* absent in 10.3.9.  */
92CG_EXTERN CGImageRef CGBitmapContextCreateImage (CGContextRef);
93
94/* Bootstrap functions */
95static int              QZ_Available ();
96static SDL_VideoDevice* QZ_CreateDevice (int device_index);
97static void             QZ_DeleteDevice (SDL_VideoDevice *device);
98
99/* Initialization, Query, Setup, and Redrawing functions */
100static int          QZ_VideoInit        (_THIS, SDL_PixelFormat *video_format);
101
102static SDL_Rect**   QZ_ListModes        (_THIS, SDL_PixelFormat *format,
103                                         Uint32 flags);
104static void         QZ_UnsetVideoMode   (_THIS, BOOL to_desktop, BOOL save_gl);
105
106static SDL_Surface* QZ_SetVideoMode     (_THIS, SDL_Surface *current,
107                                         int width, int height, int bpp,
108                                         Uint32 flags);
109static int          QZ_ToggleFullScreen (_THIS, int on);
110static int          QZ_SetColors        (_THIS, int first_color,
111                                         int num_colors, SDL_Color *colors);
112
113#if (MAC_OS_X_VERSION_MIN_REQUIRED < 1070)
114static int          QZ_LockDoubleBuffer   (_THIS, SDL_Surface *surface);
115static void         QZ_UnlockDoubleBuffer (_THIS, SDL_Surface *surface);
116static int          QZ_ThreadFlip         (_THIS);
117static int          QZ_FlipDoubleBuffer   (_THIS, SDL_Surface *surface);
118static void         QZ_DoubleBufferUpdate (_THIS, int num_rects, SDL_Rect *rects);
119static void         QZ_DirectUpdate     (_THIS, int num_rects, SDL_Rect *rects);
120#endif
121
122static void         QZ_UpdateRects      (_THIS, int num_rects, SDL_Rect *rects);
123static void         QZ_VideoQuit        (_THIS);
124
125static int  QZ_LockHWSurface(_THIS, SDL_Surface *surface);
126static void QZ_UnlockHWSurface(_THIS, SDL_Surface *surface);
127static int QZ_AllocHWSurface(_THIS, SDL_Surface *surface);
128static void QZ_FreeHWSurface (_THIS, SDL_Surface *surface);
129
130/* Bootstrap binding, enables entry point into the driver */
131VideoBootStrap QZ_bootstrap = {
132    "Quartz", "Mac OS X CoreGraphics", QZ_Available, QZ_CreateDevice
133};
134
135/* Disable compiler warnings we can't avoid. */
136#if (defined(__GNUC__) && (__GNUC__ >= 4))
137#  if (MAC_OS_X_VERSION_MAX_ALLOWED <= 1070)
138#    pragma GCC diagnostic ignored "-Wdeprecated-declarations"
139#  endif
140#endif
141
142static void QZ_ReleaseDisplayMode(_THIS, const void *moderef)
143{
144    /* we only own these references in the 10.6+ API. */
145#if (MAC_OS_X_VERSION_MAX_ALLOWED >= 1060)
146    if (use_new_mode_apis) {
147        CGDisplayModeRelease((CGDisplayModeRef) moderef);
148    }
149#endif
150}
151
152static void QZ_ReleaseDisplayModeList(_THIS, CFArrayRef mode_list)
153{
154    /* we only own these references in the 10.6+ API. */
155#if (MAC_OS_X_VERSION_MAX_ALLOWED >= 1060)
156    if (use_new_mode_apis) {
157        CFRelease(mode_list);
158    }
159#endif
160}
161
162
163/* Bootstrap functions */
164static int QZ_Available ()
165{
166    return 1;
167}
168
169static SDL_VideoDevice* QZ_CreateDevice (int device_index)
170{
171#pragma unused (device_index)
172
173    SDL_VideoDevice *device;
174    SDL_PrivateVideoData *hidden;
175
176    device = (SDL_VideoDevice*) SDL_malloc (sizeof (*device) );
177    hidden = (SDL_PrivateVideoData*) SDL_malloc (sizeof (*hidden) );
178
179    if (device == NULL || hidden == NULL)
180        SDL_OutOfMemory ();
181
182    SDL_memset (device, 0, sizeof (*device) );
183    SDL_memset (hidden, 0, sizeof (*hidden) );
184
185    device->hidden = hidden;
186
187    device->VideoInit        = QZ_VideoInit;
188    device->ListModes        = QZ_ListModes;
189    device->SetVideoMode     = QZ_SetVideoMode;
190    device->ToggleFullScreen = QZ_ToggleFullScreen;
191    device->UpdateMouse      = QZ_UpdateMouse;
192    device->SetColors        = QZ_SetColors;
193    /* device->UpdateRects      = QZ_UpdateRects; this is determined by SetVideoMode() */
194    device->VideoQuit        = QZ_VideoQuit;
195
196    device->LockHWSurface   = QZ_LockHWSurface;
197    device->UnlockHWSurface = QZ_UnlockHWSurface;
198    device->AllocHWSurface   = QZ_AllocHWSurface;
199    device->FreeHWSurface   = QZ_FreeHWSurface;
200
201    device->SetGamma     = QZ_SetGamma;
202    device->GetGamma     = QZ_GetGamma;
203    device->SetGammaRamp = QZ_SetGammaRamp;
204    device->GetGammaRamp = QZ_GetGammaRamp;
205
206    device->GL_GetProcAddress = QZ_GL_GetProcAddress;
207    device->GL_GetAttribute   = QZ_GL_GetAttribute;
208    device->GL_MakeCurrent    = QZ_GL_MakeCurrent;
209    device->GL_SwapBuffers    = QZ_GL_SwapBuffers;
210    device->GL_LoadLibrary    = QZ_GL_LoadLibrary;
211
212    device->FreeWMCursor   = QZ_FreeWMCursor;
213    device->CreateWMCursor = QZ_CreateWMCursor;
214    device->ShowWMCursor   = QZ_ShowWMCursor;
215    device->WarpWMCursor   = QZ_WarpWMCursor;
216    device->MoveWMCursor   = QZ_MoveWMCursor;
217    device->CheckMouseMode = QZ_CheckMouseMode;
218    device->InitOSKeymap   = QZ_InitOSKeymap;
219    device->PumpEvents     = QZ_PumpEvents;
220
221    device->SetCaption    = QZ_SetCaption;
222    device->SetIcon       = QZ_SetIcon;
223    device->IconifyWindow = QZ_IconifyWindow;
224    device->SetWindowPos  = QZ_SetWindowPos;
225    device->GetWindowPos  = QZ_GetWindowPos;
226    device->IsWindowVisible = QZ_IsWindowVisible;
227    device->GetMonitorDPI = QZ_GetMonitorDPI;
228    device->GetMonitorRect = QZ_GetMonitorRect;
229    device->GetWMInfo     = QZ_GetWMInfo;
230    device->GrabInput     = QZ_GrabInput;
231
232    /*
233     * This is a big hassle, needing QuickDraw and QuickTime on older
234     *  systems, and god knows what on 10.6, so we immediately fail here,
235     *  which causes SDL to make an RGB surface and manage the YUV overlay
236     *  in software. Sorry. Use SDL 1.3 if you want YUV rendering in a pixel
237     *  shader.  :)
238     */
239    /*device->CreateYUVOverlay = QZ_CreateYUVOverlay;*/
240
241    device->free             = QZ_DeleteDevice;
242
243    return device;
244}
245
246static void QZ_DeleteDevice (SDL_VideoDevice *device)
247{
248    _THIS = device;
249    QZ_ReleaseDisplayMode(this, save_mode);
250    QZ_ReleaseDisplayMode(this, mode);
251    SDL_free (device->hidden);
252    SDL_free (device);
253}
254
255static void QZ_GetModeInfo(_THIS, const void *_mode, Uint32 *w, Uint32 *h, Uint32 *bpp)
256{
257    *w = *h = *bpp = 0;
258    if (_mode == NULL) {
259        return;
260    }
261
262#if (MAC_OS_X_VERSION_MAX_ALLOWED >= 1060)
263    if (use_new_mode_apis) {
264        CGDisplayModeRef vidmode = (CGDisplayModeRef) _mode;
265        CFStringRef fmt = CGDisplayModeCopyPixelEncoding(vidmode);
266
267        *w = (Uint32) CGDisplayModeGetWidth(vidmode);
268        *h = (Uint32) CGDisplayModeGetHeight(vidmode);
269
270        /* we only care about the 32-bit modes... */
271        if (CFStringCompare(fmt, CFSTR(IO32BitDirectPixels),
272                            kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
273            *bpp = 32;
274        }
275
276        CFRelease(fmt);
277    }
278#endif
279
280#if (MAC_OS_X_VERSION_MIN_REQUIRED < 1060)
281    if (!use_new_mode_apis) {
282        CFDictionaryRef vidmode = (CFDictionaryRef) _mode;
283        CFNumberGetValue (
284            CFDictionaryGetValue (vidmode, kCGDisplayBitsPerPixel),
285            kCFNumberSInt32Type, bpp);
286
287        CFNumberGetValue (
288            CFDictionaryGetValue (vidmode, kCGDisplayWidth),
289            kCFNumberSInt32Type, w);
290
291        CFNumberGetValue (
292            CFDictionaryGetValue (vidmode, kCGDisplayHeight),
293            kCFNumberSInt32Type, h);
294    }
295#endif
296
297    /* we only care about the 32-bit modes... */
298    if (*bpp != 32) {
299        *bpp = 0;
300    }
301}
302
303static int QZ_VideoInit (_THIS, SDL_PixelFormat *video_format)
304{
305    NSRect r = NSMakeRect(0.0, 0.0, 0.0, 0.0);
306    const char *env = NULL;
307
308    if ( Gestalt(gestaltSystemVersion, &system_version) != noErr )
309        system_version = 0;
310
311    use_new_mode_apis = NO;
312
313#if (MAC_OS_X_VERSION_MAX_ALLOWED >= 1060)
314    use_new_mode_apis = IS_SNOW_LEOPARD_OR_LATER(this);
315#endif
316
317    /* Initialize the video settings; this data persists between mode switches */
318    display_id = kCGDirectMainDisplay;
319
320#if 0 /* The mouse event code needs to take this into account... */
321    env = getenv("SDL_VIDEO_FULLSCREEN_DISPLAY");
322    if ( env ) {
323        int monitor = SDL_atoi(env);
324    	CGDirectDisplayID activeDspys [3];
325    	CGDisplayCount dspyCnt;
326    	CGGetActiveDisplayList (3, activeDspys, &dspyCnt);
327        if ( monitor >= 0 && monitor < dspyCnt ) {
328    	    display_id = activeDspys[monitor];
329        }
330    }
331#endif
332
333#if (MAC_OS_X_VERSION_MAX_ALLOWED >= 1060)
334    if (use_new_mode_apis) {
335        save_mode = CGDisplayCopyDisplayMode(display_id);
336    }
337#endif
338
339#if (MAC_OS_X_VERSION_MIN_REQUIRED < 1060)
340    if (!use_new_mode_apis) {
341        save_mode = CGDisplayCurrentMode(display_id);
342    }
343#endif
344
345#if (MAC_OS_X_VERSION_MIN_REQUIRED < 1070)
346    if (!IS_LION_OR_LATER(this)) {
347        palette = CGPaletteCreateDefaultColorPalette();
348    }
349#endif
350
351    if (save_mode == NULL) {
352        SDL_SetError("Couldn't figure out current display mode.");
353        return -1;
354    }
355
356    /* Allow environment override of screensaver disable. */
357    env = SDL_getenv("SDL_VIDEO_ALLOW_SCREENSAVER");
358    if ( env ) {
359        allow_screensaver = SDL_atoi(env);
360    } else {
361#ifdef SDL_VIDEO_DISABLE_SCREENSAVER
362        allow_screensaver = 0;
363#else
364        allow_screensaver = 1;
365#endif
366    }
367
368    /* Gather some information that is useful to know about the display */
369    QZ_GetModeInfo(this, save_mode, &device_width, &device_height, &device_bpp);
370    if (device_bpp == 0) {
371        QZ_ReleaseDisplayMode(this, save_mode);
372        save_mode = NULL;
373        SDL_SetError("Unsupported display mode");
374        return -1;
375    }
376
377    /* Determine the current screen size */
378    this->info.current_w = device_width;
379    this->info.current_h = device_height;
380
381    /* Determine the default screen depth */
382    video_format->BitsPerPixel = device_bpp;
383
384    /* Set misc globals */
385    current_grab_mode = SDL_GRAB_OFF;
386    cursor_should_be_visible    = YES;
387    cursor_visible              = YES;
388    current_mods = 0;
389    field_edit =  [[SDLTranslatorResponder alloc] initWithFrame:r];
390
391    /* register for sleep notifications so wake from sleep generates SDL_VIDEOEXPOSE */
392    QZ_RegisterForSleepNotifications (this);
393
394    /* Fill in some window manager capabilities */
395    this->info.wm_available = 1;
396
397    return 0;
398}
399
400static SDL_Rect** QZ_ListModes (_THIS, SDL_PixelFormat *format, Uint32 flags)
401{
402    CFArrayRef mode_list = NULL;          /* list of available fullscreen modes */
403    CFIndex num_modes;
404    CFIndex i;
405
406    int list_size = 0;
407
408    /* Any windowed mode is acceptable */
409    if ( (flags & SDL_FULLSCREEN) == 0 )
410        return (SDL_Rect**)-1;
411
412    /* Free memory from previous call, if any */
413    if ( client_mode_list != NULL ) {
414        int i;
415
416        for (i = 0; client_mode_list[i] != NULL; i++)
417            SDL_free (client_mode_list[i]);
418
419        SDL_free (client_mode_list);
420        client_mode_list = NULL;
421    }
422
423#if (MAC_OS_X_VERSION_MAX_ALLOWED >= 1060)
424    if (use_new_mode_apis) {
425        mode_list = CGDisplayCopyAllDisplayModes(display_id, NULL);
426    }
427#endif
428
429#if (MAC_OS_X_VERSION_MIN_REQUIRED < 1060)
430    if (!use_new_mode_apis) {
431        mode_list = CGDisplayAvailableModes(display_id);
432    }
433#endif
434
435    num_modes = CFArrayGetCount (mode_list);
436
437    /* Build list of modes with the requested bpp */
438    for (i = 0; i < num_modes; i++) {
439        Uint32 width, height, bpp;
440        const void *onemode = CFArrayGetValueAtIndex(mode_list, i);
441
442        QZ_GetModeInfo(this, onemode, &width, &height, &bpp);
443
444        if (bpp && (bpp == format->BitsPerPixel)) {
445            int hasMode = SDL_FALSE;
446            int i;
447
448            /* Check if mode is already in the list */
449            for (i = 0; i < list_size; i++) {
450                if (client_mode_list[i]->w == width &&
451                    client_mode_list[i]->h == height) {
452                        hasMode = SDL_TRUE;
453                        break;
454                }
455            }
456
457            /* Grow the list and add mode to the list */
458            if ( ! hasMode ) {
459                SDL_Rect *rect;
460
461                list_size++;
462
463                if (client_mode_list == NULL)
464                    client_mode_list = (SDL_Rect**)
465                        SDL_malloc (sizeof(*client_mode_list) * (list_size+1) );
466                else {
467                    /* !!! FIXME: this leaks memory if SDL_realloc() fails! */
468                    client_mode_list = (SDL_Rect**)
469                        SDL_realloc (client_mode_list, sizeof(*client_mode_list) * (list_size+1));
470                }
471
472                rect = (SDL_Rect*) SDL_malloc (sizeof(**client_mode_list));
473
474                if (client_mode_list == NULL || rect == NULL) {
475                    QZ_ReleaseDisplayModeList(this, mode_list);
476                    SDL_OutOfMemory ();
477                    return NULL;
478                }
479
480                rect->x = rect->y = 0;
481                rect->w = width;
482                rect->h = height;
483
484                client_mode_list[list_size-1] = rect;
485                client_mode_list[list_size]   = NULL;
486            }
487        }
488    }
489
490    QZ_ReleaseDisplayModeList(this, mode_list);
491
492    /* Sort list largest to smallest (by area) */
493    {
494        int i, j;
495        for (i = 0; i < list_size; i++) {
496            for (j = 0; j < list_size-1; j++) {
497
498                int area1, area2;
499                area1 = client_mode_list[j]->w * client_mode_list[j]->h;
500                area2 = client_mode_list[j+1]->w * client_mode_list[j+1]->h;
501
502                if (area1 < area2) {
503                    SDL_Rect *tmp = client_mode_list[j];
504                    client_mode_list[j] = client_mode_list[j+1];
505                    client_mode_list[j+1] = tmp;
506                }
507            }
508        }
509    }
510
511    return client_mode_list;
512}
513
514static SDL_bool QZ_WindowPosition(_THIS, int *x, int *y)
515{
516    const char *window = getenv("SDL_VIDEO_WINDOW_POS");
517    if ( window ) {
518        if ( sscanf(window, "%d,%d", x, y) == 2 ) {
519            return SDL_TRUE;
520        }
521    }
522    return SDL_FALSE;
523}
524
525static CGError QZ_SetDisplayMode(_THIS, const void *vidmode)
526{
527#if (MAC_OS_X_VERSION_MAX_ALLOWED >= 1060)
528    if (use_new_mode_apis) {
529        return CGDisplaySetDisplayMode(display_id, (CGDisplayModeRef) vidmode, NULL);
530    }
531#endif
532
533#if (MAC_OS_X_VERSION_MIN_REQUIRED < 1060)
534    if (!use_new_mode_apis) {
535        return CGDisplaySwitchToMode(display_id, (CFDictionaryRef) vidmode);
536    }
537#endif
538
539    return kCGErrorFailure;
540}
541
542static inline CGError QZ_RestoreDisplayMode(_THIS)
543{
544    return QZ_SetDisplayMode(this, save_mode);
545}
546
547static void QZ_UnsetVideoMode (_THIS, BOOL to_desktop, BOOL save_gl)
548{
549    /* Reset values that may change between switches */
550    this->info.blit_fill  = 0;
551    this->FillHWRect      = NULL;
552    this->UpdateRects     = NULL;
553    this->LockHWSurface   = NULL;
554    this->UnlockHWSurface = NULL;
555
556    if (cg_context) {
557        CGContextFlush (cg_context);
558        CGContextRelease (cg_context);
559        cg_context = nil;
560    }
561
562    /* Release fullscreen resources */
563    if ( mode_flags & SDL_FULLSCREEN ) {
564
565        NSRect screen_rect;
566
567        /*  Release double buffer stuff */
568#if (MAC_OS_X_VERSION_MIN_REQUIRED < 1070)
569        if ( !IS_LION_OR_LATER(this) && (mode_flags & SDL_DOUBLEBUF) ) {
570            quit_thread = YES;
571            SDL_SemPost (sem1);
572            SDL_WaitThread (thread, NULL);
573            SDL_DestroySemaphore (sem1);
574            SDL_DestroySemaphore (sem2);
575            SDL_free (sw_buffers[0]);
576        }
577#endif
578
579        /* If we still have a valid window, close it. */
580        if ( qz_window ) {
581            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 */
582            [ qz_window close ]; /* includes release because [qz_window isReleasedWhenClosed] */
583            qz_window = nil;
584            window_view = nil;
585        }
586        /*
587            Release the OpenGL context
588            Do this first to avoid trash on the display before fade
589        */
590        if ( mode_flags & SDL_OPENGL ) {
591            if (!save_gl) {
592                QZ_TearDownOpenGL (this);
593            }
594
595            #ifdef __powerpc__  /* we only use this for pre-10.3 compatibility. */
596            CGLSetFullScreen (NULL);
597            #endif
598        }
599        if (to_desktop) {
600            /* !!! FIXME: keep an eye on this.
601             * This API is officially unavailable for 64-bit binaries.
602             *  It happens to work, as of 10.7, but we're going to see if
603             *  we can just simply do without it on newer OSes...
604             */
605            #if (MAC_OS_X_VERSION_MIN_REQUIRED < 1070) && !defined(__LP64__)
606            if ( !IS_LION_OR_LATER(this) ) {
607                ShowMenuBar ();
608            }
609            #endif
610
611            /* Restore original screen resolution/bpp */
612            QZ_RestoreDisplayMode (this);
613            CGReleaseAllDisplays ();
614            /*
615                Reset the main screen's rectangle
616                See comment in QZ_SetVideoFullscreen for why we do this
617            */
618            screen_rect = NSMakeRect(0,0,device_width,device_height);
619            QZ_SetFrame(this, [ NSScreen mainScreen ], screen_rect);
620        }
621    }
622    /* Release window mode resources */
623    else {
624        id delegate = [ qz_window delegate ];
625        [ qz_window close ]; /* includes release because [qz_window isReleasedWhenClosed] */
626        if (delegate != nil) [ delegate release ];
627        qz_window = nil;
628        window_view = nil;
629
630        /* Release the OpenGL context */
631        if ( mode_flags & SDL_OPENGL ) {
632            if (!save_gl) {
633                QZ_TearDownOpenGL (this);
634            }
635        }
636    }
637
638    /* Signal successful teardown */
639    video_set = SDL_FALSE;
640}
641
642static const void *QZ_BestMode(_THIS, const int bpp, const int w, const int h)
643{
644    const void *best = NULL;
645
646    if (bpp == 0) {
647        return NULL;
648    }
649
650#if (MAC_OS_X_VERSION_MAX_ALLOWED >= 1060)
651    if (use_new_mode_apis) {
652        /* apparently, we have to roll our own now. :/ */
653        CFArrayRef mode_list = CGDisplayCopyAllDisplayModes(display_id, NULL);
654        if (mode_list != NULL) {
655            const CFIndex num_modes = CFArrayGetCount(mode_list);
656            CFIndex i;
657            for (i = 0; i < num_modes; i++) {
658                const void *vidmode = CFArrayGetValueAtIndex(mode_list, i);
659                Uint32 thisw, thish, thisbpp;
660                QZ_GetModeInfo(this, vidmode, &thisw, &thish, &thisbpp);
661
662                /* We only care about exact matches, apparently. */
663                if ((thisbpp == bpp) && (thisw == w) && (thish == h)) {
664                    best = vidmode;
665                    break;  /* got it! */
666                }
667            }
668            CGDisplayModeRetain((CGDisplayModeRef) best);  /* NULL is ok */
669            CFRelease(mode_list);
670        }
671    }
672#endif
673
674#if (MAC_OS_X_VERSION_MIN_REQUIRED < 1060)
675    if (!use_new_mode_apis) {
676        boolean_t exact = 0;
677        best = CGDisplayBestModeForParameters(display_id, bpp, w, h, &exact);
678        if (!exact) {
679            best = NULL;
680        }
681    }
682#endif
683
684    return best;
685}
686
687static SDL_Surface* QZ_SetVideoFullScreen (_THIS, SDL_Surface *current, int width,
688                                           int height, int bpp, Uint32 flags,
689                                           const BOOL save_gl)
690{
691    const BOOL isLion = IS_LION_OR_LATER(this);
692    NSRect screen_rect;
693    CGError error;
694    NSRect contentRect;
695    CGDisplayFadeReservationToken fade_token = kCGDisplayFadeReservationInvalidToken;
696
697    current->flags = SDL_FULLSCREEN;
698    current->w = width;
699    current->h = height;
700
701    contentRect = NSMakeRect (0, 0, width, height);
702
703    /* Fade to black to hide resolution-switching flicker (and garbage
704       that is displayed by a destroyed OpenGL context, if applicable) */
705    if ( CGAcquireDisplayFadeReservation (5, &fade_token) == kCGErrorSuccess ) {
706        CGDisplayFade (fade_token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, TRUE);
707    }
708
709    /* Destroy any previous mode */
710    if (video_set == SDL_TRUE)
711        QZ_UnsetVideoMode (this, FALSE, save_gl);
712
713    /* Sorry, QuickDraw was ripped out. */
714    if (getenv("SDL_NSWindowPointer") || getenv("SDL_NSQuickDrawViewPointer")) {
715        SDL_SetError ("Embedded QuickDraw windows are no longer supported");
716        goto ERR_NO_MATCH;
717    }
718
719    QZ_ReleaseDisplayMode(this, mode);  /* NULL is okay. */
720
721    /* See if requested mode exists */
722    mode = QZ_BestMode(this, bpp, width, height);
723
724    /* Require an exact match to the requested mode */
725    if ( mode == NULL ) {
726        SDL_SetError ("Failed to find display resolution: %dx%dx%d", width, height, bpp);
727        goto ERR_NO_MATCH;
728    }
729
730    /* Put up the blanking window (a window above all other windows) */
731    if (getenv ("SDL_SINGLEDISPLAY"))
732        error = CGDisplayCapture (display_id);
733    else
734        error = CGCaptureAllDisplays ();
735
736    if ( CGDisplayNoErr != error ) {
737        SDL_SetError ("Failed capturing display");
738        goto ERR_NO_CAPTURE;
739    }
740
741    /* Do the physical switch */
742    if ( CGDisplayNoErr != QZ_SetDisplayMode(this, mode) ) {
743        SDL_SetError ("Failed switching display resolution");
744        goto ERR_NO_SWITCH;
745    }
746
747#if (MAC_OS_X_VERSION_MIN_REQUIRED < 1070)
748    if ( !isLion ) {
749        current->pixels = (Uint32*) CGDisplayBaseAddress (display_id);
750        current->pitch  = CGDisplayBytesPerRow (display_id);
751
752        current->flags |= SDL_HWSURFACE;
753        current->flags |= SDL_PREALLOC;
754        /* current->hwdata = (void *) CGDisplayGetDrawingContext (display_id); */
755
756        this->UpdateRects     = QZ_DirectUpdate;
757        this->LockHWSurface   = QZ_LockHWSurface;
758        this->UnlockHWSurface = QZ_UnlockHWSurface;
759
760        /* Setup double-buffer emulation */
761        if ( flags & SDL_DOUBLEBUF ) {
762
763            /*
764            Setup a software backing store for reasonable results when
765            double buffering is requested (since a single-buffered hardware
766            surface looks hideous).
767
768            The actual screen blit occurs in a separate thread to allow
769            other blitting while waiting on the VBL (and hence results in higher framerates).
770            */
771            this->LockHWSurface = NULL;
772            this->UnlockHWSurface = NULL;
773            this->UpdateRects = NULL;
774
775            current->flags |= (SDL_HWSURFACE|SDL_DOUBLEBUF);
776            this->UpdateRects = QZ_DoubleBufferUpdate;
777            this->LockHWSurface = QZ_LockDoubleBuffer;
778            this->UnlockHWSurface = QZ_UnlockDoubleBuffer;
779            this->FlipHWSurface = QZ_FlipDoubleBuffer;
780
781            current->pixels = SDL_malloc (current->pitch * current->h * 2);
782            if (current->pixels == NULL) {
783                SDL_OutOfMemory ();
784                goto ERR_DOUBLEBUF;
785            }
786
787            sw_buffers[0] = current->pixels;
788            sw_buffers[1] = (Uint8*)current->pixels + current->pitch * current->h;
789
790            quit_thread = NO;
791            sem1 = SDL_CreateSemaphore (0);
792            sem2 = SDL_CreateSemaphore (1);
793            thread = SDL_CreateThread ((int (*)(void *))QZ_ThreadFlip, this);
794        }
795
796        if ( CGDisplayCanSetPalette (display_id) )
797            current->flags |= SDL_HWPALETTE;
798    }
799#endif
800
801    /* Check if we should recreate the window */
802    if (qz_window == nil) {
803        /* Manually create a window, avoids having a nib file resource */
804        qz_window = [ [ SDL_QuartzWindow alloc ]
805            initWithContentRect:contentRect
806                styleMask:(isLion ? NSBorderlessWindowMask : 0)
807                    backing:NSBackingStoreBuffered
808                        defer:NO ];
809
810        if (qz_window != nil) {
811            [ qz_window setAcceptsMouseMovedEvents:YES ];
812            [ qz_window setViewsNeedDisplay:NO ];
813            if (isLion) {
814                [ qz_window setContentView: [ [ [ SDL_QuartzView alloc ] init ] autorelease ] ];
815            }
816        }
817    }
818    /* We already have a window, just change its size */
819    else {
820        [ qz_window setContentSize:contentRect.size ];
821        current->flags |= (SDL_NOFRAME|SDL_RESIZABLE) & mode_flags;
822        [ window_view setFrameSize:contentRect.size ];
823    }
824
825    /* Setup OpenGL for a fullscreen context */
826    if (flags & SDL_OPENGL) {
827
828        if ( ! save_gl ) {
829            if ( ! QZ_SetupOpenGL (this, bpp, flags) ) {
830                goto ERR_NO_GL;
831            }
832        }
833
834        /* Initialize the NSView and add it to our window.  The presence of a valid window and
835           view allow the cursor to be changed whilst in fullscreen.*/
836        window_view = [ [ NSView alloc ] initWithFrame:contentRect ];
837
838        if ( isLion ) {
839            [ window_view setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable ];
840        }
841
842        [ [ qz_window contentView ] addSubview:window_view ];
843
844        /* Apparently Lion checks some version flag set by the linker
845           and changes API behavior. Annoying. */
846        if ( isLion ) {
847            [ qz_window setLevel:CGShieldingWindowLevel() ];
848            [ gl_context setView: window_view ];
849            //[ gl_context setFullScreen ];
850            [ gl_context update ];
851        }
852
853#if (MAC_OS_X_VERSION_MIN_REQUIRED < 1070)
854        if ( !isLion ) {
855            CGLError err;
856            CGLContextObj ctx;
857
858            [ qz_window setLevel:NSNormalWindowLevel ];
859            ctx = QZ_GetCGLContextObj (gl_context);
860            err = CGLSetFullScreen (ctx);
861
862            if (err) {
863                SDL_SetError ("Error setting OpenGL fullscreen: %s", CGLErrorString(err));
864                goto ERR_NO_GL;
865            }
866        }
867#endif
868
869        [ window_view release ];
870        [ gl_context makeCurrentContext];
871
872        glClear (GL_COLOR_BUFFER_BIT);
873
874        [ gl_context flushBuffer ];
875
876        current->flags |= SDL_OPENGL;
877    } else if (isLion) {  /* For 2D, we build a CGBitmapContext */
878        CGColorSpaceRef cgColorspace;
879
880        /* Only recreate the view if it doesn't already exist */
881        if (window_view == nil) {
882            window_view = [ [ NSView alloc ] initWithFrame:contentRect ];
883            [ window_view setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable ];
884            [ [ qz_window contentView ] addSubview:window_view ];
885            [ window_view release ];
886        }
887
888        cgColorspace = CGColorSpaceCreateDeviceRGB();
889        current->pitch = 4 * current->w;
890        current->pixels = SDL_malloc (current->h * current->pitch);
891
892        cg_context = CGBitmapContextCreate (current->pixels, current->w, current->h,
893                        8, current->pitch, cgColorspace,
894                        kCGImageAlphaNoneSkipFirst);
895        CGColorSpaceRelease (cgColorspace);
896
897        current->flags |= SDL_SWSURFACE;
898        current->flags |= SDL_ASYNCBLIT;
899        current->hwdata = (void *) cg_context;
900
901        /* Force this window to draw above _everything_. */
902        [ qz_window setLevel:CGShieldingWindowLevel() ];
903
904        this->UpdateRects     = QZ_UpdateRects;
905        this->LockHWSurface   = QZ_LockHWSurface;
906        this->UnlockHWSurface = QZ_UnlockHWSurface;
907    }
908
909    if (isLion) {
910        [ qz_window setHasShadow:NO];
911        [ qz_window setOpaque:YES];
912        [ qz_window makeKeyAndOrderFront:nil ];
913    }
914
915    /* !!! FIXME: keep an eye on this.
916     * This API is officially unavailable for 64-bit binaries.
917     *  It happens to work, as of 10.7, but we're going to see if
918     *  we can just simply do without it on newer OSes...
919     */
920    #if (MAC_OS_X_VERSION_MIN_REQUIRED < 1070) && !defined(__LP64__)
921    if ( !isLion ) {
922        /* If we don't hide menu bar, it will get events and interrupt the program */
923        HideMenuBar ();
924    }
925    #endif
926
927    /* Fade in again (asynchronously) */
928    if ( fade_token != kCGDisplayFadeReservationInvalidToken ) {
929        CGDisplayFade (fade_token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0.0, 0.0, 0.0, FALSE);
930        CGReleaseDisplayFadeReservation(fade_token);
931    }
932
933    /*
934        There is a bug in Cocoa where NSScreen doesn't synchronize
935        with CGDirectDisplay, so the main screen's frame is wrong.
936        As a result, coordinate translation produces incorrect results.
937        We can hack around this bug by setting the screen rect
938        ourselves. This hack should be removed if/when the bug is fixed.
939    */
940    screen_rect = NSMakeRect(0,0,width,height);
941    QZ_SetFrame(this, [ NSScreen mainScreen ], screen_rect);
942
943    /* Save the flags to ensure correct tear-down */
944    mode_flags = current->flags;
945
946    /* Set app state, hide cursor if necessary, ... */
947    QZ_DoActivate(this);
948
949    return current;
950
951    /* Since the blanking window covers *all* windows (even force quit) correct recovery is crucial */
952ERR_NO_GL:      goto ERR_DOUBLEBUF;  /* this goto is to stop a compiler warning on newer SDKs. */
953ERR_DOUBLEBUF:  QZ_RestoreDisplayMode(this);
954ERR_NO_SWITCH:  CGReleaseAllDisplays ();
955ERR_NO_CAPTURE:
956ERR_NO_MATCH:   if ( fade_token != kCGDisplayFadeReservationInvalidToken ) {
957                    CGDisplayFade (fade_token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0.0, 0.0, 0.0, FALSE);
958                    CGReleaseDisplayFadeReservation (fade_token);
959                }
960                return NULL;
961}
962
963static SDL_Surface* QZ_SetVideoWindowed (_THIS, SDL_Surface *current, int width,
964                                         int height, int *bpp, Uint32 flags,
965                                         const BOOL save_gl)
966{
967    unsigned int style;
968    NSRect contentRect;
969    int center_window = 1;
970    int origin_x, origin_y;
971    CGDisplayFadeReservationToken fade_token = kCGDisplayFadeReservationInvalidToken;
972
973    current->flags = 0;
974    current->w = width;
975    current->h = height;
976
977    contentRect = NSMakeRect (0, 0, width, height);
978
979    /*
980        Check if we should completely destroy the previous mode
981        - If it is fullscreen
982        - If it has different noframe or resizable attribute
983        - If it is OpenGL (since gl attributes could be different)
984        - If new mode is OpenGL, but previous mode wasn't
985    */
986    if (video_set == SDL_TRUE) {
987        if (mode_flags & SDL_FULLSCREEN) {
988            /* Fade to black to hide resolution-switching flicker (and garbage
989               that is displayed by a destroyed OpenGL context, if applicable) */
990            if (CGAcquireDisplayFadeReservation (5, &fade_token) == kCGErrorSuccess) {
991                CGDisplayFade (fade_token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, TRUE);
992            }
993            QZ_UnsetVideoMode (this, TRUE, save_gl);
994        }
995        else if ( ((mode_flags ^ flags) & (SDL_NOFRAME|SDL_RESIZABLE)) ||
996                  (mode_flags & SDL_OPENGL) ||
997                  (flags & SDL_OPENGL) ) {
998            QZ_UnsetVideoMode (this, TRUE, save_gl);
999        }
1000    }
1001
1002    /* Sorry, QuickDraw was ripped out. */
1003    if (getenv("SDL_NSWindowPointer") || getenv("SDL_NSQuickDrawViewPointer")) {
1004        SDL_SetError ("Embedded QuickDraw windows are no longer supported");
1005        if (fade_token != kCGDisplayFadeReservationInvalidToken) {
1006            CGDisplayFade (fade_token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0.0, 0.0, 0.0, FALSE);
1007            CGReleaseDisplayFadeReservation (fade_token);
1008        }
1009        return NULL;
1010    }
1011
1012    /* Check if we should recreate the window */
1013    if (qz_window == nil) {
1014
1015        /* Set the window style based on input flags */
1016        if ( flags & SDL_NOFRAME ) {
1017            style = NSBorderlessWindowMask;
1018            current->flags |= SDL_NOFRAME;
1019        } else {
1020            style = NSTitledWindowMask;
1021            style |= (NSMiniaturizableWindowMask | NSClosableWindowMask);
1022            if ( flags & SDL_RESIZABLE ) {
1023                style |= NSResizableWindowMask;
1024                current->flags |= SDL_RESIZABLE;
1025            }
1026        }
1027
1028        /* Manually create a window, avoids having a nib file resource */
1029        qz_window = [ [ SDL_QuartzWindow alloc ]
1030            initWithContentRect:contentRect
1031                styleMask:style
1032                    backing:NSBackingStoreBuffered
1033                        defer:YES ];
1034
1035        if (qz_window == nil) {
1036            SDL_SetError ("Could not create the Cocoa window");
1037            if (fade_token != kCGDisplayFadeReservationInvalidToken) {
1038                CGDisplayFade (fade_token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0.0, 0.0, 0.0, FALSE);
1039                CGReleaseDisplayFadeReservation (fade_token);
1040            }
1041            return NULL;
1042        }
1043
1044        /*[ qz_window setReleasedWhenClosed:YES ];*/ /* no need to set this as it's the default for NSWindows */
1045        QZ_SetCaption(this, this->wm_title, this->wm_icon);
1046        [ qz_window setAcceptsMouseMovedEvents:YES ];
1047        [ qz_window setViewsNeedDisplay:NO ];
1048
1049        if ( QZ_WindowPosition(this, &origin_x, &origin_y) ) {
1050            /* have to flip the Y value (NSPoint is lower left corner origin) */
1051            [ qz_window setFrameTopLeftPoint:NSMakePoint((float) origin_x, (float) (this->info.current_h - origin_y))];
1052            center_window = 0;
1053        } else if ( center_window ) {
1054            [ qz_window center ];
1055        }
1056
1057        [ qz_window setDelegate:
1058            [ [ SDL_QuartzWindowDelegate alloc ] init ] ];
1059        [ qz_window setContentView: [ [ [ SDL_QuartzView alloc ] init ] autorelease ] ];
1060    }
1061    /* We already have a window, just change its size */
1062    else {
1063        [ qz_window setContentSize:contentRect.size ];
1064        current->flags |= (SDL_NOFRAME|SDL_RESIZABLE) & mode_flags;
1065        [ window_view setFrameSize:contentRect.size ];
1066    }
1067
1068    /* For OpenGL, we bind the context to a subview */
1069    if ( flags & SDL_OPENGL ) {
1070
1071        if ( ! save_gl ) {
1072            if ( ! QZ_SetupOpenGL (this, *bpp, flags) ) {
1073                if (fade_token != kCGDisplayFadeReservationInvalidToken) {
1074                    CGDisplayFade (fade_token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0.0, 0.0, 0.0, FALSE);
1075                    CGReleaseDisplayFadeReservation (fade_token);
1076                }
1077                return NULL;
1078            }
1079        }
1080
1081        window_view = [ [ NSView alloc ] initWithFrame:contentRect ];
1082        [ window_view setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable ];
1083        [ [ qz_window contentView ] addSubview:window_view ];
1084        [ gl_context setView: window_view ];
1085        [ window_view release ];
1086        [ gl_context makeCurrentContext];
1087        [ qz_window makeKeyAndOrderFront:nil ];
1088        current->flags |= SDL_OPENGL;
1089    }
1090    /* For 2D, we build a CGBitmapContext */
1091    else {
1092        CGColorSpaceRef cgColorspace;
1093
1094        /* Only recreate the view if it doesn't already exist */
1095        if (window_view == nil) {
1096
1097            window_view = [ [ NSView alloc ] initWithFrame:contentRect ];
1098            [ window_view setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable ];
1099            [ [ qz_window contentView ] addSubview:window_view ];
1100            [ window_view release ];
1101            [ qz_window makeKeyAndOrderFront:nil ];
1102        }
1103
1104        cgColorspace = CGColorSpaceCreateDeviceRGB();
1105        current->pitch = 4 * current->w;
1106        current->pixels = SDL_malloc (current->h * current->pitch);
1107
1108        cg_context = CGBitmapContextCreate (current->pixels, current->w, current->h,
1109                        8, current->pitch, cgColorspace,
1110                        kCGImageAlphaNoneSkipFirst);
1111        CGColorSpaceRelease (cgColorspace);
1112
1113        current->flags |= SDL_SWSURFACE;
1114        current->flags |= SDL_ASYNCBLIT;
1115        current->hwdata = (void *) cg_context;
1116
1117        this->UpdateRects     = QZ_UpdateRects;
1118        this->LockHWSurface   = QZ_LockHWSurface;
1119        this->UnlockHWSurface = QZ_UnlockHWSurface;
1120    }
1121
1122    /* Save flags to ensure correct teardown */
1123    mode_flags = current->flags;
1124
1125    /* Fade in again (asynchronously) if we came from a fullscreen mode and faded to black */
1126    if (fade_token != kCGDisplayFadeReservationInvalidToken) {
1127        CGDisplayFade (fade_token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0.0, 0.0, 0.0, FALSE);
1128        CGReleaseDisplayFadeReservation (fade_token);
1129    }
1130
1131    return current;
1132}
1133
1134
1135static SDL_Surface* QZ_SetVideoModeInternal (_THIS, SDL_Surface *current,
1136                                             int width, int height, int bpp,
1137                                             Uint32 flags, BOOL save_gl)
1138{
1139    const BOOL isLion = IS_LION_OR_LATER(this);
1140
1141    current->flags = 0;
1142    current->pixels = NULL;
1143
1144    /* Setup full screen video */
1145    if ( flags & SDL_FULLSCREEN ) {
1146        if ( isLion ) {
1147            bpp = 32;
1148        }
1149        current = QZ_SetVideoFullScreen (this, current, width, height, bpp, flags, save_gl );
1150        if (current == NULL)
1151            return NULL;
1152    }
1153    /* Setup windowed video */
1154    else {
1155        /* Force bpp to 32 */
1156        bpp = 32;
1157        current = QZ_SetVideoWindowed (this, current, width, height, &bpp, flags, save_gl );
1158        if (current == NULL)
1159            return NULL;
1160    }
1161
1162    if (qz_window != nil) {
1163        nsgfx_context = [NSGraphicsContext graphicsContextWithWindow:qz_window];
1164        [NSGraphicsContext setCurrentContext:nsgfx_context];
1165    }
1166
1167    /* Setup the new pixel format */
1168    {
1169        int amask = 0,
1170        rmask = 0,
1171        gmask = 0,
1172        bmask = 0;
1173
1174        switch (bpp) {
1175            case 16:   /* (1)-5-5-5 RGB */
1176                amask = 0;
1177                rmask = 0x7C00;
1178                gmask = 0x03E0;
1179                bmask = 0x001F;
1180                break;
1181            case 24:
1182                SDL_SetError ("24bpp is not available");
1183                return NULL;
1184            case 32:   /* (8)-8-8-8 ARGB */
1185                amask = 0x00000000;
1186                if ( (!isLion) && (flags & SDL_FULLSCREEN) ) {
1187                    rmask = 0x00FF0000;
1188                    gmask = 0x0000FF00;
1189                    bmask = 0x000000FF;
1190                } else {
1191#if SDL_BYTEORDER == SDL_LIL_ENDIAN
1192                    rmask = 0x0000FF00;
1193                    gmask = 0x00FF0000;
1194                    bmask = 0xFF000000;
1195#else
1196                    rmask = 0x00FF0000;
1197                    gmask = 0x0000FF00;
1198                    bmask = 0x000000FF;
1199#endif
1200                    break;
1201                }
1202        }
1203
1204        if ( ! SDL_ReallocFormat (current, bpp,
1205                                  rmask, gmask, bmask, amask ) ) {
1206            SDL_SetError ("Couldn't reallocate pixel format");
1207            return NULL;
1208        }
1209    }
1210
1211    /* Signal successful completion (used internally) */
1212    video_set = SDL_TRUE;
1213
1214    return current;
1215}
1216
1217static SDL_Surface* QZ_SetVideoMode(_THIS, SDL_Surface *current,
1218                                    int width, int height, int bpp,
1219                                    Uint32 flags)
1220{
1221    /* Don't throw away the GL context if we can just resize the current one. */
1222#if 0  /* !!! FIXME: half-finished side project. Reenable this if you ever debug the corner cases. */
1223    const BOOL save_gl = ( (video_set == SDL_TRUE) && ((flags & SDL_OPENGL) == (current->flags & SDL_OPENGL)) && (bpp == current->format->BitsPerPixel) );
1224#else
1225    const BOOL save_gl = NO;
1226#endif
1227
1228    NSOpenGLContext *glctx = gl_context;
1229    SDL_Surface* retval = NULL;
1230
1231    if (save_gl) {
1232        [glctx retain];  /* just so we don't lose this when killing old views, etc */
1233    }
1234
1235    retval = QZ_SetVideoModeInternal (this, current, width, height, bpp, flags, save_gl);
1236
1237    if (save_gl) {
1238        [glctx release];  /* something else should own this now, or we legitimately release it. */
1239    }
1240
1241    return retval;
1242}
1243
1244
1245static int QZ_ToggleFullScreen (_THIS, int on)
1246{
1247    return 0;
1248}
1249
1250static int QZ_SetColors (_THIS, int first_color, int num_colors,
1251                         SDL_Color *colors)
1252{
1253#if (MAC_OS_X_VERSION_MIN_REQUIRED < 1070)
1254    /* we shouldn't have an 8-bit mode on Lion! */
1255    if (!IS_LION_OR_LATER(this)) {
1256        CGTableCount  index;
1257        CGDeviceColor color;
1258
1259        for (index = first_color; index < first_color+num_colors; index++) {
1260
1261            /* Clamp colors between 0.0 and 1.0 */
1262            color.red   = colors->r / 255.0;
1263            color.blue  = colors->b / 255.0;
1264            color.green = colors->g / 255.0;
1265
1266            colors++;
1267
1268            CGPaletteSetColorAtIndex (palette, color, index);
1269        }
1270
1271        return ( CGDisplayNoErr == CGDisplaySetPalette (display_id, palette) );
1272    }
1273#endif
1274
1275    return 0;
1276}
1277
1278#if (MAC_OS_X_VERSION_MIN_REQUIRED < 1070)
1279static int QZ_LockDoubleBuffer (_THIS, SDL_Surface *surface)
1280{
1281    return 1;
1282}
1283
1284static void QZ_UnlockDoubleBuffer (_THIS, SDL_Surface *surface)
1285{
1286}
1287
1288/* The VBL delay is based on code by Ian R Ollmann's RezLib <iano@cco.caltech.edu> */
1289static AbsoluteTime QZ_SecondsToAbsolute ( double seconds )
1290{
1291    union
1292    {
1293        UInt64 i;
1294        Nanoseconds ns;
1295    } temp;
1296
1297    temp.i = seconds * 1000000000.0;
1298
1299    return NanosecondsToAbsolute ( temp.ns );
1300}
1301
1302static int QZ_ThreadFlip (_THIS)
1303{
1304    Uint8 *src, *dst;
1305    int skip, len, h;
1306
1307    /*
1308        Give this thread the highest scheduling priority possible,
1309        in the hopes that it will immediately run after the VBL delay
1310    */
1311    {
1312        pthread_t current_thread;
1313        int policy;
1314        struct sched_param param;
1315
1316        current_thread = pthread_self ();
1317        pthread_getschedparam (current_thread, &policy, &param);
1318        policy = SCHED_RR;
1319        param.sched_priority = sched_get_priority_max (policy);
1320        pthread_setschedparam (current_thread, policy, &param);
1321    }
1322
1323    while (1) {
1324
1325        SDL_SemWait (sem1);
1326        if (quit_thread)
1327            return 0;
1328
1329        /*
1330         * We have to add SDL_VideoSurface->offset here, since we might be a
1331         *  smaller surface in the center of the framebuffer (you asked for
1332         *  a fullscreen resolution smaller than the hardware could supply
1333         *  so SDL is centering it in a bigger resolution)...
1334         */
1335        dst = ((Uint8 *)((size_t)CGDisplayBaseAddress (display_id))) + SDL_VideoSurface->offset;
1336        src = current_buffer + SDL_VideoSurface->offset;
1337        len = SDL_VideoSurface->w * SDL_VideoSurface->format->BytesPerPixel;
1338        h = SDL_VideoSurface->h;
1339        skip = SDL_VideoSurface->pitch;
1340
1341        /* Wait for the VBL to occur (estimated since we don't have a hardware interrupt) */
1342        {
1343
1344            /* The VBL delay is based on Ian Ollmann's RezLib <iano@cco.caltech.edu> */
1345            double refreshRate;
1346            double linesPerSecond;
1347            double target;
1348            double position;
1349            double adjustment;
1350            AbsoluteTime nextTime;
1351            CFNumberRef refreshRateCFNumber;
1352
1353            refreshRateCFNumber = CFDictionaryGetValue (mode, kCGDisplayRefreshRate);
1354            if ( NULL == refreshRateCFNumber ) {
1355                SDL_SetError ("Mode has no refresh rate");
1356                goto ERROR;
1357            }
1358
1359            if ( 0 == CFNumberGetValue (refreshRateCFNumber, kCFNumberDoubleType, &refreshRate) ) {
1360                SDL_SetError ("Error getting refresh rate");
1361                goto ERROR;
1362            }
1363
1364            if ( 0 == refreshRate ) {
1365
1366               SDL_SetError ("Display has no refresh rate, using 60hz");
1367
1368                /* ok, for LCD's we'll emulate a 60hz refresh, which may or may not look right */
1369                refreshRate = 60.0;
1370            }
1371
1372            linesPerSecond = refreshRate * h;
1373            target = h;
1374
1375            /* Figure out the first delay so we start off about right */
1376            position = CGDisplayBeamPosition (display_id);
1377            if (position > target)
1378                position = 0;
1379
1380            adjustment = (target - position) / linesPerSecond;
1381
1382            nextTime = AddAbsoluteToAbsolute (UpTime (), QZ_SecondsToAbsolute (adjustment));
1383
1384            MPDelayUntil (&nextTime);
1385        }
1386
1387
1388        /* On error, skip VBL delay */
1389        ERROR:
1390
1391        /* TODO: use CGContextDrawImage here too!  Create two CGContextRefs the same way we
1392           create two buffers, replace current_buffer with current_context and set it
1393           appropriately in QZ_FlipDoubleBuffer.  */
1394        while ( h-- ) {
1395
1396            SDL_memcpy (dst, src, len);
1397            src += skip;
1398            dst += skip;
1399        }
1400
1401        /* signal flip completion */
1402        SDL_SemPost (sem2);
1403    }
1404
1405    return 0;
1406}
1407
1408static int QZ_FlipDoubleBuffer (_THIS, SDL_Surface *surface)
1409{
1410    /* wait for previous flip to complete */
1411    SDL_SemWait (sem2);
1412
1413    current_buffer = surface->pixels;
1414
1415    if (surface->pixels == sw_buffers[0])
1416        surface->pixels = sw_buffers[1];
1417    else
1418        surface->pixels = sw_buffers[0];
1419
1420    /* signal worker thread to do the flip */
1421    SDL_SemPost (sem1);
1422
1423    return 0;
1424}
1425
1426static void QZ_DoubleBufferUpdate (_THIS, int num_rects, SDL_Rect *rects)
1427{
1428    /* perform a flip if someone calls updaterects on a doublebuferred surface */
1429    this->FlipHWSurface (this, SDL_VideoSurface);
1430}
1431
1432static void QZ_DirectUpdate (_THIS, int num_rects, SDL_Rect *rects)
1433{
1434#pragma unused(this,num_rects,rects)
1435}
1436#endif
1437
1438/* Resize icon, BMP format */
1439static const unsigned char QZ_ResizeIcon[] = {
1440    0x42,0x4d,0x31,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x36,0x00,0x00,0x00,0x28,0x00,
1441    0x00,0x00,0x0d,0x00,0x00,0x00,0x0d,0x00,0x00,0x00,0x01,0x00,0x18,0x00,0x00,0x00,
1442    0x00,0x00,0xfb,0x01,0x00,0x00,0x13,0x0b,0x00,0x00,0x13,0x0b,0x00,0x00,0x00,0x00,
1443    0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
1444    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
1445    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0b,0xff,0xff,
1446    0xff,0xda,0xda,0xda,0x87,0x87,0x87,0xe8,0xe8,0xe8,0xff,0xff,0xff,0xda,0xda,0xda,
1447    0x87,0x87,0x87,0xe8,0xe8,0xe8,0xff,0xff,0xff,0xda,0xda,0xda,0x87,0x87,0x87,0xe8,
1448    0xe8,0xe8,0xff,0xff,0xff,0x0b,0xff,0xff,0xff,0xff,0xff,0xff,0xda,0xda,0xda,0x87,
1449    0x87,0x87,0xe8,0xe8,0xe8,0xff,0xff,0xff,0xda,0xda,0xda,0x87,0x87,0x87,0xe8,0xe8,
1450    0xe8,0xff,0xff,0xff,0xda,0xda,0xda,0x87,0x87,0x87,0xff,0xff,0xff,0x0b,0xff,0xff,
1451    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xd5,0xd5,0xd5,0x87,0x87,0x87,0xe8,0xe8,0xe8,
1452    0xff,0xff,0xff,0xda,0xda,0xda,0x87,0x87,0x87,0xe8,0xe8,0xe8,0xff,0xff,0xff,0xda,
1453    0xda,0xda,0xff,0xff,0xff,0x0b,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
1454    0xff,0xff,0xd7,0xd7,0xd7,0x87,0x87,0x87,0xe8,0xe8,0xe8,0xff,0xff,0xff,0xda,0xda,
1455    0xda,0x87,0x87,0x87,0xe8,0xe8,0xe8,0xff,0xff,0xff,0xff,0xff,0xff,0x0b,0xff,0xff,
1456    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xd7,0xd7,0xd7,
1457    0x87,0x87,0x87,0xe8,0xe8,0xe8,0xff,0xff,0xff,0xda,0xda,0xda,0x87,0x87,0x87,0xe8,
1458    0xe8,0xe8,0xff,0xff,0xff,0x0b,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
1459    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xd7,0xd7,0xd7,0x87,0x87,0x87,0xe8,0xe8,
1460    0xe8,0xff,0xff,0xff,0xdc,0xdc,0xdc,0x87,0x87,0x87,0xff,0xff,0xff,0x0b,0xff,0xff,
1461    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
1462    0xff,0xff,0xff,0xd9,0xd9,0xd9,0x87,0x87,0x87,0xe8,0xe8,0xe8,0xff,0xff,0xff,0xdc,
1463    0xdc,0xdc,0xff,0xff,0xff,0x0b,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
1464    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xdb,0xdb,
1465    0xdb,0x87,0x87,0x87,0xe8,0xe8,0xe8,0xff,0xff,0xff,0xff,0xff,0xff,0x0b,0xff,0xff,
1466    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
1467    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xdb,0xdb,0xdb,0x87,0x87,0x87,0xe8,
1468    0xe8,0xe8,0xff,0xff,0xff,0x0b,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
1469    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
1470    0xff,0xff,0xff,0xff,0xdc,0xdc,0xdc,0x87,0x87,0x87,0xff,0xff,0xff,0x0b,0xff,0xff,
1471    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
1472    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xdc,
1473    0xdc,0xdc,0xff,0xff,0xff,0x0b,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
1474    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
1475    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0b
1476};
1477
1478static void QZ_DrawResizeIcon (_THIS)
1479{
1480    /* Check if we should draw the resize icon */
1481    if (SDL_VideoSurface->flags & SDL_RESIZABLE) {
1482
1483        SDL_Rect icon_rect;
1484
1485        /* Create the icon image */
1486        if (resize_icon == NULL) {
1487
1488            SDL_RWops *rw;
1489            SDL_Surface *tmp;
1490
1491            rw = SDL_RWFromConstMem (QZ_ResizeIcon, sizeof(QZ_ResizeIcon));
1492            tmp = SDL_LoadBMP_RW (rw, SDL_TRUE);
1493
1494            resize_icon = SDL_ConvertSurface (tmp, SDL_VideoSurface->format, SDL_SRCCOLORKEY);
1495            SDL_SetColorKey (resize_icon, SDL_SRCCOLORKEY, 0xFFFFFF);
1496
1497            SDL_FreeSurface (tmp);
1498        }
1499
1500        icon_rect.x = SDL_VideoSurface->w - 13;
1501        icon_rect.y = SDL_VideoSurface->h - 13;
1502        icon_rect.w = 13;
1503        icon_rect.h = 13;
1504
1505        SDL_BlitSurface (resize_icon, NULL, SDL_VideoSurface, &icon_rect);
1506    }
1507}
1508
1509static void QZ_UpdateRects (_THIS, int numRects, SDL_Rect *rects)
1510{
1511    if (SDL_VideoSurface->flags & SDL_OPENGLBLIT) {
1512        QZ_GL_SwapBuffers (this);
1513    }
1514    else if ( [ qz_window isMiniaturized ] ) {
1515
1516        /* Do nothing if miniaturized */
1517    }
1518
1519    else {
1520        NSGraphicsContext *ctx = [NSGraphicsContext currentContext];
1521        if (ctx != nsgfx_context) { /* uhoh, you might be rendering from another thread... */
1522            [NSGraphicsContext setCurrentContext:nsgfx_context];
1523            ctx = nsgfx_context;
1524        }
1525        CGContextRef cgc = (CGContextRef) [ctx graphicsPort];
1526        QZ_DrawResizeIcon (this);
1527        CGContextFlush (cg_context);
1528        CGImageRef image = CGBitmapContextCreateImage (cg_context);
1529        CGRect rectangle = CGRectMake (0,0,[window_view frame].size.width,[window_view frame].size.height);
1530
1531        CGContextDrawImage (cgc, rectangle, image);
1532        CGImageRelease(image);
1533        CGContextFlush (cgc);
1534    }
1535}
1536
1537static void QZ_VideoQuit (_THIS)
1538{
1539    CGDisplayFadeReservationToken fade_token = kCGDisplayFadeReservationInvalidToken;
1540
1541    /* Restore gamma settings */
1542    CGDisplayRestoreColorSyncSettings ();
1543
1544    /* Ensure the cursor will be visible and working when we quit */
1545    CGDisplayShowCursor (display_id);
1546    CGAssociateMouseAndMouseCursorPosition (1);
1547
1548    if (mode_flags & SDL_FULLSCREEN) {
1549        /* Fade to black to hide resolution-switching flicker (and garbage
1550           that is displayed by a destroyed OpenGL context, if applicable) */
1551        if (CGAcquireDisplayFadeReservation (5, &fade_token) == kCGErrorSuccess) {
1552            CGDisplayFade (fade_token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, TRUE);
1553        }
1554        QZ_UnsetVideoMode (this, TRUE, FALSE);
1555        if (fade_token != kCGDisplayFadeReservationInvalidToken) {
1556            CGDisplayFade (fade_token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0.0, 0.0, 0.0, FALSE);
1557            CGReleaseDisplayFadeReservation (fade_token);
1558        }
1559    }
1560    else
1561        QZ_UnsetVideoMode (this, TRUE, FALSE);
1562
1563#if (MAC_OS_X_VERSION_MIN_REQUIRED < 1070)
1564    if (!IS_LION_OR_LATER(this)) {
1565        CGPaletteRelease(palette);
1566    }
1567#endif
1568
1569    if (opengl_library) {
1570        SDL_UnloadObject(opengl_library);
1571        opengl_library = NULL;
1572    }
1573    this->gl_config.driver_loaded = 0;
1574
1575    if (field_edit) {
1576        [field_edit release];
1577        field_edit = NULL;
1578    }
1579}
1580
1581static int  QZ_LockHWSurface(_THIS, SDL_Surface *surface)
1582{
1583    return 1;
1584}
1585
1586static void QZ_UnlockHWSurface(_THIS, SDL_Surface *surface)
1587{
1588}
1589
1590static int QZ_AllocHWSurface(_THIS, SDL_Surface *surface)
1591{
1592    return(-1); /* unallowed (no HWSURFACE support here). */
1593}
1594
1595static void QZ_FreeHWSurface (_THIS, SDL_Surface *surface)
1596{
1597}
1598
1599/* Gamma functions */
1600int QZ_SetGamma (_THIS, float red, float green, float blue)
1601{
1602    const CGGammaValue min = 0.0, max = 1.0;
1603
1604    if (red == 0.0)
1605        red = FLT_MAX;
1606    else
1607        red = 1.0 / red;
1608
1609    if (green == 0.0)
1610        green = FLT_MAX;
1611    else
1612        green = 1.0 / green;
1613
1614    if (blue == 0.0)
1615        blue = FLT_MAX;
1616    else
1617        blue  = 1.0 / blue;
1618
1619    if ( CGDisplayNoErr == CGSetDisplayTransferByFormula
1620         (display_id, min, max, red, min, max, green, min, max, blue) ) {
1621
1622        return 0;
1623    }
1624    else {
1625
1626        return -1;
1627    }
1628}
1629
1630int QZ_GetGamma (_THIS, float *red, float *green, float *blue)
1631{
1632    CGGammaValue dummy;
1633    if ( CGDisplayNoErr == CGGetDisplayTransferByFormula
1634         (display_id, &dummy, &dummy, red,
1635          &dummy, &dummy, green, &dummy, &dummy, blue) )
1636
1637        return 0;
1638    else
1639        return -1;
1640}
1641
1642int QZ_SetGammaRamp (_THIS, Uint16 *ramp)
1643{
1644    const uint32_t tableSize = 255;
1645    CGGammaValue redTable[tableSize];
1646    CGGammaValue greenTable[tableSize];
1647    CGGammaValue blueTable[tableSize];
1648
1649    int i;
1650
1651    /* Extract gamma values into separate tables, convert to floats between 0.0 and 1.0 */
1652    for (i = 0; i < 256; i++)
1653        redTable[i % 256] = ramp[i] / 65535.0;
1654
1655    for (i=256; i < 512; i++)
1656        greenTable[i % 256] = ramp[i] / 65535.0;
1657
1658    for (i=512; i < 768; i++)
1659        blueTable[i % 256] = ramp[i] / 65535.0;
1660
1661    if ( CGDisplayNoErr == CGSetDisplayTransferByTable
1662         (display_id, tableSize, redTable, greenTable, blueTable) )
1663        return 0;
1664    else
1665        return -1;
1666}
1667
1668int QZ_GetGammaRamp (_THIS, Uint16 *ramp)
1669{
1670    const uint32_t tableSize = 255;
1671    CGGammaValue redTable[tableSize];
1672    CGGammaValue greenTable[tableSize];
1673    CGGammaValue blueTable[tableSize];
1674    uint32_t actual;
1675    int i;
1676
1677    if ( CGDisplayNoErr != CGGetDisplayTransferByTable
1678         (display_id, tableSize, redTable, greenTable, blueTable, &actual) ||
1679         actual != tableSize)
1680
1681        return -1;
1682
1683    /* Pack tables into one array, with values from 0 to 65535 */
1684    for (i = 0; i < 256; i++)
1685        ramp[i] = redTable[i % 256] * 65535.0;
1686
1687    for (i=256; i < 512; i++)
1688        ramp[i] = greenTable[i % 256] * 65535.0;
1689
1690    for (i=512; i < 768; i++)
1691        ramp[i] = blueTable[i % 256] * 65535.0;
1692
1693    return 0;
1694}
1695
1696