Nonexclusive mouse input with OIS on OSX

Problems building or running the engine, queries about how to use features etc.
Post Reply
arthare
Halfling
Posts: 44
Joined: Fri Mar 29, 2013 5:40 pm
x 4

Nonexclusive mouse input with OIS on OSX

Post by arthare »

I've done a bunch of searching, and the last thing I could find was from 2011, where it seemed that it wasn't possible to have nonexclusive mouse input on the mac. I've had a couple customer complaints about it (my game doesn't require mouse input - it's played by riding a bicycle).

Has this changed? If not, can anyone suggest a way I can fake it? I'm thinking if the mouse gets within 10 pixels of the edge of my window, I'll call some magical function to make my window not the topmost (or at least, not active). Anyone know if there's an ogre or OSX function that'll do that for me?
User avatar
areay
Bugbear
Posts: 819
Joined: Wed May 05, 2010 4:59 am
Location: Auckland, NZ
x 69

Re: Nonexclusive mouse input with OIS on OSX

Post by areay »

This is all I could find http://ogre3d.org/forums/viewtopic.php?f=2&t=64155 TLDR; looks like OIS might not support this for Mac.

I have verified that the current release on SF still doesn't support this but I did find another user's homebrew patch to add it in http://www.wreckedgames.com/forum/index ... pic=1176.0
arthare
Halfling
Posts: 44
Joined: Fri Mar 29, 2013 5:40 pm
x 4

Re: Nonexclusive mouse input with OIS on OSX

Post by arthare »

Rats. Identical to my own findings.
arthare
Halfling
Posts: 44
Joined: Fri Mar 29, 2013 5:40 pm
x 4

Re: Nonexclusive mouse input with OIS on OSX

Post by arthare »

If you're compiling OIS from source (it's actually pretty easy), it isn't hard to make the necessary changes to OIS::MacMouse.

I didn't want _entirely_ nonexclusive mouse input, just a way for the user to quickly get their cursor back. So I changed MacMouse to "Ungrab" the mouse if the user hit the edge of my window going fairly quickly.

I didn't look into full nonexclusion since it wasn't my goal, but I can't imagine it'd be hard to at least simulate. Just get rid of the CGAssociateMouseAndMouseCursorPosition(FALSE), CGDisplayHideCursor, and a couple others. It's only a couple hundred lines of code.

Here's my MacMouse.cpp. The key changes are in capture() [where I check for a fast-moving exit] and the new function "Ungrab()", where I give the cursor back to the OS.

Code: Select all

#include "mac/MacMouse.h"
#include "mac/MacInputManager.h"
#include "mac/MacHelpers.h"
#include "OISException.h"
#include "OISEvents.h"

#include <Carbon/Carbon.h>

#include <list>

#include <iostream>

using namespace OIS;

//Events we subscribe to and remove from queue
const EventTypeSpec mouseEvents[] = {
	{ kEventClassMouse, kEventMouseDown },
	{ kEventClassMouse, kEventMouseUp },
	{ kEventClassMouse, kEventMouseMoved },
	{ kEventClassMouse, kEventMouseDragged },
	{ kEventClassMouse, kEventMouseWheelMoved }		
};

const EventTypeSpec WinFocusAcquired [] = {{kEventClassApplication, kEventAppDeactivated}};

//-------------------------------------------------------------------//
MacMouse::MacMouse( InputManager* creator, bool buffered )
	: Mouse(creator->inputSystemName(), buffered, 0, creator), mNeedsToRegainFocus( false )
{
    mouseEventRef = NULL;
	mWindowFocusHandler = NULL;

    // Get a "Univeral procedure pointer" for our callback
    mouseUPP = NewEventHandlerUPP(MouseWrapper);
	mWindowFocusListener = NewEventHandlerUPP(WindowFocusChanged);

	static_cast<MacInputManager*>(mCreator)->_setMouseUsed(true);
}

MacMouse::~MacMouse()
{
    if(mouseEventRef != NULL)
		RemoveEventHandler(mouseEventRef);

	if(mWindowFocusHandler != NULL)
		RemoveEventHandler(mWindowFocusHandler);
	
	DisposeEventHandlerUPP(mouseUPP);
	DisposeEventHandlerUPP(mWindowFocusListener);
	
	// Restore Mouse
	CGAssociateMouseAndMouseCursorPosition(TRUE);
	CGDisplayShowCursor(kCGDirectMainDisplay);

	static_cast<MacInputManager*>(mCreator)->_setMouseUsed(false);
}

void MacMouse::_initialize()
{
	mState.clear();
	mTempState.clear();
	mMouseWarped = false;
	
	// Hide OS Mouse
 	CGDisplayHideCursor(kCGDirectMainDisplay);

	MacInputManager* im = static_cast<MacInputManager*>(mCreator);
	WindowRef win = im->_getWindow();
	
	if(win)
	{
		Rect clipRect = {0.0f, 0.0f, 0.0f, 0.0f};
		GetWindowBounds(win, kWindowContentRgn, &clipRect);
		
		CGPoint warpPoint;
		warpPoint.x = ((clipRect.right - clipRect.left) / 2) + clipRect.left;
		warpPoint.y = ((clipRect.bottom - clipRect.top) / 2) + clipRect.top;
		CGDisplayMoveCursorToPoint(kCGDirectMainDisplay, warpPoint); //Place at display origin
		
		mMouseWarped = true;
	}

	//Now that mouse is warped, start listening for events
	EventTargetRef event = ((MacInputManager*)mCreator)->_getEventTarget();
    
	if(mouseEventRef != NULL)
		RemoveEventHandler(mouseEventRef);
		
	if(mWindowFocusHandler != NULL)
		RemoveEventHandler(mWindowFocusHandler);

	mouseEventRef = mWindowFocusHandler = NULL;

	if(InstallEventHandler(event, mouseUPP, GetEventTypeCount(mouseEvents), mouseEvents, this, &mouseEventRef) != noErr)
		OIS_EXCEPT( E_General, "MacMouse::_initialize >> Error loading Mouse event handler" );

	if(InstallEventHandler(event, mWindowFocusListener, GetEventTypeCount(WinFocusAcquired), WinFocusAcquired, this, &mWindowFocusHandler) != noErr)
		OIS_EXCEPT( E_General, "MacMouse::_initialize >> Error loading Mouse event handler" );		

	//Lock OS Mouse movement
	mNeedsToRegainFocus = false;
	CGAssociateMouseAndMouseCursorPosition(FALSE);
}

OSStatus MacMouse::WindowFocusChanged(EventHandlerCallRef nextHandler, EventRef event, void* macMouse)
{
	//std::cout << "Window Focus Changed\n";

	MacMouse* _this = static_cast<MacMouse*>(macMouse);
    if (_this)
	{
    _this->Ungrab();

        // propagate the event down the chain
        return CallNextEventHandler(nextHandler, event);
    }
    else
        OIS_EXCEPT(E_General, "MouseWrapper >> Being called by something other than our event handler!");
}

void MacMouse::setBuffered( bool buffered )
{
	mBuffered = buffered;
}

void MacMouse::Ungrab()
{
  mNeedsToRegainFocus = true;
  CGAssociateMouseAndMouseCursorPosition(TRUE);
 	CGDisplayShowCursor(kCGDirectMainDisplay);
  
  { // setting OS cursor to where the game cursor is so the user has at least a mildly-happy outcome
    MacInputManager* im = static_cast<MacInputManager*>(mCreator);
    WindowRef win = im->_getWindow();
    
    Rect clipRect = {0.0f, 0.0f, 0.0f, 0.0f};
    GetWindowBounds(win, kWindowContentRgn, &clipRect);
		
    CGPoint warpPoint;
    warpPoint.x = clipRect.left + mState.X.abs;
    warpPoint.y = clipRect.top + mState.Y.abs;

    CGDisplayMoveCursorToPoint(kCGDirectMainDisplay, warpPoint); //Place at display origin
  }
}

void MacMouse::capture()
{
	mState.X.rel = 0;
	mState.Y.rel = 0;
	mState.Z.rel = 0;
	    
	if(mTempState.X.rel || mTempState.Y.rel || mTempState.Z.rel)
	{
		//printf("%i %i %i\n\n", mTempState.X.rel, mTempState.Y.rel, mTempState.Z.rel);

		//Set new relative motion values
		mState.X.rel = mTempState.X.rel;
		mState.Y.rel = mTempState.Y.rel;
		mState.Z.rel = mTempState.Z.rel;
		
		//Update absolute position
		mState.X.abs += mTempState.X.rel;
		mState.Y.abs += mTempState.Y.rel;
		
    int diffLevel = 0;
		if(mState.X.abs > mState.width)
    {
      diffLevel = mState.X.abs - mState.width;
			mState.X.abs = mState.width;
    }
		else if(mState.X.abs < 0)
    {
      diffLevel = -mState.X.abs;
			mState.X.abs = 0;
    }

		if(mState.Y.abs > mState.height)
    {
      diffLevel = mState.Y.abs - mState.height;
			mState.Y.abs = mState.height;
    }
		else if(mState.Y.abs < 0)
    {
      diffLevel = -mState.Y.abs;
			mState.Y.abs = 0;
    }
			
    if(diffLevel > 20) // if they hit the edge hard enough, then ungrab the mouse - the user wants to get out of this window...
    {
      Ungrab();
    }
    
		mState.Z.abs += mTempState.Z.rel;
		
		//Fire off event
		if(mListener && mBuffered)
			mListener->mouseMoved(MouseEvent(this, mState));
	}

	mTempState.clear();
}

