src/video/quartz/SDL_QuartzWM.m
author Sam Lantinga <slouken@libsdl.org>
Sun, 21 Nov 2004 00:57:47 +0000
changeset 983 7f08bd66f1ca
parent 952 6f5c8bd997aa
child 1192 54aa9aa32327
permissions -rw-r--r--
Date: Fri, 19 Nov 2004 06:23:53 -0800 (PST)
From: Eric Wing
Subject: OS X Mouse inversion problem fix (again)

Here's yet another patch for the OS X mouse inversion
problem. This should fix the problem once and for all.
I know I've said this before, but *This time for
sure!* :)

If you recall, my last patch broke the non-OpenGL
windowed code and caused the inversion to occur there
instead. Max submitted a patch that partially reverted
the changes back which included the os version hack
which is currently the most recent CVS.

Aaron Sullivan identified and reported to the mailing
list the other day, that the last partial regression
of the code broke OS X 10.2. Looking over the results,
I'm thinking that I was slightly more successful than
I thought at unifying the code. I think I was trying
to unify the code base for OpenGL and non-OpenGL
windowed modes for all versions of the OS. It looks
like I failed at at unifying the OpenGL and non-OpenGL
code, but I did succeed at unifying the OS versions.

Thus, we no longer need the hack for the OS version
checks. The partial regression still included an OS
check which is what broke things for < 10.3.

Attached is the patch for SDL_QuartzWM.m. It basically
is a half-line change that removes one of the two
checks that decides if the mouse coordinates need to
be inverted, i.e:

if (system_version >= 0x1030 &&
(SDL_VideoSurface->flags & SDL_OPENGL) )
becomes this:
if(SDL_VideoSurface->flags & SDL_OPENGL)

With Aaron's outstanding help, we have collectively
tested:

windowed OpenGL
windowed non-OpenGL
fullscreen OpenGL
fullscreen non-OpenGL

under OS X 10.2 (Jaguar), 10.3 (Panther), and 10.4
(Tiger).

