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