void MacMouse::_mouseCallback( EventRef theEvent )
{
	OSStatus result = eventNotHandledErr;
    UInt32 kind = GetEventKind (theEvent);

	switch(kind)
	{
		case kEventMouseDragged:
		case kEventMouseMoved:
		{
			//HIPoint location = {0.0f, 0.0f};
			HIPoint delta = {0.0f, 0.0f};
			//Rect clipRect = {0.0f, 0.0f, 0.0f, 0.0f};
			
			if(mNeedsToRegainFocus)
				break;

			// Capture the parameters
			// TODO: Look into HIViewNewTrackingArea
			//GetEventParameter(theEvent, kEventParamMouseLocation, typeHIPoint, NULL, sizeof(HIPoint), NULL, &location);
			GetEventParameter(theEvent, kEventParamMouseDelta, typeHIPoint, NULL, sizeof(HIPoint), NULL, &delta);
			
			// relative positioning
			if(!mMouseWarped)
			{
				mTempState.X.rel += delta.x;
				mTempState.Y.rel += delta.y;
			}
			
			mMouseWarped = false;

			break;
		}
		case kEventMouseDown:
		{
			EventMouseButton button = 0;
			int mouseButton = 3;
			UInt32 modifiers = 0;
			
			if(mNeedsToRegainFocus)
				break;

			// Capture parameters
			GetEventParameter(theEvent, kEventParamMouseButton, typeMouseButton, NULL, sizeof(EventMouseButton), NULL, &button);
			GetEventParameter(theEvent, kEventParamKeyModifiers, typeUInt32, NULL, sizeof(UInt32), NULL, &modifiers);
			
			if((button == kEventMouseButtonTertiary) || ((button == kEventMouseButtonPrimary) && (modifiers & optionKey)))
			{
				mouseButton = 2;
				mState.buttons |= 1 << mouseButton;
			}
            else if((button == kEventMouseButtonSecondary) || ((button == kEventMouseButtonPrimary) && (modifiers & controlKey)))
            {	
                mouseButton = 1;
                mState.buttons |= 1 << mouseButton;
            }
            else if(button == kEventMouseButtonPrimary)
            {
                mouseButton = 0;
                mState.buttons |= 1 << mouseButton;
            }

            if( mListener && mBuffered )
                mListener->mousePressed( MouseEvent( this, mState ), (MouseButtonID)mouseButton );

            break;
		}
		case kEventMouseUp:
		{
			EventMouseButton button = 0;
			int mouseButton = 3;
			UInt32 modifiers = 0;
			
			if(mNeedsToRegainFocus)
			{
				mNeedsToRegainFocus = false;
				CGAssociateMouseAndMouseCursorPosition(false);
				
				MacInputManager* im = static_cast<MacInputManager*>(mCreator);
				WindowRef win = im->_getWindow();
				
				if(win)
				{
					Rect clipRect = {0.0f, 0.0f, 0.0f, 0.0f};
					GetWindowBounds(win, kWindowContentRgn, &clipRect);
					
					CGPoint warpPoint;
					warpPoint.x = ((clipRect.right - clipRect.left) / 2) + clipRect.left;
					warpPoint.y = ((clipRect.bottom - clipRect.top) / 2) + clipRect.top;
					CGDisplayMoveCursorToPoint(kCGDirectMainDisplay, warpPoint); //Place at display origin
					
					CGDisplayHideCursor(kCGDirectMainDisplay);
					
					mMouseWarped = true;
				}
				
				//Once we regain focus, we do not really know what state all the buttons are in - for now, set to not pressed. todo, check current status
				//compare against old status, and send off any needed events
				mState.buttons = 0;
				
				break;
			}
			
			// Capture parameters
			GetEventParameter(theEvent, kEventParamMouseButton, typeMouseButton, NULL, sizeof(EventMouseButton), NULL, &button);
			GetEventParameter(theEvent, kEventParamKeyModifiers, typeUInt32, NULL, sizeof(UInt32), NULL, &modifiers);
			
			if ((button == kEventMouseButtonTertiary) || ((button == kEventMouseButtonPrimary) && (modifiers & optionKey)))
			{
				mouseButton = 2;
				mState.buttons &= ~(1 << mouseButton);
			}
            else if ((button == kEventMouseButtonSecondary) || ((button == kEventMouseButtonPrimary) && (modifiers & controlKey)))
            {	
                mouseButton = 1;
                mState.buttons &= ~(1 << mouseButton);
            }
            else if (button == kEventMouseButtonPrimary)
            {
                mouseButton = 0;
                mState.buttons &= ~(1 << mouseButton);
            }

            if( mListener && mBuffered )
                mListener->mouseReleased( MouseEvent( this, mState ), (MouseButtonID)mouseButton );

            break;
		}
		case kEventMouseWheelMoved:
		{
			SInt32 wheelDelta = 0;
			EventMouseWheelAxis	wheelAxis = 0; 

			// Capture parameters
			GetEventParameter(theEvent, kEventParamMouseWheelAxis, typeMouseWheelAxis, NULL, sizeof(EventMouseWheelAxis), NULL, &wheelAxis);
			GetEventParameter(theEvent, kEventParamMouseWheelDelta, typeSInt32, NULL, sizeof(SInt32), NULL, &wheelDelta);
			
			// If the Y axis of the wheel changed, then update the Z
			// Does OIS care about the X wheel axis?
			if(wheelAxis == kEventMouseWheelAxisY)
				mTempState.Z.rel += (wheelDelta * 60);

            break;
		}
		default:
			break;
	}    
}
Post Reply