We don't have access to 10.0 or 10.1, but since the
original problem didn't materialize until 10.3, I'm
hopeful that testing 10.2 is sufficient. And now that
the code is uniform, I'm also hoping we'll be safe
moving forward to deal with future revisions of the OS
with this issue.
     1 /*
     2     SDL - Simple DirectMedia Layer
     3     Copyright (C) 1997-2003  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 
    23 #include "SDL_QuartzVideo.h"
    24 
    25 
    26 struct WMcursor {
    27     Cursor curs;
    28 };
    29 
    30 void QZ_FreeWMCursor     (_THIS, WMcursor *cursor) { 
    31 
    32     if ( cursor != NULL )
    33         free (cursor);
    34 }
    35 
    36 /* Use the Carbon cursor routines for now */
    37 WMcursor*    QZ_CreateWMCursor   (_THIS, Uint8 *data, Uint8 *mask, 
    38                                          int w, int h, int hot_x, int hot_y) { 
    39     WMcursor *cursor;
    40     int row, bytes;
    41         
    42     /* Allocate the cursor memory */
    43     cursor = (WMcursor *)malloc(sizeof(WMcursor));
    44     if ( cursor == NULL ) {
    45         SDL_OutOfMemory();
    46         return(NULL);
    47     }
    48     memset(cursor, 0, sizeof(*cursor));
    49     
    50     if (w > 16)
    51         w = 16;
    52     
    53     if (h > 16)
    54         h = 16;
    55     
    56     bytes = (w+7)/8;
    57 
    58     for ( row=0; row<h; ++row ) {
    59         memcpy(&cursor->curs.data[row], data, bytes);
    60         data += bytes;
    61     }
    62     for ( row=0; row<h; ++row ) {
    63         memcpy(&cursor->curs.mask[row], mask, bytes);
    64         mask += bytes;
    65     }
    66     cursor->curs.hotSpot.h = hot_x;
    67     cursor->curs.hotSpot.v = hot_y;
    68     
    69     return(cursor);
    70 }
    71 
    72 void QZ_ShowMouse (_THIS) {
    73     if (!cursor_visible) {
    74         [ NSCursor unhide ];
    75         cursor_visible = YES;
    76     }
    77 }
    78 
    79 void QZ_HideMouse (_THIS) {
    80     BOOL isInGameWin = QZ_IsMouseInWindow (this);
    81     if (isInGameWin && cursor_visible) {
    82         [ NSCursor hide ];
    83         cursor_visible = NO;
    84     }
    85 }
    86 
    87 BOOL QZ_IsMouseInWindow (_THIS) {
    88     return (mode_flags & SDL_FULLSCREEN) ? true : NSPointInRect([ qz_window mouseLocationOutsideOfEventStream ], [ window_view frame ]);
    89 }
    90 
    91 int QZ_ShowWMCursor (_THIS, WMcursor *cursor) { 
    92 
    93     if ( cursor == NULL) {
    94         if ( cursor_should_be_visible ) {
    95             QZ_HideMouse (this);
    96             cursor_should_be_visible = NO;
    97             QZ_ChangeGrabState (this, QZ_HIDECURSOR);
    98         }
    99     }
   100     else {
   101         SetCursor(&cursor->curs);
   102         if ( ! cursor_should_be_visible ) {
   103             QZ_ShowMouse (this);
   104             cursor_should_be_visible = YES;
   105             QZ_ChangeGrabState (this, QZ_SHOWCURSOR);
   106         }
   107     }
   108 
   109     return 1;
   110 }
   111 
   112 /*
   113     Coordinate conversion functions, for convenience
   114     Cocoa sets the origin at the lower left corner of the window/screen
   115     SDL, CoreGraphics/WindowServer, and QuickDraw use the origin at the upper left corner
   116     The routines were written so they could be called before SetVideoMode() has finished;
   117     this might have limited usefulness at the moment, but the extra cost is trivial.
   118 */
   119 
   120 /* Convert Cocoa screen coordinate to Cocoa window coordinate */
   121 void QZ_PrivateGlobalToLocal (_THIS, NSPoint *p) {
   122 
   123     *p = [ qz_window convertScreenToBase:*p ];
   124 }
   125 
   126 
   127 /* Convert Cocoa window coordinate to Cocoa screen coordinate */
   128 void QZ_PrivateLocalToGlobal (_THIS, NSPoint *p) {
   129 
   130     *p = [ qz_window convertBaseToScreen:*p ];
   131 }
   132 
   133 /* Convert SDL coordinate to Cocoa coordinate */
   134 void QZ_PrivateSDLToCocoa (_THIS, NSPoint *p) {
   135 
   136     if ( CGDisplayIsCaptured (display_id) ) { /* capture signals fullscreen */
   137     
   138         p->y = CGDisplayPixelsHigh (display_id) - p->y;
   139     }
   140     else {
   141        
   142         *p = [ window_view convertPoint:*p toView: nil ];
   143     }
   144 }
   145 
   146 /* Convert Cocoa coordinate to SDL coordinate */
   147 void QZ_PrivateCocoaToSDL (_THIS, NSPoint *p) {
   148 
   149     if ( CGDisplayIsCaptured (display_id) ) { /* capture signals fullscreen */
   150     
   151         p->y = CGDisplayPixelsHigh (display_id) - p->y;
   152     }
   153     else {
   154 
   155         *p = [ window_view convertPoint:*p fromView: nil ];
   156         
   157         /* We need a workaround in OpenGL mode */
   158         if ( SDL_VideoSurface->flags & SDL_OPENGL ) {
   159             p->y = [window_view frame].size.height - p->y - 1;
   160         }
   161     }
   162 }
   163 
   164 /* Convert SDL coordinate to window server (CoreGraphics) coordinate */
   165 CGPoint QZ_PrivateSDLToCG (_THIS, NSPoint *p) {
   166     
   167     CGPoint cgp;
   168     
   169     if ( ! CGDisplayIsCaptured (display_id) ) { /* not captured => not fullscreen => local coord */
   170     
   171         int height;
   172         
   173         QZ_PrivateSDLToCocoa (this, p);
   174         QZ_PrivateLocalToGlobal (this, p);
   175         
   176         height = CGDisplayPixelsHigh (display_id);
   177         p->y = height - p->y;
   178     }
   179     
   180     cgp.x = p->x;
   181     cgp.y = p->y;
   182     
   183     return cgp;
   184 }
   185 
   186 #if 0 /* Dead code */
   187 /* Convert window server (CoreGraphics) coordinate to SDL coordinate */
   188 void QZ_PrivateCGToSDL (_THIS, NSPoint *p) {
   189             
   190     if ( ! CGDisplayIsCaptured (display_id) ) { /* not captured => not fullscreen => local coord */
   191     
   192         int height;
   193 
   194         /* Convert CG Global to Cocoa Global */
   195         height = CGDisplayPixelsHigh (display_id);
   196         p->y = height - p->y;
   197 
   198         QZ_PrivateGlobalToLocal (this, p);
   199         QZ_PrivateCocoaToSDL (this, p);
   200     }
   201 }
   202 #endif /* Dead code */
   203 
   204 void  QZ_PrivateWarpCursor (_THIS, int x, int y) {
   205     
   206     NSPoint p;
   207     CGPoint cgp;
   208     
   209     p = NSMakePoint (x, y);
   210     cgp = QZ_PrivateSDLToCG (this, &p);
   211     
   212     /* this is the magic call that fixes cursor "freezing" after warp */
   213     CGSetLocalEventsSuppressionInterval (0.0);
   214     CGWarpMouseCursorPosition (cgp);
   215 }
   216 
   217 void QZ_WarpWMCursor (_THIS, Uint16 x, Uint16 y) {
   218 
   219     /* Only allow warping when in foreground */
   220     if ( ! [ NSApp isActive ] )
   221         return;
   222             
   223     /* Do the actual warp */
   224     QZ_PrivateWarpCursor (this, x, y);
   225 
   226     /* Generate the mouse moved event */
   227     SDL_PrivateMouseMotion (0, 0, x, y);
   228 }
   229 
   230 void QZ_MoveWMCursor     (_THIS, int x, int y) { }
   231 void QZ_CheckMouseMode   (_THIS) { }
   232 
   233 void QZ_SetCaption    (_THIS, const char *title, const char *icon) {
   234 
   235     if ( qz_window != nil ) {
   236         NSString *string;
   237         if ( title != NULL ) {
   238             string = [ [ NSString alloc ] initWithUTF8String:title ];
   239             [ qz_window setTitle:string ];
   240             [ string release ];
   241         }
   242         if ( icon != NULL ) {
   243             string = [ [ NSString alloc ] initWithUTF8String:icon ];
   244             [ qz_window setMiniwindowTitle:string ];
   245             [ string release ];
   246         }
   247     }
   248 }
   249 
   250 void QZ_SetIcon       (_THIS, SDL_Surface *icon, Uint8 *mask)
   251 {
   252     NSBitmapImageRep *imgrep;
   253     NSImage *img;
   254     SDL_Surface *mergedSurface;
   255     int i,j;
   256     NSAutoreleasePool *pool;
   257     SDL_Rect rrect;
   258     NSSize imgSize = {icon->w, icon->h};
   259     
   260     pool = [ [ NSAutoreleasePool alloc ] init ];
   261     SDL_GetClipRect(icon, &rrect);
   262     
   263     /* create a big endian RGBA surface */
   264     mergedSurface = SDL_CreateRGBSurface(SDL_SWSURFACE|SDL_SRCALPHA, 
   265                     icon->w, icon->h, 32, 0xff<<24, 0xff<<16, 0xff<<8, 0xff<<0);
   266     if (mergedSurface==NULL) {
   267         NSLog(@"Error creating surface for merge");
   268         goto freePool;
   269     }
   270     
   271     if (mergedSurface->pitch != 
   272         mergedSurface->format->BytesPerPixel * mergedSurface->w) {
   273         SDL_SetError ("merged surface has wrong format");
   274         SDL_FreeSurface (mergedSurface);
   275         goto freePool;
   276     }
   277     
   278     if (SDL_BlitSurface(icon,&rrect,mergedSurface,&rrect)) {
   279         NSLog(@"Error blitting to mergedSurface");
   280         goto freePool;
   281     }
   282     
   283     if (mask) {
   284 
   285         Uint32 *pixels = mergedSurface->pixels;
   286         for (i = 0; i < mergedSurface->h; i++) {
   287             for (j = 0; j < mergedSurface->w; j++) {
   288                 
   289                 int index = i * mergedSurface->w + j;
   290                 int mindex = index >> 3;
   291                 int bindex = 7 - (index & 0x7);
   292                 
   293                 if (mask[mindex] & (1 << bindex))
   294                     pixels[index] |= 0x000000FF;
   295                 else
   296                     pixels[index] &= 0xFFFFFF00;
   297             }
   298         }
   299     }
   300     
   301     imgrep = [ [ NSBitmapImageRep alloc] 
   302                     initWithBitmapDataPlanes:(unsigned char **)&mergedSurface->pixels 
   303                         pixelsWide:icon->w pixelsHigh:icon->h bitsPerSample:8 samplesPerPixel:4 
   304                         hasAlpha:YES isPlanar:NO colorSpaceName:NSDeviceRGBColorSpace 
   305                         bytesPerRow:icon->w<<2 bitsPerPixel:32 ];
   306     
   307     img = [ [ NSImage alloc ] initWithSize:imgSize ];
   308     
   309     [ img addRepresentation: imgrep ];
   310     [ NSApp setApplicationIconImage:img ];
   311     
   312     [ img release ];
   313     [ imgrep release ];
   314     SDL_FreeSurface(mergedSurface);
   315 freePool:
   316     [pool release];
   317 }
   318 
   319 int  QZ_IconifyWindow (_THIS) { 
   320 
   321     if ( ! [ qz_window isMiniaturized ] ) {
   322         [ qz_window miniaturize:nil ];
   323         return 1;
   324     }
   325     else {
   326         SDL_SetError ("window already iconified");
   327         return 0;
   328     }
   329 }
   330 
   331 /*
   332 int  QZ_GetWMInfo  (_THIS, SDL_SysWMinfo *info) { 
   333     info->nsWindowPtr = qz_window;
   334     return 0; 
   335 }*/
   336 
   337 void QZ_ChangeGrabState (_THIS, int action) {
   338 
   339     /* 
   340         Figure out what the next state should be based on the action.
   341         Ignore actions that can't change the current state.
   342     */
   343     if ( grab_state == QZ_UNGRABBED ) {
   344         if ( action == QZ_ENABLE_GRAB ) {
   345             if ( cursor_should_be_visible )
   346                 grab_state = QZ_VISIBLE_GRAB;
   347             else
   348                 grab_state = QZ_INVISIBLE_GRAB;
   349         }
   350     }
   351     else if ( grab_state == QZ_VISIBLE_GRAB ) {
   352         if ( action == QZ_DISABLE_GRAB )
   353             grab_state = QZ_UNGRABBED;
   354         else if ( action == QZ_HIDECURSOR )
   355             grab_state = QZ_INVISIBLE_GRAB;
   356     }
   357     else {
   358         assert( grab_state == QZ_INVISIBLE_GRAB );
   359         
   360         if ( action == QZ_DISABLE_GRAB )
   361             grab_state = QZ_UNGRABBED;
   362         else if ( action == QZ_SHOWCURSOR )
   363             grab_state = QZ_VISIBLE_GRAB;
   364     }
   365     
   366     /* now apply the new state */
   367     if (grab_state == QZ_UNGRABBED) {
   368     
   369         CGAssociateMouseAndMouseCursorPosition (1);
   370     }
   371     else if (grab_state == QZ_VISIBLE_GRAB) {
   372     
   373         CGAssociateMouseAndMouseCursorPosition (1);
   374     }
   375     else {
   376         assert( grab_state == QZ_INVISIBLE_GRAB );
   377 
   378         QZ_PrivateWarpCursor (this, SDL_VideoSurface->w / 2, SDL_VideoSurface->h / 2);
   379         CGAssociateMouseAndMouseCursorPosition (0);
   380     }
   381 }
   382 
   383 SDL_GrabMode QZ_GrabInput (_THIS, SDL_GrabMode grab_mode) {
   384 
   385     int doGrab = grab_mode & SDL_GRAB_ON;
   386     /*int fullscreen = grab_mode & SDL_GRAB_FULLSCREEN;*/
   387 
   388     if ( this->screen == NULL ) {
   389         SDL_SetError ("QZ_GrabInput: screen is NULL");
   390         return SDL_GRAB_OFF;
   391     }
   392         
   393     if ( ! video_set ) {
   394         /*SDL_SetError ("QZ_GrabInput: video is not set, grab will take effect on mode switch"); */
   395         current_grab_mode = grab_mode;
   396         return grab_mode;       /* Will be set later on mode switch */
   397     }
   398 
   399     if ( grab_mode != SDL_GRAB_QUERY ) {
   400         if ( doGrab )
   401             QZ_ChangeGrabState (this, QZ_ENABLE_GRAB);
   402         else
   403             QZ_ChangeGrabState (this, QZ_DISABLE_GRAB);
   404         
   405         current_grab_mode = doGrab ? SDL_GRAB_ON : SDL_GRAB_OFF;
   406     }
   407 
   408     return current_grab_mode;
   409 }