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