comparison src/video/quartz/SDL_QuartzEvents.m @ 563:04dcaf3da918

Massive Quartz input enhancements from Darrell Walisser. His email: Enclosed is a patch that addresses the following: --Various minor cleanups. Removed dead/obsolete code, made some style cleanups --Mouse Events Now keep track of what button(s) were pressed so we know when to send the mouse up event. This fixes the case where the mouse is dragged outside of the game window and released (in which case we want to send the mouse up event even though the mouse is outside the game window). --Input Grabbing Here is my take on the grabbing situation, which is the basis for the new implementation. There are 3 grab states, ungrabbed (UG), visible (VG), and invisible (IG). Both VG and IG keep the mouse constrained to the window and produce relative motion events. In VG the cursor is visible (duh), in IG it is not. In VG, absolute motion events also work. There are 6 actions that can affect grabbing: 1. Set Fullscreen/Window (F/W). In fullscreen, a visible grab should do nothing. However, a fullscreen visible grab can be treated just like a windowed visible grab, which is what I have done to help simplify things. 2. Cursor hide/show (H/S). If the cursor is hidden when grabbing, the grab is an invisible grab. If the cursor is visible, the grab should just constrain the mouse to the window. 3. Input grab/ungrab(G/U). If grabbed, the cursor should be confined to the window as should the keyboard input. On Mac OS X, the keyboard input is implicitly grabbed by confining the cursor, except for command-tab which can switch away from the application. Should the window come to the foreground if the application is deactivated and grab input is called? This isn't necessary in this implementation because the grab state will be asserted upon activation. Using my notation, these are all the cases that need to be handled (state + action = new state). UG+U = UG UG+G = VG or IG, if cursor is visible or not UG+H = UG UG+S = UG VG+U = UG VG+G = VG VG+H = IG VG+S = VG IG+U = UG IG+G = IG IG+H = IG IG+S = VG The cases that result in the same state can be ignored in the code, which cuts it down to just 5 cases. Another issue is what happens when the app loses/gains input focus from deactivate/activate or iconify/deiconify. I think that if input focus is ever lost (outside of SDL's control), the grab state should be suspended and the cursor should become visible and active again. When regained, the cursor should reappear in its original location and/or grab state. This way, when reactivating the cursor is still in the same position as before so apps shouldn't get confused when the next motion event comes in. This is what I've done in this patch.
author Ryan C. Gordon <icculus@icculus.org>
date Fri, 27 Dec 2002 20:52:41 +0000
parents 4bcf7dd06c47
children 7ec821f3cbd0
comparison
equal deleted inserted replaced
562:cb40b26523a5 563:04dcaf3da918
298 } 298 }
299 299
300 static void QZ_DoActivate (_THIS) 300 static void QZ_DoActivate (_THIS)
301 { 301 {
302 in_foreground = YES; 302 in_foreground = YES;
303 303
304 /* Regrab the mouse, only if it was previously grabbed */ 304 /* Hide the mouse cursor if was hidden */
305 if (!cursor_visible) {
306 HideCursor ();
307 }
308
309 /* Regrab input, only if it was previously grabbed */
305 if ( current_grab_mode == SDL_GRAB_ON ) { 310 if ( current_grab_mode == SDL_GRAB_ON ) {
306 QZ_WarpWMCursor (this, SDL_VideoSurface->w / 2, SDL_VideoSurface->h / 2); 311
307 CGAssociateMouseAndMouseCursorPosition (0); 312 /* Restore cursor location if input was grabbed */
308 } 313 QZ_PrivateWarpCursor (this, cursor_loc.x, cursor_loc.y);
309 314 QZ_ChangeGrabState (this, QZ_ENABLE_GRAB);
310 /* Hide the mouse cursor if inside the app window */
311 if (!QZ_cursor_visible) {
312 HideCursor ();
313 } 315 }
314 316
315 SDL_PrivateAppActive (1, SDL_APPINPUTFOCUS); 317 SDL_PrivateAppActive (1, SDL_APPINPUTFOCUS);
316 } 318 }
317 319
318 static void QZ_DoDeactivate (_THIS) { 320 static void QZ_DoDeactivate (_THIS) {
319 321
320 in_foreground = NO; 322 in_foreground = NO;
321 323
322 /* Ungrab mouse if it is grabbed */ 324 /* Get the current cursor location, for restore on activate */
323 if ( current_grab_mode == SDL_GRAB_ON ) { 325 cursor_loc = [ NSEvent mouseLocation ]; /* global coordinates */
324 CGAssociateMouseAndMouseCursorPosition (1); 326 if (qz_window)
325 } 327 QZ_PrivateGlobalToLocal (this, &cursor_loc);
326 328 QZ_PrivateCocoaToSDL (this, &cursor_loc);
327 /* Show the mouse cursor */ 329
328 if (!QZ_cursor_visible) { 330 /* Reassociate mouse and cursor */
329 ShowCursor (); 331 CGAssociateMouseAndMouseCursorPosition (1);
330 } 332
333 /* Show the cursor */
334 ShowCursor ();
331 335
332 SDL_PrivateAppActive (0, SDL_APPINPUTFOCUS); 336 SDL_PrivateAppActive (0, SDL_APPINPUTFOCUS);
333 } 337 }
334 338
335 void QZ_SleepNotificationHandler (void * refcon, 339 void QZ_SleepNotificationHandler (void * refcon,
340 SDL_VideoDevice *this = (SDL_VideoDevice*)refcon; 344 SDL_VideoDevice *this = (SDL_VideoDevice*)refcon;
341 345
342 switch(messageType) 346 switch(messageType)
343 { 347 {
344 case kIOMessageSystemWillSleep: 348 case kIOMessageSystemWillSleep:
345 IOAllowPowerChange(powerConnection, (long) messageArgument); 349 IOAllowPowerChange(power_connection, (long) messageArgument);
346 break; 350 break;
347 case kIOMessageCanSystemSleep: 351 case kIOMessageCanSystemSleep:
348 IOAllowPowerChange(powerConnection, (long) messageArgument); 352 IOAllowPowerChange(power_connection, (long) messageArgument);
349 break; 353 break;
350 case kIOMessageSystemHasPoweredOn: 354 case kIOMessageSystemHasPoweredOn:
351 /* awake */ 355 /* awake */
352 SDL_PrivateExpose(); 356 SDL_PrivateExpose();
353 break; 357 break;
358 { 362 {
359 CFRunLoopSourceRef rls; 363 CFRunLoopSourceRef rls;
360 IONotificationPortRef thePortRef; 364 IONotificationPortRef thePortRef;
361 io_object_t notifier; 365 io_object_t notifier;
362 366
363 powerConnection = IORegisterForSystemPower (this, &thePortRef, QZ_SleepNotificationHandler, &notifier); 367 power_connection = IORegisterForSystemPower (this, &thePortRef, QZ_SleepNotificationHandler, &notifier);
364 368
365 if (powerConnection == 0) 369 if (power_connection == 0)
366 NSLog(@"SDL: QZ_SleepNotificationHandler() IORegisterForSystemPower failed."); 370 NSLog(@"SDL: QZ_SleepNotificationHandler() IORegisterForSystemPower failed.");
367 371
368 rls = IONotificationPortGetRunLoopSource (thePortRef); 372 rls = IONotificationPortGetRunLoopSource (thePortRef);
369 CFRunLoopAddSource (CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode); 373 CFRunLoopAddSource (CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
370 CFRelease (rls); 374 CFRelease (rls);
399 403
400 /* Poll for an event. This will not block */ 404 /* Poll for an event. This will not block */
401 event = [ NSApp nextEventMatchingMask:NSAnyEventMask 405 event = [ NSApp nextEventMatchingMask:NSAnyEventMask
402 untilDate:distantPast 406 untilDate:distantPast
403 inMode: NSDefaultRunLoopMode dequeue:YES ]; 407 inMode: NSDefaultRunLoopMode dequeue:YES ];
404
405 if (event != nil) { 408 if (event != nil) {
406 409
407 unsigned int type; 410 unsigned int type;
408 BOOL isForGameWin; 411 BOOL isForGameWin;
409 412 BOOL isInGameWin;
410 #define DO_MOUSE_DOWN(button, sendToWindow) do { \ 413
414 #define DO_MOUSE_DOWN(button) do { \
411 if ( in_foreground ) { \ 415 if ( in_foreground ) { \
412 if ( (SDL_VideoSurface->flags & SDL_FULLSCREEN) || \ 416 if ( isInGameWin ) { \
413 NSPointInRect([event locationInWindow], winRect) ) \ 417 SDL_PrivateMouseButton (SDL_PRESSED, button, 0, 0); \
414 SDL_PrivateMouseButton (SDL_PRESSED, button, 0, 0); \ 418 expect_mouse_up |= 1<<button; \
419 } \
415 } \ 420 } \
416 else { \ 421 else { \
417 QZ_DoActivate (this); \ 422 QZ_DoActivate (this); \
418 } \ 423 } \
419 [ NSApp sendEvent:event ]; \ 424 [ NSApp sendEvent:event ]; \
420 } while(0) 425 } while(0)
421 426
422 #define DO_MOUSE_UP(button, sendToWindow) do { \ 427 #define DO_MOUSE_UP(button) do { \
423 if ( (SDL_VideoSurface->flags & SDL_FULLSCREEN) || \ 428 if ( expect_mouse_up & (1<<button) ) { \
424 !NSPointInRect([event locationInWindow], titleBarRect) ) \ 429 SDL_PrivateMouseButton (SDL_RELEASED, button, 0, 0); \
425 SDL_PrivateMouseButton (SDL_RELEASED, button, 0, 0); \ 430 expect_mouse_up &= ~(1<<button); \
426 [ NSApp sendEvent:event ]; \ 431 } \
432 [ NSApp sendEvent:event ]; \
427 } while(0) 433 } while(0)
428 434
429 type = [ event type ]; 435 type = [ event type ];
430 isForGameWin = (qz_window == [ event window ]); 436 isForGameWin = (qz_window == [ event window ]);
437 isInGameWin = NSPointInRect([event locationInWindow], winRect);
431 switch (type) { 438 switch (type) {
432
433 case NSLeftMouseDown: 439 case NSLeftMouseDown:
434 if ( getenv("SDL_HAS3BUTTONMOUSE") ) { 440 if ( getenv("SDL_HAS3BUTTONMOUSE") ) {
435 DO_MOUSE_DOWN (1, 1); 441 DO_MOUSE_DOWN (SDL_BUTTON_LEFT);
436 } else { 442 } else {
437 if ( NSCommandKeyMask & current_mods ) { 443 if ( NSCommandKeyMask & current_mods ) {
438 last_virtual_button = 3; 444 last_virtual_button = SDL_BUTTON_RIGHT;
439 DO_MOUSE_DOWN (3, 0); 445 DO_MOUSE_DOWN (SDL_BUTTON_RIGHT);
440 } 446 }
441 else if ( NSAlternateKeyMask & current_mods ) { 447 else if ( NSAlternateKeyMask & current_mods ) {
442 last_virtual_button = 2; 448 last_virtual_button = SDL_BUTTON_MIDDLE;
443 DO_MOUSE_DOWN (2, 0); 449 DO_MOUSE_DOWN (SDL_BUTTON_MIDDLE);
444 } 450 }
445 else { 451 else {
446 DO_MOUSE_DOWN (1, 1); 452 DO_MOUSE_DOWN (SDL_BUTTON_LEFT);
447 } 453 }
448 } 454 }
449 break; 455 break;
450 case NSOtherMouseDown: DO_MOUSE_DOWN (2, 0); break; 456 case NSOtherMouseDown: DO_MOUSE_DOWN (SDL_BUTTON_MIDDLE); break;
451 case NSRightMouseDown: DO_MOUSE_DOWN (3, 0); break; 457 case NSRightMouseDown: DO_MOUSE_DOWN (SDL_BUTTON_RIGHT); break;
452 case NSLeftMouseUp: 458 case NSLeftMouseUp:
453 if ( last_virtual_button != 0 ) { 459 if ( last_virtual_button != 0 ) {
454 DO_MOUSE_UP (last_virtual_button, 0); 460 DO_MOUSE_UP (last_virtual_button);
455 last_virtual_button = 0; 461 last_virtual_button = 0;
456 } 462 }
457 else { 463 else {
458 DO_MOUSE_UP (1, 1); 464 DO_MOUSE_UP (SDL_BUTTON_LEFT);
459 } 465 }
460 break; 466 break;
461 case NSOtherMouseUp: DO_MOUSE_UP (2, 0); break; 467 case NSOtherMouseUp: DO_MOUSE_UP (SDL_BUTTON_MIDDLE); break;
462 case NSRightMouseUp: DO_MOUSE_UP (3, 0); break; 468 case NSRightMouseUp: DO_MOUSE_UP (SDL_BUTTON_RIGHT); break;
463 case NSSystemDefined: 469 case NSSystemDefined:
464 /* 470 /*
465 Future: up to 32 "mouse" buttons can be handled. 471 Future: up to 32 "mouse" buttons can be handled.
466 if ([event subtype] == 7) { 472 if ([event subtype] == 7) {
467 unsigned int buttons; 473 unsigned int buttons;
470 break; 476 break;
471 case NSLeftMouseDragged: 477 case NSLeftMouseDragged:
472 case NSRightMouseDragged: 478 case NSRightMouseDragged:
473 case NSOtherMouseDragged: /* usually middle mouse dragged */ 479 case NSOtherMouseDragged: /* usually middle mouse dragged */
474 case NSMouseMoved: 480 case NSMouseMoved:
475 if (current_grab_mode == SDL_GRAB_ON) { 481 if ( grab_state == QZ_INVISIBLE_GRAB ) {
476 482
477 /* 483 /*
478 If input is grabbed, the cursor doesn't move, 484 If input is grabbed+hidden, the cursor doesn't move,
479 so we have to call the lowlevel window server 485 so we have to call the lowlevel window server
480 function. This is less accurate but works OK. 486 function. This is less accurate but works OK.
481 */ 487 */
482 CGMouseDelta dx1, dy1; 488 CGMouseDelta dx1, dy1;
483 CGGetLastMouseDelta (&dx1, &dy1); 489 CGGetLastMouseDelta (&dx1, &dy1);
484 dx += dx1; 490 dx += dx1;
485 dy += dy1; 491 dy += dy1;
486 }
487 else if (warp_flag) {
488
489 /*
490 If we just warped the mouse, the cursor is frozen for a while.
491 So we have to use the lowlevel function until it
492 unfreezes. This really helps apps that continuously
493 warp the mouse to keep it in the game window. Developers should
494 really use GrabInput, but our GrabInput freezes the HW cursor,
495 which doesn't cut it for some apps.
496 */
497 Uint32 ticks;
498
499 ticks = SDL_GetTicks();
500 if (ticks - warp_ticks < 150) {
501
502 CGMouseDelta dx1, dy1;
503 CGGetLastMouseDelta (&dx1, &dy1);
504 dx += dx1;
505 dy += dy1;
506 }
507 else {
508
509 warp_flag = 0;
510 }
511 } 492 }
512 else if (firstMouseEvent) { 493 else if (firstMouseEvent) {
513 494
514 /* 495 /*
515 Get the first mouse event in a possible 496 Get the first mouse event in a possible
518 compensate any inaccuracy in deltas, and 499 compensate any inaccuracy in deltas, and
519 provides the first known mouse position, 500 provides the first known mouse position,
520 since everything after this uses deltas 501 since everything after this uses deltas
521 */ 502 */
522 NSPoint p = [ event locationInWindow ]; 503 NSPoint p = [ event locationInWindow ];
523 QZ_PrivateCocoaToSDL(this, &p); 504 QZ_PrivateCocoaToSDL (this, &p);
524
525 SDL_PrivateMouseMotion (0, 0, p.x, p.y); 505 SDL_PrivateMouseMotion (0, 0, p.x, p.y);
526
527 firstMouseEvent = 0; 506 firstMouseEvent = 0;
528 } 507 }
529 else { 508 else {
530 509
531 /* 510 /*
533 add it on for one big move event at the end. 512 add it on for one big move event at the end.
534 */ 513 */
535 dx += [ event deltaX ]; 514 dx += [ event deltaX ];
536 dy += [ event deltaY ]; 515 dy += [ event deltaY ];
537 } 516 }
517
518 /*
519 Handle grab input+cursor visible by warping the cursor back
520 into the game window. This still generates a mouse moved event,
521 but not as a result of the warp (so it's in the right direction).
522 */
523 if ( grab_state == QZ_VISIBLE_GRAB &&
524 !isInGameWin ) {
525
526 NSPoint p = [ event locationInWindow ];
527 QZ_PrivateCocoaToSDL (this, &p);
528
529 if ( p.x < 0.0 )
530 p.x = 0.0;
531
532 if ( p.y < 0.0 )
533 p.y = 0.0;
534
535 if ( p.x >= winRect.size.width )
536 p.x = winRect.size.width-1;
537
538 if ( p.y >= winRect.size.height )
539 p.y = winRect.size.height-1;
540
541 QZ_PrivateWarpCursor (this, p.x, p.y);
542 }
538 break; 543 break;
539 case NSScrollWheel: 544 case NSScrollWheel:
540 if (NSPointInRect([ event locationInWindow ], winRect)) { 545 if ( isInGameWin ) {
541 float dy; 546 float dy;
542 Uint8 button; 547 Uint8 button;
543 dy = [ event deltaY ]; 548 dy = [ event deltaY ];
544 if ( dy > 0.0 ) /* Scroll up */ 549 if ( dy > 0.0 ) /* Scroll up */
545 button = SDL_BUTTON_WHEELUP; 550 button = SDL_BUTTON_WHEELUP;
546 else /* Scroll down */ 551 else /* Scroll down */
547 button = SDL_BUTTON_WHEELDOWN; 552 button = SDL_BUTTON_WHEELDOWN;
548 /* For now, wheel is sent as a quick down+up */ 553 /* For now, wheel is sent as a quick down+up */
549 SDL_PrivateMouseButton (SDL_PRESSED, button, 0, 0); 554 SDL_PrivateMouseButton (SDL_PRESSED, button, 0, 0);
550 SDL_PrivateMouseButton (SDL_RELEASED, button, 0, 0); 555 SDL_PrivateMouseButton (SDL_RELEASED, button, 0, 0);
551 } 556 }
552 break; 557 break;
553 case NSKeyUp: 558 case NSKeyUp: