src/video/quartz/SDL_QuartzWM.m
author Sam Lantinga <slouken@libsdl.org>
Sat, 24 Jun 2006 17:31:46 +0000
branchSDL-1.3
changeset 1708 cd14138a8703
parent 1662 782fd950bd46
permissions -rw-r--r--
Merged fix for bug #240 from SDL 1.2
     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 #include "SDL_config.h"
    23 
    24 #include "SDL_QuartzVideo.h"
    25 
    26 
    27 struct WMcursor
    28 {
    29     NSCursor *nscursor;
    30 };
    31 
    32 void
    33 QZ_FreeWMCursor(_THIS, WMcursor * cursor)
    34 {
    35 
    36     if (cursor != NULL) {
    37         [cursor->nscursor release];
    38         free(cursor);
    39     }
    40 }
    41 
    42 WMcursor *
    43 QZ_CreateWMCursor(_THIS, Uint8 * data, Uint8 * mask,
    44                   int w, int h, int hot_x, int hot_y)
    45 {
    46     WMcursor *cursor;
    47     NSBitmapImageRep *imgrep;
    48     NSImage *img;
    49     unsigned char *planes[5];
    50     int i;
    51     NSAutoreleasePool *pool;
    52 
    53     pool =[[NSAutoreleasePool alloc] init];
    54 
    55     /* Allocate the cursor memory */
    56     cursor = (WMcursor *) SDL_malloc(sizeof(WMcursor));
    57     if (cursor == NULL)
    58         goto outOfMemory;
    59 
    60     /* create the image representation and get the pointers to its storage */
    61   imgrep =[[[NSBitmapImageRep alloc] initWithBitmapDataPlanes: NULL pixelsWide: w pixelsHigh: h bitsPerSample: 1 samplesPerPixel: 2 hasAlpha: YES isPlanar: YES colorSpaceName: NSDeviceBlackColorSpace bytesPerRow: (w + 7) / 8 bitsPerPixel:0] autorelease];
    62     if (imgrep == nil)
    63         goto outOfMemory;
    64   [imgrep getBitmapDataPlanes:planes];
    65 
    66     /* copy data and mask, extending the mask to all black pixels because the inversion effect doesn't work with Cocoa's alpha-blended cursors */
    67     for (i = 0; i < (w + 7) / 8 * h; i++) {
    68         planes[0][i] = data[i];
    69         planes[1][i] = mask[i] | data[i];
    70     }
    71 
    72     /* create image and cursor */
    73   img =[[[NSImage alloc] initWithSize:NSMakeSize(w, h)] autorelease];
    74     if (img == nil)
    75         goto outOfMemory;
    76   [img addRepresentation:imgrep];
    77     if (system_version < 0x1030) {      /* on 10.2, cursors must be 16*16 */
    78         if (w > 16 || h > 16) { /* too big: scale it down */
    79           [img setScalesWhenResized:YES];
    80             hot_x = hot_x * 16 / w;
    81             hot_y = hot_y * 16 / h;
    82         } else {                /* too small (or just right): extend it (from the bottom left corner, so hot_y must be adjusted) */
    83             hot_y += 16 - h;
    84         }
    85       [img setSize:NSMakeSize(16, 16)];
    86     }
    87   cursor->nscursor =[[NSCursor alloc] initWithImage: img hotSpot:NSMakePoint(hot_x,
    88                 hot_y)];
    89     if (cursor->nscursor == nil)
    90         goto outOfMemory;
    91 
    92     [pool release];
    93     return (cursor);
    94 
    95   outOfMemory:
    96     [pool release];
    97     if (cursor != NULL)
    98         SDL_free(cursor);
    99     SDL_OutOfMemory();
   100     return (NULL);
   101 }
   102 
   103 void
   104 QZ_ShowMouse(_THIS)
   105 {
   106     if (!cursor_visible) {
   107         [NSCursor unhide];
   108         cursor_visible = YES;
   109     }
   110 }
   111 
   112 void
   113 QZ_HideMouse(_THIS)
   114 {
   115     if ((SDL_GetAppState() & SDL_APPMOUSEFOCUS) && cursor_visible) {
   116         [NSCursor hide];
   117         cursor_visible = NO;
   118     }
   119 }
   120 
   121 BOOL
   122 QZ_IsMouseInWindow(_THIS)
   123 {
   124     if (qz_window == nil)
   125         return YES;             /*fullscreen */
   126     else {
   127         NSPoint p =[qz_window mouseLocationOutsideOfEventStream];
   128         p.y -= 1.0f;            /* Apparently y goes from 1 to h, not from 0 to h-1 (i.e. the "location of the mouse" seems to be defined as "the location of the top left corner of the mouse pointer's hot pixel" */
   129         return NSPointInRect(p,[window_view frame]);
   130     }
   131 }
   132 
   133 int
   134 QZ_ShowWMCursor(_THIS, WMcursor * cursor)
   135 {
   136 
   137     if (cursor == NULL) {
   138         if (cursor_should_be_visible) {
   139             QZ_HideMouse(this);
   140             cursor_should_be_visible = NO;
   141             QZ_ChangeGrabState(this, QZ_HIDECURSOR);
   142         }
   143     } else {
   144         [cursor->nscursor set];
   145         if (!cursor_should_be_visible) {
   146             QZ_ShowMouse(this);
   147             cursor_should_be_visible = YES;
   148             QZ_ChangeGrabState(this, QZ_SHOWCURSOR);
   149         }
   150     }
   151 
   152     return 1;
   153 }
   154 
   155 /*
   156     Coordinate conversion functions, for convenience
   157     Cocoa sets the origin at the lower left corner of the window/screen
   158     SDL, CoreGraphics/WindowServer, and QuickDraw use the origin at the upper left corner
   159     The routines were written so they could be called before SetVideoMode() has finished;
   160     this might have limited usefulness at the moment, but the extra cost is trivial.
   161 */
   162 
   163 /* Convert Cocoa screen coordinate to Cocoa window coordinate */
   164 void
   165 QZ_PrivateGlobalToLocal(_THIS, NSPoint * p)
   166 {
   167 
   168   *p =[qz_window convertScreenToBase:*p];
   169 }
   170 
   171 
   172 /* Convert Cocoa window coordinate to Cocoa screen coordinate */
   173 void
   174 QZ_PrivateLocalToGlobal(_THIS, NSPoint * p)
   175 {
   176 
   177   *p =[qz_window convertBaseToScreen:*p];
   178 }
   179 
   180 /* Convert SDL coordinate to Cocoa coordinate */
   181 void
   182 QZ_PrivateSDLToCocoa(_THIS, NSPoint * p)
   183 {
   184 
   185     if (CGDisplayIsCaptured(display_id)) {      /* capture signals fullscreen */
   186 
   187         p->y = CGDisplayPixelsHigh(display_id) - p->y;
   188     } else {
   189 
   190       *p =[window_view convertPoint: *p toView:nil];
   191 
   192         /* We need a workaround in OpenGL mode */
   193         if (SDL_VideoSurface->flags & SDL_OPENGL) {
   194             p->y =[window_view frame].size.height - p->y;
   195         }
   196     }
   197 }
   198 
   199 /* Convert Cocoa coordinate to SDL coordinate */
   200 void
   201 QZ_PrivateCocoaToSDL(_THIS, NSPoint * p)
   202 {
   203 
   204     if (CGDisplayIsCaptured(display_id)) {      /* capture signals fullscreen */
   205 
   206         p->y = CGDisplayPixelsHigh(display_id) - p->y;
   207     } else {
   208 
   209       *p =[window_view convertPoint: *p fromView:nil];
   210 
   211         /* We need a workaround in OpenGL mode */
   212         if (SDL_VideoSurface != NULL
   213             && (SDL_VideoSurface->flags & SDL_OPENGL)) {
   214             p->y =[window_view frame].size.height - p->y;
   215         }
   216     }
   217 }
   218 
   219 /* Convert SDL coordinate to window server (CoreGraphics) coordinate */
   220 CGPoint
   221 QZ_PrivateSDLToCG(_THIS, NSPoint * p)
   222 {
   223 
   224     CGPoint cgp;
   225 
   226     if (!CGDisplayIsCaptured(display_id)) {     /* not captured => not fullscreen => local coord */
   227 
   228         int height;
   229 
   230         QZ_PrivateSDLToCocoa(this, p);
   231         QZ_PrivateLocalToGlobal(this, p);
   232 
   233         height = CGDisplayPixelsHigh(display_id);
   234         p->y = height - p->y;
   235     }
   236 
   237     cgp.x = p->x;
   238     cgp.y = p->y;
   239 
   240     return cgp;
   241 }
   242 
   243 #if 0                           /* Dead code */
   244 /* Convert window server (CoreGraphics) coordinate to SDL coordinate */
   245 void
   246 QZ_PrivateCGToSDL(_THIS, NSPoint * p)
   247 {
   248 
   249     if (!CGDisplayIsCaptured(display_id)) {     /* not captured => not fullscreen => local coord */
   250 
   251         int height;
   252 
   253         /* Convert CG Global to Cocoa Global */
   254         height = CGDisplayPixelsHigh(display_id);
   255         p->y = height - p->y;
   256 
   257         QZ_PrivateGlobalToLocal(this, p);
   258         QZ_PrivateCocoaToSDL(this, p);
   259     }
   260 }
   261 #endif /* Dead code */
   262 
   263 void
   264 QZ_PrivateWarpCursor(_THIS, int x, int y)
   265 {
   266 
   267     NSPoint p;
   268     CGPoint cgp;
   269 
   270     p = NSMakePoint(x, y);
   271     cgp = QZ_PrivateSDLToCG(this, &p);
   272 
   273     /* this is the magic call that fixes cursor "freezing" after warp */
   274     CGSetLocalEventsSuppressionInterval(0.0);
   275     CGWarpMouseCursorPosition(cgp);
   276 }
   277 
   278 void
   279 QZ_WarpWMCursor(_THIS, Uint16 x, Uint16 y)
   280 {
   281 
   282     /* Only allow warping when in foreground */
   283     if (![NSApp isActive])
   284         return;
   285 
   286     /* Do the actual warp */
   287     if (grab_state != QZ_INVISIBLE_GRAB)
   288         QZ_PrivateWarpCursor(this, x, y);
   289 
   290     /* Generate the mouse moved event */
   291     SDL_PrivateMouseMotion(0, 0, x, y);
   292 }
   293 
   294 void
   295 QZ_MoveWMCursor(_THIS, int x, int y)
   296 {
   297 }
   298 void
   299 QZ_CheckMouseMode(_THIS)
   300 {
   301 }
   302 
   303 void
   304 QZ_SetCaption(_THIS, const char *title, const char *icon)
   305 {
   306 
   307     if (qz_window != nil) {
   308         NSString *string;
   309         if (title != NULL) {
   310           string =[[NSString alloc] initWithUTF8String:title];
   311           [qz_window setTitle:string];
   312             [string release];
   313         }
   314         if (icon != NULL) {
   315           string =[[NSString alloc] initWithUTF8String:icon];
   316           [qz_window setMiniwindowTitle:string];
   317             [string release];
   318         }
   319     }
   320 }
   321 
   322 void
   323 QZ_SetIcon(_THIS, SDL_Surface * icon, Uint8 * mask)
   324 {
   325     NSBitmapImageRep *imgrep;
   326     NSImage *img;
   327     SDL_Surface *mergedSurface;
   328     NSAutoreleasePool *pool;
   329     Uint8 *pixels;
   330     SDL_bool iconSrcAlpha;
   331     Uint8 iconAlphaValue;
   332     int i, j, maskPitch, index;
   333 
   334     pool =[[NSAutoreleasePool alloc] init];
   335 
   336   imgrep =[[[NSBitmapImageRep alloc] initWithBitmapDataPlanes: NULL pixelsWide: icon->w pixelsHigh: icon->h bitsPerSample: 8 samplesPerPixel: 4 hasAlpha: YES isPlanar: NO colorSpaceName: NSDeviceRGBColorSpace bytesPerRow: 4 * icon->w bitsPerPixel:32] autorelease];
   337     if (imgrep == nil)
   338         goto freePool;
   339     pixels =[imgrep bitmapData];
   340     SDL_memset(pixels, 0, 4 * icon->w * icon->h);       /* make the background, which will survive in colorkeyed areas, completely transparent */
   341 
   342 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
   343 #define BYTEORDER_DEPENDENT_RGBA_MASKS 0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF
   344 #else
   345 #define BYTEORDER_DEPENDENT_RGBA_MASKS 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000
   346 #endif
   347     mergedSurface =
   348         SDL_CreateRGBSurfaceFrom(pixels, icon->w, icon->h, 32, 4 * icon->w,
   349                                  BYTEORDER_DEPENDENT_RGBA_MASKS);
   350     if (mergedSurface == NULL)
   351         goto freePool;
   352 
   353     /* blit, with temporarily cleared SRCALPHA flag because we want to copy, not alpha-blend */
   354     iconSrcAlpha = ((icon->flags & SDL_SRCALPHA) != 0);
   355     iconAlphaValue = icon->format->alpha;
   356     SDL_SetAlpha(icon, 0, 255);
   357     SDL_BlitSurface(icon, NULL, mergedSurface, NULL);
   358     if (iconSrcAlpha)
   359         SDL_SetAlpha(icon, SDL_SRCALPHA, iconAlphaValue);
   360 
   361     SDL_FreeSurface(mergedSurface);
   362 
   363     /* apply mask, source alpha, and premultiply color values by alpha */
   364     maskPitch = (icon->w + 7) / 8;
   365     for (i = 0; i < icon->h; i++) {
   366         for (j = 0; j < icon->w; j++) {
   367             index = i * 4 * icon->w + j * 4;
   368             if (!(mask[i * maskPitch + j / 8] & (128 >> j % 8))) {
   369                 pixels[index + 3] = 0;
   370             } else {
   371                 if (iconSrcAlpha) {
   372                     if (icon->format->Amask == 0)
   373                         pixels[index + 3] = icon->format->alpha;
   374                 } else {
   375                     pixels[index + 3] = 255;
   376                 }
   377             }
   378             if (pixels[index + 3] < 255) {
   379                 pixels[index + 0] =
   380                     (Uint16) pixels[index + 0] * pixels[index + 3] / 255;
   381                 pixels[index + 1] =
   382                     (Uint16) pixels[index + 1] * pixels[index + 3] / 255;
   383                 pixels[index + 2] =
   384                     (Uint16) pixels[index + 2] * pixels[index + 3] / 255;
   385             }
   386         }
   387     }
   388 
   389   img =[[[NSImage alloc] initWithSize:NSMakeSize(icon->w,
   390                icon->h)] autorelease];
   391     if (img == nil)
   392         goto freePool;
   393   [img addRepresentation:imgrep];
   394   [NSApp setApplicationIconImage:img];
   395 
   396   freePool:
   397     [pool release];
   398 }
   399 
   400 int
   401 QZ_IconifyWindow(_THIS)
   402 {
   403 
   404     if (![qz_window isMiniaturized]) {
   405       [qz_window miniaturize:nil];
   406         return 1;
   407     } else {
   408         SDL_SetError("window already iconified");
   409         return 0;
   410     }
   411 }
   412 
   413 /*
   414 int  QZ_GetWMInfo  (_THIS, SDL_SysWMinfo *info) { 
   415     info->nsWindowPtr = qz_window;
   416     return 0; 
   417 }*/
   418 
   419 void
   420 QZ_ChangeGrabState(_THIS, int action)
   421 {
   422 
   423     /* 
   424        Figure out what the next state should be based on the action.
   425        Ignore actions that can't change the current state.
   426      */
   427     if (grab_state == QZ_UNGRABBED) {
   428         if (action == QZ_ENABLE_GRAB) {
   429             if (cursor_should_be_visible)
   430                 grab_state = QZ_VISIBLE_GRAB;
   431             else
   432                 grab_state = QZ_INVISIBLE_GRAB;
   433         }
   434     } else if (grab_state == QZ_VISIBLE_GRAB) {
   435         if (action == QZ_DISABLE_GRAB)
   436             grab_state = QZ_UNGRABBED;
   437         else if (action == QZ_HIDECURSOR)
   438             grab_state = QZ_INVISIBLE_GRAB;
   439     } else {
   440         assert(grab_state == QZ_INVISIBLE_GRAB);
   441 
   442         if (action == QZ_DISABLE_GRAB)
   443             grab_state = QZ_UNGRABBED;
   444         else if (action == QZ_SHOWCURSOR)
   445             grab_state = QZ_VISIBLE_GRAB;
   446     }
   447 
   448     /* now apply the new state */
   449     if (grab_state == QZ_UNGRABBED) {
   450 
   451         CGAssociateMouseAndMouseCursorPosition(1);
   452     } else if (grab_state == QZ_VISIBLE_GRAB) {
   453 
   454         CGAssociateMouseAndMouseCursorPosition(1);
   455     } else {
   456         assert(grab_state == QZ_INVISIBLE_GRAB);
   457 
   458         QZ_PrivateWarpCursor(this, SDL_VideoSurface->w / 2,
   459                              SDL_VideoSurface->h / 2);
   460         CGAssociateMouseAndMouseCursorPosition(0);
   461     }
   462 }
   463 
   464 SDL_GrabMode
   465 QZ_GrabInput(_THIS, SDL_GrabMode grab_mode)
   466 {
   467 
   468     int doGrab = grab_mode & SDL_GRAB_ON;
   469     /*int fullscreen = grab_mode & SDL_GRAB_FULLSCREEN; */
   470 
   471     if (this->screen == NULL) {
   472         SDL_SetError("QZ_GrabInput: screen is NULL");
   473         return SDL_GRAB_OFF;
   474     }
   475 
   476     if (!video_set) {
   477         /*SDL_SetError ("QZ_GrabInput: video is not set, grab will take effect on mode switch"); */
   478         current_grab_mode = grab_mode;
   479         return grab_mode;       /* Will be set later on mode switch */
   480     }
   481 
   482     if (grab_mode != SDL_GRAB_QUERY) {
   483         if (doGrab)
   484             QZ_ChangeGrabState(this, QZ_ENABLE_GRAB);
   485         else
   486             QZ_ChangeGrabState(this, QZ_DISABLE_GRAB);
   487 
   488         current_grab_mode = doGrab ? SDL_GRAB_ON : SDL_GRAB_OFF;
   489     }
   490 
   491     return current_grab_mode;
   492 }