Skip to content

Commit

Permalink
Massive Quartz input enhancements from Darrell Walisser. His email:
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
icculus committed Dec 27, 2002
1 parent 654b446 commit 8af54d9
Show file tree
Hide file tree
Showing 4 changed files with 195 additions and 108 deletions.
157 changes: 81 additions & 76 deletions src/video/quartz/SDL_QuartzEvents.m
Expand Up @@ -300,16 +300,18 @@ static void QZ_DoModifiers (_THIS, unsigned int newMods) {
static void QZ_DoActivate (_THIS)
{
in_foreground = YES;

/* Regrab the mouse, only if it was previously grabbed */
if ( current_grab_mode == SDL_GRAB_ON ) {
QZ_WarpWMCursor (this, SDL_VideoSurface->w / 2, SDL_VideoSurface->h / 2);
CGAssociateMouseAndMouseCursorPosition (0);

/* Hide the mouse cursor if was hidden */
if (!cursor_visible) {
HideCursor ();
}

/* Hide the mouse cursor if inside the app window */
if (!QZ_cursor_visible) {
HideCursor ();
/* Regrab input, only if it was previously grabbed */
if ( current_grab_mode == SDL_GRAB_ON ) {

/* Restore cursor location if input was grabbed */
QZ_PrivateWarpCursor (this, cursor_loc.x, cursor_loc.y);
QZ_ChangeGrabState (this, QZ_ENABLE_GRAB);
}

SDL_PrivateAppActive (1, SDL_APPINPUTFOCUS);
Expand All @@ -319,15 +321,17 @@ static void QZ_DoDeactivate (_THIS) {

in_foreground = NO;

/* Ungrab mouse if it is grabbed */
if ( current_grab_mode == SDL_GRAB_ON ) {
CGAssociateMouseAndMouseCursorPosition (1);
}
/* Get the current cursor location, for restore on activate */
cursor_loc = [ NSEvent mouseLocation ]; /* global coordinates */
if (qz_window)
QZ_PrivateGlobalToLocal (this, &cursor_loc);
QZ_PrivateCocoaToSDL (this, &cursor_loc);

/* Reassociate mouse and cursor */
CGAssociateMouseAndMouseCursorPosition (1);

/* Show the mouse cursor */
if (!QZ_cursor_visible) {
ShowCursor ();
}
/* Show the cursor */
ShowCursor ();

SDL_PrivateAppActive (0, SDL_APPINPUTFOCUS);
}
Expand All @@ -342,10 +346,10 @@ void QZ_SleepNotificationHandler (void * refcon,
switch(messageType)
{
case kIOMessageSystemWillSleep:
IOAllowPowerChange(powerConnection, (long) messageArgument);
IOAllowPowerChange(power_connection, (long) messageArgument);
break;
case kIOMessageCanSystemSleep:
IOAllowPowerChange(powerConnection, (long) messageArgument);
IOAllowPowerChange(power_connection, (long) messageArgument);
break;
case kIOMessageSystemHasPoweredOn:
/* awake */
Expand All @@ -360,9 +364,9 @@ static void QZ_RegisterForSleepNotifications (_THIS)
IONotificationPortRef thePortRef;
io_object_t notifier;

powerConnection = IORegisterForSystemPower (this, &thePortRef, QZ_SleepNotificationHandler, &notifier);
power_connection = IORegisterForSystemPower (this, &thePortRef, QZ_SleepNotificationHandler, &notifier);

if (powerConnection == 0)
if (power_connection == 0)
NSLog(@"SDL: QZ_SleepNotificationHandler() IORegisterForSystemPower failed.");

rls = IONotificationPortGetRunLoopSource (thePortRef);
Expand Down Expand Up @@ -401,65 +405,67 @@ static void QZ_PumpEvents (_THIS)
event = [ NSApp nextEventMatchingMask:NSAnyEventMask
untilDate:distantPast
inMode: NSDefaultRunLoopMode dequeue:YES ];

if (event != nil) {

unsigned int type;
BOOL isForGameWin;

#define DO_MOUSE_DOWN(button, sendToWindow) do { \
BOOL isInGameWin;

#define DO_MOUSE_DOWN(button) do { \
if ( in_foreground ) { \
if ( (SDL_VideoSurface->flags & SDL_FULLSCREEN) || \
NSPointInRect([event locationInWindow], winRect) ) \
SDL_PrivateMouseButton (SDL_PRESSED, button, 0, 0); \
if ( isInGameWin ) { \
SDL_PrivateMouseButton (SDL_PRESSED, button, 0, 0); \
expect_mouse_up |= 1<<button; \
} \
} \
else { \
QZ_DoActivate (this); \
} \
[ NSApp sendEvent:event ]; \
} while(0)

#define DO_MOUSE_UP(button, sendToWindow) do { \
if ( (SDL_VideoSurface->flags & SDL_FULLSCREEN) || \
!NSPointInRect([event locationInWindow], titleBarRect) ) \
SDL_PrivateMouseButton (SDL_RELEASED, button, 0, 0); \
[ NSApp sendEvent:event ]; \
#define DO_MOUSE_UP(button) do { \
if ( expect_mouse_up & (1<<button) ) { \
SDL_PrivateMouseButton (SDL_RELEASED, button, 0, 0); \
expect_mouse_up &= ~(1<<button); \
} \
[ NSApp sendEvent:event ]; \
} while(0)

type = [ event type ];
isForGameWin = (qz_window == [ event window ]);
isInGameWin = NSPointInRect([event locationInWindow], winRect);
switch (type) {

case NSLeftMouseDown:
if ( getenv("SDL_HAS3BUTTONMOUSE") ) {
DO_MOUSE_DOWN (1, 1);
DO_MOUSE_DOWN (SDL_BUTTON_LEFT);
} else {
if ( NSCommandKeyMask & current_mods ) {
last_virtual_button = 3;
DO_MOUSE_DOWN (3, 0);
last_virtual_button = SDL_BUTTON_RIGHT;
DO_MOUSE_DOWN (SDL_BUTTON_RIGHT);
}
else if ( NSAlternateKeyMask & current_mods ) {
last_virtual_button = 2;
DO_MOUSE_DOWN (2, 0);
last_virtual_button = SDL_BUTTON_MIDDLE;
DO_MOUSE_DOWN (SDL_BUTTON_MIDDLE);
}
else {
DO_MOUSE_DOWN (1, 1);
DO_MOUSE_DOWN (SDL_BUTTON_LEFT);
}
}
break;
case NSOtherMouseDown: DO_MOUSE_DOWN (2, 0); break;
case NSRightMouseDown: DO_MOUSE_DOWN (3, 0); break;
case NSOtherMouseDown: DO_MOUSE_DOWN (SDL_BUTTON_MIDDLE); break;
case NSRightMouseDown: DO_MOUSE_DOWN (SDL_BUTTON_RIGHT); break;
case NSLeftMouseUp:
if ( last_virtual_button != 0 ) {
DO_MOUSE_UP (last_virtual_button, 0);
DO_MOUSE_UP (last_virtual_button);
last_virtual_button = 0;
}
else {
DO_MOUSE_UP (1, 1);
DO_MOUSE_UP (SDL_BUTTON_LEFT);
}
break;
case NSOtherMouseUp: DO_MOUSE_UP (2, 0); break;
case NSRightMouseUp: DO_MOUSE_UP (3, 0); break;
case NSOtherMouseUp: DO_MOUSE_UP (SDL_BUTTON_MIDDLE); break;
case NSRightMouseUp: DO_MOUSE_UP (SDL_BUTTON_RIGHT); break;
case NSSystemDefined:
/*
Future: up to 32 "mouse" buttons can be handled.
Expand All @@ -472,10 +478,10 @@ static void QZ_PumpEvents (_THIS)
case NSRightMouseDragged:
case NSOtherMouseDragged: /* usually middle mouse dragged */
case NSMouseMoved:
if (current_grab_mode == SDL_GRAB_ON) {
if ( grab_state == QZ_INVISIBLE_GRAB ) {

/*
If input is grabbed, the cursor doesn't move,
If input is grabbed+hidden, the cursor doesn't move,
so we have to call the lowlevel window server
function. This is less accurate but works OK.
*/
Expand All @@ -484,31 +490,6 @@ static void QZ_PumpEvents (_THIS)
dx += dx1;
dy += dy1;
}
else if (warp_flag) {

/*
If we just warped the mouse, the cursor is frozen for a while.
So we have to use the lowlevel function until it
unfreezes. This really helps apps that continuously
warp the mouse to keep it in the game window. Developers should
really use GrabInput, but our GrabInput freezes the HW cursor,
which doesn't cut it for some apps.
*/
Uint32 ticks;

ticks = SDL_GetTicks();
if (ticks - warp_ticks < 150) {

CGMouseDelta dx1, dy1;
CGGetLastMouseDelta (&dx1, &dy1);
dx += dx1;
dy += dy1;
}
else {

warp_flag = 0;
}
}
else if (firstMouseEvent) {

/*
Expand All @@ -520,10 +501,8 @@ static void QZ_PumpEvents (_THIS)
since everything after this uses deltas
*/
NSPoint p = [ event locationInWindow ];
QZ_PrivateCocoaToSDL(this, &p);

QZ_PrivateCocoaToSDL (this, &p);
SDL_PrivateMouseMotion (0, 0, p.x, p.y);

firstMouseEvent = 0;
}
else {
Expand All @@ -535,17 +514,43 @@ static void QZ_PumpEvents (_THIS)
dx += [ event deltaX ];
dy += [ event deltaY ];
}

/*
Handle grab input+cursor visible by warping the cursor back
into the game window. This still generates a mouse moved event,
but not as a result of the warp (so it's in the right direction).
*/
if ( grab_state == QZ_VISIBLE_GRAB &&
!isInGameWin ) {

NSPoint p = [ event locationInWindow ];
QZ_PrivateCocoaToSDL (this, &p);

if ( p.x < 0.0 )
p.x = 0.0;

if ( p.y < 0.0 )
p.y = 0.0;

if ( p.x >= winRect.size.width )
p.x = winRect.size.width-1;

if ( p.y >= winRect.size.height )
p.y = winRect.size.height-1;

QZ_PrivateWarpCursor (this, p.x, p.y);
}
break;
case NSScrollWheel:
if (NSPointInRect([ event locationInWindow ], winRect)) {
if ( isInGameWin ) {
float dy;
Uint8 button;
dy = [ event deltaY ];
if ( dy > 0.0 ) /* Scroll up */
button = SDL_BUTTON_WHEELUP;
else /* Scroll down */
button = SDL_BUTTON_WHEELDOWN;
/* For now, wheel is sent as a quick down+up */
/* For now, wheel is sent as a quick down+up */
SDL_PrivateMouseButton (SDL_PRESSED, button, 0, 0);
SDL_PrivateMouseButton (SDL_RELEASED, button, 0, 0);
}
Expand Down
30 changes: 27 additions & 3 deletions src/video/quartz/SDL_QuartzVideo.h
Expand Up @@ -132,8 +132,12 @@ typedef struct SDL_PrivateVideoData {
SDLKey keymap[256]; /* Mac OS X to SDL key mapping */
Uint32 current_mods; /* current keyboard modifiers, to track modifier state */
Uint32 last_virtual_button;/* last virtual mouse button pressed */
io_connect_t powerConnection; /* used with IOKit to detect wake from sleep */

io_connect_t power_connection; /* used with IOKit to detect wake from sleep */
Uint8 expect_mouse_up; /* used to determine when to send mouse up events */
Uint8 grab_state; /* used to manage grab behavior */
NSPoint cursor_loc; /* saved cursor coords, for activate/deactivate when grabbed */
BOOL cursor_visible; /* tells if cursor was hidden or not */

ImageDescriptionHandle yuv_idh;
MatrixRecordPtr yuv_matrix;
DecompressorComponent yuv_codec;
Expand Down Expand Up @@ -167,7 +171,11 @@ typedef struct SDL_PrivateVideoData {
#define keymap (this->hidden->keymap)
#define current_mods (this->hidden->current_mods)
#define last_virtual_button (this->hidden->last_virtual_button)
#define powerConnection (this->hidden->powerConnection)
#define power_connection (this->hidden->power_connection)
#define expect_mouse_up (this->hidden->expect_mouse_up)
#define grab_state (this->hidden->grab_state)
#define cursor_loc (this->hidden->cursor_loc)
#define cursor_visible (this->hidden->cursor_visible)

#define yuv_idh (this->hidden->yuv_idh)
#define yuv_matrix (this->hidden->yuv_matrix)
Expand All @@ -179,6 +187,22 @@ typedef struct SDL_PrivateVideoData {
#define yuv_height (this->hidden->yuv_height)
#define yuv_port (this->hidden->yuv_port)


/* grab states - the input is in one of these states */
enum {
QZ_UNGRABBED = 0,
QZ_VISIBLE_GRAB,
QZ_INVISIBLE_GRAB
};

/* grab actions - these can change the grabbed state */
enum {
QZ_ENABLE_GRAB = 0,
QZ_DISABLE_GRAB,
QZ_HIDECURSOR,
QZ_SHOWCURSOR
};

/*
Obscuring code: maximum number of windows above ours (inclusive)
Expand Down
2 changes: 1 addition & 1 deletion src/video/quartz/SDL_QuartzVideo.m
Expand Up @@ -130,6 +130,7 @@ static int QZ_VideoInit (_THIS, SDL_PixelFormat *video_format) {
/* Set misc globals */
current_grab_mode = SDL_GRAB_OFF;
in_foreground = YES;
cursor_visible = YES;

/* register for sleep notifications so wake from sleep generates SDL_VIDEOEXPOSE */
QZ_RegisterForSleepNotifications (this);
Expand Down Expand Up @@ -1491,7 +1492,6 @@ static void QZ_FreeHWYUV (_THIS, SDL_Overlay *overlay) {
QDFlushPortBuffer (port, nil);
}
*/

}
else {
port = [ window_view qdPort ];
Expand Down

0 comments on commit 8af54d9

Please sign in to comment.