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#include "SDL_QuartzWM.h" 26 27void QZ_FreeWMCursor (_THIS, WMcursor *cursor) { 28 29 if ( cursor != NULL ) { 30 [ cursor->nscursor release ]; 31 free (cursor); 32 } 33} 34 35WMcursor* QZ_CreateWMCursor (_THIS, Uint8 *data, Uint8 *mask, 36 int w, int h, int hot_x, int hot_y) { 37 WMcursor *cursor; 38 NSBitmapImageRep *imgrep; 39 NSImage *img; 40 unsigned char *planes[5]; 41 int i; 42 NSAutoreleasePool *pool; 43 44 pool = [ [ NSAutoreleasePool alloc ] init ]; 45 46 /* Allocate the cursor memory */ 47 cursor = (WMcursor *)SDL_malloc(sizeof(WMcursor)); 48 if (cursor == NULL) goto outOfMemory; 49 50 /* create the image representation and get the pointers to its storage */ 51 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 ]; 52 if (imgrep == nil) goto outOfMemory; 53 [ imgrep getBitmapDataPlanes: planes ]; 54 55 /* copy data and mask, extending the mask to all black pixels because the inversion effect doesn't work with Cocoa's alpha-blended cursors */ 56 for (i = 0; i < (w+7)/8*h; i++) { 57 planes[0][i] = data[i]; 58 planes[1][i] = mask[i] | data[i]; 59 } 60 61 /* create image and cursor */ 62 img = [ [ [ NSImage alloc ] initWithSize: NSMakeSize(w, h) ] autorelease ]; 63 if (img == nil) goto outOfMemory; 64 [ img addRepresentation: imgrep ]; 65 if (system_version < 0x1030) { /* on 10.2, cursors must be 16*16 */ 66 if (w > 16 || h > 16) { /* too big: scale it down */ 67 [ img setScalesWhenResized: YES ]; 68 hot_x = hot_x*16/w; 69 hot_y = hot_y*16/h; 70 } 71 else { /* too small (or just right): extend it (from the bottom left corner, so hot_y must be adjusted) */ 72 hot_y += 16 - h; 73 } 74 [ img setSize: NSMakeSize(16, 16) ]; 75 } 76 cursor->nscursor = [ [ NSCursor alloc ] initWithImage: img hotSpot: NSMakePoint(hot_x, hot_y) ]; 77 if (cursor->nscursor == nil) goto outOfMemory; 78 79 [ pool release ]; 80 return(cursor); 81 82outOfMemory: 83 [ pool release ]; 84 if (cursor != NULL) SDL_free(cursor); 85 SDL_OutOfMemory(); 86 return(NULL); 87} 88 89void QZ_UpdateCursor (_THIS) { 90 BOOL state; 91 92 if (cursor_should_be_visible || !(SDL_GetAppState() & SDL_APPMOUSEFOCUS)) { 93 state = YES; 94 } else { 95 state = NO; 96 } 97 if (state != cursor_visible) { 98 if (state) { 99 [ NSCursor unhide ]; 100 } else { 101 [ NSCursor hide ]; 102 } 103 cursor_visible = state; 104 } 105} 106 107BOOL QZ_IsMouseInWindow (_THIS) { 108 if (qz_window == nil || (mode_flags & SDL_FULLSCREEN)) return YES; /*fullscreen*/ 109 else { 110 NSPoint p = [ qz_window mouseLocationOutsideOfEventStream ]; 111 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" */ 112 return NSPointInRect(p, [ window_view frame ]); 113 } 114} 115 116int QZ_ShowWMCursor (_THIS, WMcursor *cursor) { 117 118 if ( cursor == NULL) { 119 if ( cursor_should_be_visible ) { 120 cursor_should_be_visible = NO; 121 QZ_ChangeGrabState (this, QZ_HIDECURSOR); 122 } 123 QZ_UpdateCursor(this); 124 } 125 else { 126 if (qz_window ==nil || (mode_flags & SDL_FULLSCREEN)) { 127 [ cursor->nscursor set ]; 128 } 129 else { 130 [ qz_window invalidateCursorRectsForView: [ qz_window contentView ] ]; 131 } 132 if ( ! cursor_should_be_visible ) { 133 cursor_should_be_visible = YES; 134 QZ_ChangeGrabState (this, QZ_SHOWCURSOR); 135 } 136 QZ_UpdateCursor(this); 137 } 138 139 return 1; 140} 141 142/* 143 Coordinate conversion functions, for convenience 144 Cocoa sets the origin at the lower left corner of the window/screen 145 SDL, CoreGraphics/WindowServer, and QuickDraw use the origin at the upper left corner 146 The routines were written so they could be called before SetVideoMode() has finished; 147 this might have limited usefulness at the moment, but the extra cost is trivial. 148*/ 149 150/* Convert Cocoa screen coordinate to Cocoa window coordinate */ 151void QZ_PrivateGlobalToLocal (_THIS, NSPoint *p) { 152 153 *p = [ qz_window convertScreenToBase:*p ]; 154} 155 156 157/* Convert Cocoa window coordinate to Cocoa screen coordinate */ 158void QZ_PrivateLocalToGlobal (_THIS, NSPoint *p) { 159 160 *p = [ qz_window convertBaseToScreen:*p ]; 161} 162 163/* Convert SDL coordinate to Cocoa coordinate */ 164void QZ_PrivateSDLToCocoa (_THIS, NSPoint *p) { 165 166 if ( CGDisplayIsCaptured (display_id) ) { /* capture signals fullscreen */ 167 168 p->y = CGDisplayPixelsHigh (display_id) - p->y; 169 } 170 else { 171 172 *p = [ window_view convertPoint:*p toView: nil ]; 173 174 /* We need a workaround in OpenGL mode */ 175 if ( SDL_VideoSurface->flags & SDL_OPENGL ) { 176 p->y = [window_view frame].size.height - p->y; 177 } 178 } 179} 180 181/* Convert Cocoa coordinate to SDL coordinate */ 182void QZ_PrivateCocoaToSDL (_THIS, NSPoint *p) { 183 184 if ( CGDisplayIsCaptured (display_id) ) { /* capture signals fullscreen */ 185 186 p->y = CGDisplayPixelsHigh (display_id) - p->y; 187 } 188 else { 189 190 *p = [ window_view convertPoint:*p fromView: nil ]; 191 192 /* We need a workaround in OpenGL mode */ 193 if ( SDL_VideoSurface != NULL && (SDL_VideoSurface->flags & SDL_OPENGL) ) { 194 p->y = [window_view frame].size.height - p->y; 195 } 196 } 197} 198 199/* Convert SDL coordinate to window server (CoreGraphics) coordinate */ 200CGPoint QZ_PrivateSDLToCG (_THIS, NSPoint *p) { 201 202 CGPoint cgp; 203 204 if ( ! CGDisplayIsCaptured (display_id) ) { /* not captured => not fullscreen => local coord */ 205 206 int height; 207 208 QZ_PrivateSDLToCocoa (this, p); 209 QZ_PrivateLocalToGlobal (this, p); 210 211 height = CGDisplayPixelsHigh (display_id); 212 p->y = height - p->y; 213 } 214 215 cgp.x = p->x; 216 cgp.y = p->y; 217 218 return cgp; 219} 220 221#if 0 /* Dead code */ 222/* Convert window server (CoreGraphics) coordinate to SDL coordinate */ 223void QZ_PrivateCGToSDL (_THIS, NSPoint *p) { 224 225 if ( ! CGDisplayIsCaptured (display_id) ) { /* not captured => not fullscreen => local coord */ 226 227 int height; 228 229 /* Convert CG Global to Cocoa Global */ 230 height = CGDisplayPixelsHigh (display_id); 231 p->y = height - p->y; 232 233 QZ_PrivateGlobalToLocal (this, p); 234 QZ_PrivateCocoaToSDL (this, p); 235 } 236} 237#endif /* Dead code */ 238 239void QZ_PrivateWarpCursor (_THIS, int x, int y) { 240 241 NSPoint p; 242 CGPoint cgp; 243 244 p = NSMakePoint (x, y); 245 cgp = QZ_PrivateSDLToCG (this, &p); 246 247 /* this is the magic call that fixes cursor "freezing" after warp */ 248 CGSetLocalEventsSuppressionInterval (0.0); 249 CGWarpMouseCursorPosition (cgp); 250} 251 252void QZ_WarpWMCursor (_THIS, Uint16 x, Uint16 y) { 253 254 /* Only allow warping when in foreground */ 255 if ( ! [ NSApp isActive ] ) 256 return; 257 258 /* Do the actual warp */ 259 if (grab_state != QZ_INVISIBLE_GRAB) QZ_PrivateWarpCursor (this, x, y); 260 261 /* Generate the mouse moved event */ 262 SDL_PrivateMouseMotion (0, 0, x, y); 263} 264 265void QZ_MoveWMCursor (_THIS, int x, int y) { } 266void QZ_CheckMouseMode (_THIS) { } 267 268void QZ_SetCaption (_THIS, const char *title, const char *icon) { 269 270 if ( qz_window != nil ) { 271 NSString *string; 272 if ( title != NULL ) { 273 string = [ [ NSString alloc ] initWithUTF8String:title ]; 274 [ qz_window setTitle:string ]; 275 [ string release ]; 276 } 277 if ( icon != NULL ) { 278 string = [ [ NSString alloc ] initWithUTF8String:icon ]; 279 [ qz_window setMiniwindowTitle:string ]; 280 [ string release ]; 281 } 282 } 283} 284 285void QZ_SetWindowPos (_THIS, int x, int y) 286{ 287 if ( qz_window == nil ) { 288 //printf( "%s(%d,%d): called for NULL window\n", __FUNCTION__, x, y ); 289 return; 290 } 291 292 [ qz_window setFrameTopLeftPoint:NSMakePoint( x, this->hidden->height - y ) ]; 293 //printf( "%s(%d,%d): done\n", __FUNCTION__, x, y ); 294} 295 296void QZ_GetWindowPos(_THIS, int *px, int *py) 297{ 298 NSPoint pt; 299 300 *px = *py = 0; 301 302 if ( qz_window == NULL ) { 303 //printf( "%s: called on NULL window\n", __FUNCTION__ ); 304 } 305 306 if ( qz_window != nil ) { 307 NSRect rect = [ qz_window frame ]; 308 *px = rect.origin.x; 309 *py = this->hidden->height - rect.origin.y - rect.size.height; 310 //printf( "%s: returning (%d,%d)\n", __FUNCTION__, *px, *py ); 311 } 312} 313 314/* determine if the window is fully visible on the current screen configuration */ 315int QZ_IsWindowVisible(_THIS, int recenter) 316{ 317 int result = 0; 318 319 //printf( "... enter %s\n", __FUNCTION__ ); 320 321 if ( qz_window != NULL ) { 322 NSRect frame = [ qz_window frame ]; 323 NSArray* screens = [ NSScreen screens ]; 324 unsigned int count = [ screens count ]; 325 unsigned int n; 326 //printf( "window frame (%d,%d) (%d,%d)\n", frame.origin.x, frame.origin.y, 327 // frame.size.width, frame.size.height ); 328 for (n = 0; n < count; n++) { 329 NSScreen* screen = [ screens objectAtIndex: n ]; 330 NSRect vis = [ screen visibleFrame ]; 331 332 //printf( "screen %d/%d frame (%d,%d) (%d,%d)\n", n+1, count, 333 // vis.origin.x, vis.origin.y, vis.size.width, vis.size.height ); 334 335 if (frame.origin.x >= vis.origin.x && 336 frame.origin.x + frame.size.width <= vis.origin.x + vis.size.width && 337 frame.origin.y >= vis.origin.y && 338 frame.origin.y + frame.size.height <= vis.origin.y + vis.size.height ) 339 { 340 result = 1; 341 break; 342 } 343 } 344 } 345 //printf ( "... exit %s, result = %d\n", __FUNCTION__, result ); 346 if ( !result && recenter ) { 347 [ qz_window center ] ; 348 } 349 return result; 350} 351 352int QZ_GetMonitorDPI(_THIS, int *xDpi, int *yDpi) 353{ 354 /* FIXME: how to get this information from Cocoa ? */ 355 return -1; 356} 357 358int QZ_GetMonitorRect (_THIS, SDL_Rect *rect) 359{ 360 NSWindow* window = qz_window; 361 NSRect frame = [ window frame ]; 362 int fx1 = frame.origin.x; 363 int fy1 = frame.origin.y; 364 int fx2 = frame.size.width + fx1; 365 int fy2 = frame.size.height + fy1; 366 NSArray* screens = [ NSScreen screens ]; 367 unsigned int count = [ screens count ]; 368 int bestScreen = -1; 369 int bestArea = 0; 370 371 unsigned int n; 372 373 /* we need to compute which screen has the most window pixels */ 374 for (n = 0; n < count; n++) { 375 NSScreen* screen = [ screens objectAtIndex: n ]; 376 NSRect vis = [ screen visibleFrame ]; 377 int vx1 = vis.origin.x; 378 int vy1 = vis.origin.y; 379 int vx2 = vis.size.width + vx1; 380 int vy2 = vis.size.height + vy1; 381 int cx1, cx2, cy1, cy2, cArea; 382 383 if (fx1 >= vx2 || vx1 >= fx2 || fy1 >= vy2 || vy1 >= fy2) 384 continue; 385 386 cx1 = (fx1 < vx1) ? vx1 : fx1; 387 cx2 = (fx2 > vx2) ? vx2 : fx2; 388 cy1 = (fy1 < vy1) ? vy1 : fy1; 389 cy2 = (fy2 > vy2) ? vy2 : fy2; 390 391 if (cx1 >= cx2 || cy1 >= cy2) 392 continue; 393 394 cArea = (cx2-cx1)*(cy2-cy1); 395 396 if (bestScreen < 0 || cArea > bestArea) { 397 bestScreen = n; 398 bestArea = cArea; 399 } 400 } 401 if (bestScreen < 0) 402 bestScreen = 0; 403 404 { 405 NSScreen* screen = [ screens objectAtIndex: bestScreen ]; 406 NSRect vis = [ screen visibleFrame ]; 407 408 rect->x = vis.origin.x; 409 rect->y = vis.origin.y; 410 rect->w = vis.size.width; 411 rect->h = vis.size.height; 412 } 413 return 0; 414} 415 416void QZ_SetIcon (_THIS, SDL_Surface *icon, Uint8 *mask) 417{ 418 NSBitmapImageRep *imgrep; 419 NSImage *img; 420 SDL_Surface *mergedSurface; 421 NSAutoreleasePool *pool; 422 Uint8 *pixels; 423 SDL_bool iconSrcAlpha; 424 Uint8 iconAlphaValue; 425 int i, j, maskPitch, index; 426 427 pool = [ [ NSAutoreleasePool alloc ] init ]; 428 429 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 ]; 430 if (imgrep == nil) goto freePool; 431 pixels = [ imgrep bitmapData ]; 432 SDL_memset(pixels, 0, 4*icon->w*icon->h); /* make the background, which will survive in colorkeyed areas, completely transparent */ 433 434#if SDL_BYTEORDER == SDL_BIG_ENDIAN 435#define BYTEORDER_DEPENDENT_RGBA_MASKS 0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF 436#else 437#define BYTEORDER_DEPENDENT_RGBA_MASKS 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000 438#endif 439 mergedSurface = SDL_CreateRGBSurfaceFrom(pixels, icon->w, icon->h, 32, 4*icon->w, BYTEORDER_DEPENDENT_RGBA_MASKS); 440 if (mergedSurface == NULL) goto freePool; 441 442 /* blit, with temporarily cleared SRCALPHA flag because we want to copy, not alpha-blend */ 443 iconSrcAlpha = ((icon->flags & SDL_SRCALPHA) != 0); 444 iconAlphaValue = icon->format->alpha; 445 SDL_SetAlpha(icon, 0, 255); 446 SDL_BlitSurface(icon, NULL, mergedSurface, NULL); 447 if (iconSrcAlpha) SDL_SetAlpha(icon, SDL_SRCALPHA, iconAlphaValue); 448 449 SDL_FreeSurface(mergedSurface); 450 451 /* apply mask, source alpha, and premultiply color values by alpha */ 452 maskPitch = (icon->w+7)/8; 453 for (i = 0; i < icon->h; i++) { 454 for (j = 0; j < icon->w; j++) { 455 index = i*4*icon->w + j*4; 456 if (!(mask[i*maskPitch + j/8] & (128 >> j%8))) { 457 pixels[index + 3] = 0; 458 } 459 else { 460 if (iconSrcAlpha) { 461 if (icon->format->Amask == 0) pixels[index + 3] = icon->format->alpha; 462 } 463 else { 464 pixels[index + 3] = 255; 465 } 466 } 467 if (pixels[index + 3] < 255) { 468 pixels[index + 0] = (Uint16)pixels[index + 0]*pixels[index + 3]/255; 469 pixels[index + 1] = (Uint16)pixels[index + 1]*pixels[index + 3]/255; 470 pixels[index + 2] = (Uint16)pixels[index + 2]*pixels[index + 3]/255; 471 } 472 } 473 } 474 475 img = [ [ [ NSImage alloc ] initWithSize: NSMakeSize(icon->w, icon->h) ] autorelease ]; 476 if (img == nil) goto freePool; 477 [ img addRepresentation: imgrep ]; 478 [ NSApp setApplicationIconImage:img ]; 479 480freePool: 481 [ pool release ]; 482} 483 484int QZ_IconifyWindow (_THIS) { 485 486 if ( ! [ qz_window isMiniaturized ] ) { 487 [ qz_window miniaturize:nil ]; 488 return 1; 489 } 490 else { 491 SDL_SetError ("window already iconified"); 492 return 0; 493 } 494} 495 496 497int QZ_GetWMInfo (_THIS, SDL_SysWMinfo *info) { 498 info->nsWindowPtr = qz_window; 499 return 0; 500} 501 502void QZ_ChangeGrabState (_THIS, int action) { 503 504 /* 505 Figure out what the next state should be based on the action. 506 Ignore actions that can't change the current state. 507 */ 508 if ( grab_state == QZ_UNGRABBED ) { 509 if ( action == QZ_ENABLE_GRAB ) { 510 if ( cursor_should_be_visible ) 511 grab_state = QZ_VISIBLE_GRAB; 512 else 513 grab_state = QZ_INVISIBLE_GRAB; 514 } 515 } 516 else if ( grab_state == QZ_VISIBLE_GRAB ) { 517 if ( action == QZ_DISABLE_GRAB ) 518 grab_state = QZ_UNGRABBED; 519 else if ( action == QZ_HIDECURSOR ) 520 grab_state = QZ_INVISIBLE_GRAB; 521 } 522 else { 523 assert( grab_state == QZ_INVISIBLE_GRAB ); 524 525 if ( action == QZ_DISABLE_GRAB ) 526 grab_state = QZ_UNGRABBED; 527 else if ( action == QZ_SHOWCURSOR ) 528 grab_state = QZ_VISIBLE_GRAB; 529 } 530 531 /* now apply the new state */ 532 if (grab_state == QZ_UNGRABBED) { 533 534 CGAssociateMouseAndMouseCursorPosition (1); 535 } 536 else if (grab_state == QZ_VISIBLE_GRAB) { 537 538 CGAssociateMouseAndMouseCursorPosition (1); 539 } 540 else { 541 assert( grab_state == QZ_INVISIBLE_GRAB ); 542 543 QZ_PrivateWarpCursor (this, SDL_VideoSurface->w / 2, SDL_VideoSurface->h / 2); 544 CGAssociateMouseAndMouseCursorPosition (0); 545 } 546} 547 548SDL_GrabMode QZ_GrabInput (_THIS, SDL_GrabMode grab_mode) { 549 550 int doGrab = grab_mode & SDL_GRAB_ON; 551 /*int fullscreen = grab_mode & SDL_GRAB_FULLSCREEN;*/ 552 553 if ( this->screen == NULL ) { 554 SDL_SetError ("QZ_GrabInput: screen is NULL"); 555 return SDL_GRAB_OFF; 556 } 557 558 if ( ! video_set ) { 559 /*SDL_SetError ("QZ_GrabInput: video is not set, grab will take effect on mode switch"); */ 560 current_grab_mode = grab_mode; 561 return grab_mode; /* Will be set later on mode switch */ 562 } 563 564 if ( grab_mode != SDL_GRAB_QUERY ) { 565 if ( doGrab ) 566 QZ_ChangeGrabState (this, QZ_ENABLE_GRAB); 567 else 568 QZ_ChangeGrabState (this, QZ_DISABLE_GRAB); 569 570 current_grab_mode = doGrab ? SDL_GRAB_ON : SDL_GRAB_OFF; 571 } 572 573 return current_grab_mode; 574} 575