/*==LICENSE==* CyanWorlds.com Engine - MMOG client, server and tools Copyright (C) 2011 Cyan Worlds, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Additional permissions under GNU GPL version 3 section 7 If you modify this Program, or any covered work, by linking or combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK, NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK (or a modified version of those libraries), containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA, PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the licensors of this Program grant you additional permission to convey the resulting work. Corresponding Source for a non-source form of such a combination shall include the source code for the parts of OpenSSL and IJG JPEG Library used as well as that of the covered work. You can contact Cyan Worlds, Inc. by email legal@cyan.com or by snail mail at: Cyan Worlds, Inc. 14617 N Newport Hwy Mead, WA 99021 *==LICENSE==*/ // plInputDevice.cpp //#include "STRING" #include "hsConfig.h" #include "hsWindows.h" #include "plInputDevice.h" #include "plInputManager.h" #include "plAvatarInputInterface.h" #include "../plMessage/plInputEventMsg.h" #include "../pnMessage/plTimeMsg.h" #include "hsUtils.h" #include "plgDispatch.h" #include "../plPipeline/plPlates.h" #include "../plPipeline/plDebugText.h" #include "../plGImage/plMipmap.h" #include "hsWindows.h" #include "../NucleusLib/inc/plPipeline.h" // The resolution that uses the base size of the cursor. // All other resolutions will scale the cursor size to keep the same physical size. #define BASE_WIDTH 1024 #define BASE_HEIGHT 768 plKeyboardDevice* plKeyboardDevice::fInstance = nil; bool plKeyboardDevice::fKeyboardState[256]; hsBool plKeyboardDevice::fIgnoreCapsLock = false; hsBool plKeyboardDevice::fKeyIsDeadKey = false; plKeyboardDevice::plKeyboardDevice() : fShiftKeyDown(false), fCapsLockKeyDown(false), fAltKeyDown(false), fCtrlKeyDown(false), fCapsLockLock(false), fControlMode(STANDARD_MODE) { fInstance = this; InitKeyboardState(); } plKeyboardDevice::~plKeyboardDevice() { } void plKeyboardDevice::InitKeyboardState() { static bool initialized = false; if (!initialized) { for (unsigned int i = 0; i < 256; ++i) plKeyboardDevice::fKeyboardState[i] = false; initialized = true; } } void plKeyboardDevice::ReleaseAllKeys() { // send a key-up message for all "normal" keys for (unsigned int i = 0; i < 256; ++i) { if ((i == KEY_SHIFT) || (i == KEY_CTRL) || (i == KEY_CAPSLOCK)) continue; // these are handled slightly differently if (fKeyboardState[i]) { fKeyboardState[i] = false; // fake a key-up command plKeyEventMsg* pMsg = TRACKED_NEW plKeyEventMsg; pMsg->SetKeyCode( (plKeyDef)i ); pMsg->SetKeyDown( false ); pMsg->SetShiftKeyDown( fShiftKeyDown ); pMsg->SetCtrlKeyDown( fCtrlKeyDown ); pMsg->SetCapsLockKeyDown( fCapsLockLock ); pMsg->SetRepeat( false ); plgDispatch::MsgSend( pMsg ); } } // send key messages for shift and ctrl if necessary because the keys above need to have // the proper states of the shift and ctrl keys sent with their messages, and our internal // flags for these keys need to be cleared. We don't send a key-up message for caps lock // because it doesn't really operate like every other key on the keyboard if (fKeyboardState[KEY_SHIFT]) { fKeyboardState[KEY_SHIFT] = false; fShiftKeyDown = false; plKeyEventMsg* pMsg = TRACKED_NEW plKeyEventMsg; pMsg->SetKeyCode( KEY_SHIFT ); pMsg->SetKeyDown( false ); pMsg->SetShiftKeyDown( false ); pMsg->SetCtrlKeyDown( false ); pMsg->SetCapsLockKeyDown( fCapsLockLock ); pMsg->SetRepeat( false ); plgDispatch::MsgSend( pMsg ); } if (fKeyboardState[KEY_CTRL]) { fKeyboardState[KEY_CTRL] = false; fCtrlKeyDown = false; plKeyEventMsg* pMsg = TRACKED_NEW plKeyEventMsg; pMsg->SetKeyCode( KEY_CTRL ); pMsg->SetKeyDown( false ); pMsg->SetShiftKeyDown( false ); pMsg->SetCtrlKeyDown( false ); pMsg->SetCapsLockKeyDown( fCapsLockLock ); pMsg->SetRepeat( false ); plgDispatch::MsgSend( pMsg ); } } hsBool plKeyboardDevice::IsCapsLockKeyOn() { return fCapsLockLock; } void plKeyboardDevice::Shutdown() { } void plKeyboardDevice::HandleKeyEvent(plOSMsg message, plKeyDef key, bool bKeyDown, hsBool bKeyRepeat) { // update the internal keyboard state unsigned int keyCode = (unsigned int)key; if ((key >= 0) && (key < 256)) fKeyboardState[key] = bKeyDown; if (key == KEY_SHIFT) { fShiftKeyDown = bKeyDown; // return; } if (key == KEY_CTRL) { fCtrlKeyDown = bKeyDown; // return; } if (key == KEY_CAPSLOCK) { // Keyboards toggle the light on key-down, so I'm going with that. if (bKeyDown && !bKeyRepeat) { fCapsLockLock = !fCapsLockLock; plAvatarInputInterface::GetInstance()->ForceAlwaysRun(fCapsLockLock); } } // send a key event... plKeyEventMsg* pMsg = TRACKED_NEW plKeyEventMsg; pMsg->SetKeyCode( key ); pMsg->SetKeyDown( bKeyDown ); pMsg->SetShiftKeyDown( fShiftKeyDown ); pMsg->SetCtrlKeyDown( fCtrlKeyDown ); pMsg->SetCapsLockKeyDown( fCapsLockLock ); pMsg->SetRepeat(bKeyRepeat); plgDispatch::MsgSend( pMsg ); } void plKeyboardDevice::HandleWindowActivate(bool bActive, HWND hWnd) { if (bActive) { fCtrlKeyDown = false; } else { ReleaseAllKeys(); // send key-up events for everything since we're losing focus } } //// KeyEventToChar ////////////////////////////////////////////////////////// // Translate a Plasma key event to an actual char char plKeyboardDevice::KeyEventToChar( plKeyEventMsg *msg ) { short code = msg->GetKeyCode(); char c = 0; unsigned char *kbState = TRACKED_NEW unsigned char[256]; unsigned char *buffer = TRACKED_NEW unsigned char[256]; UINT scanCode; int retVal; buffer[0] = 0; switch( code ) { case KEY_A: case KEY_B: case KEY_C: case KEY_D: case KEY_E: case KEY_F: case KEY_G: case KEY_H: case KEY_I: case KEY_J: case KEY_K: case KEY_L: case KEY_M: case KEY_N: case KEY_O: case KEY_P: case KEY_Q: case KEY_R: case KEY_S: case KEY_T: case KEY_U: case KEY_V: case KEY_W: case KEY_X: case KEY_Y: case KEY_Z: case KEY_1: case KEY_2: case KEY_3: case KEY_4: case KEY_5: case KEY_6: case KEY_7: case KEY_8: case KEY_9: case KEY_0: case KEY_TILDE: case KEY_COMMA: case KEY_PERIOD: case KEY_LBRACKET: case KEY_RBRACKET: case KEY_BACKSLASH: case KEY_SLASH: case KEY_DASH: case KEY_EQUAL: case KEY_SEMICOLON: case KEY_QUOTE: // let windows translate everything for us! scanCode = MapVirtualKeyEx(code,0,GetKeyboardLayout(0)); GetKeyboardState(kbState); if (fIgnoreCapsLock) kbState[KEY_CAPSLOCK] = 0; // clear the caps lock key retVal = ToAsciiEx(code,scanCode,kbState,(unsigned short*)buffer,0,GetKeyboardLayout(0)); if (retVal == 2) { if ((buffer[0] == buffer[1]) && (!fKeyIsDeadKey)) { // it's actually a dead key, since the previous key wasn't a dead key c = (char)buffer[0]; fKeyIsDeadKey = true; } else { c = (char)buffer[1]; // it was an untranslated dead key, so copy the unconverted key fKeyIsDeadKey = false; } } else if (retVal == 0) c = 0; // it's invalid else { c = (char)buffer[0]; if (retVal < 0) // the key was a dead key fKeyIsDeadKey = true; else fKeyIsDeadKey = false; } break; case KEY_ESCAPE: c = 27; break; case KEY_TAB: c = '\t'; break; case KEY_BACKSPACE: c = 8; break; case KEY_ENTER: c = '\n'; break; case KEY_SPACE: c = ' '; break; // numlock on numbers case KEY_NUMPAD0: c = '0'; break; case KEY_NUMPAD1: c = '1'; break; case KEY_NUMPAD2: c = '2'; break; case KEY_NUMPAD3: c = '3'; break; case KEY_NUMPAD4: c = '4'; break; case KEY_NUMPAD5: c = '5'; break; case KEY_NUMPAD6: c = '6'; break; case KEY_NUMPAD7: c = '7'; break; case KEY_NUMPAD8: c = '8'; break; case KEY_NUMPAD9: c = '9'; break; // everything else default: c = 0; break; } delete [] kbState; delete [] buffer; return c; } // // // // plMouseDevice // // bool plMouseDevice::bMsgAlways = true; bool plMouseDevice::bCursorHidden = false; bool plMouseDevice::bCursorOverride = false; bool plMouseDevice::bInverted = false; hsScalar plMouseDevice::fWidth = BASE_WIDTH; hsScalar plMouseDevice::fHeight = BASE_HEIGHT; plMouseDevice* plMouseDevice::fInstance = 0; plMouseDevice::plMouseDevice() { fXPos = 0; fYPos = 0; fCursorID = CURSOR_UP; fButtonState = 0; fOpacity = 1.f; fCursor = nil; CreateCursor( fCursorID ); plMouseDevice::fInstance = this; fXMsg = nil; fYMsg = nil; fB2Msg = nil; fLeftBMsg[0] = nil; fLeftBMsg[1] = nil; fRightBMsg[0] = nil; fRightBMsg[1] = nil; fMiddleBMsg[0] = nil; fMiddleBMsg[1] = nil; } plMouseDevice::~plMouseDevice() { plPlateManager::Instance().DestroyPlate( fCursor ); fCursor = nil; plMouseDevice::fInstance = nil; } void plMouseDevice::SetDisplayResolution(hsScalar Width, hsScalar Height) { fWidth = Width; fHeight = Height; IUpdateCursorSize(); } void plMouseDevice::CreateCursor( char* cursor ) { if( fCursor == nil ) { plPlateManager::Instance().CreatePlate( &fCursor ); fCursor->CreateFromResource(cursor); } else { fCursor->ReloadFromResource(cursor); } fCursor->SetPosition( 0, 0, 0 ); IUpdateCursorSize(); fCursor->SetVisible(!bCursorHidden); fCursor->SetOpacity( fOpacity ); } void plMouseDevice::IUpdateCursorSize() { if(fCursor) { // set the size of the cursor based on resolution. fCursor->SetSize( 2*fCursor->GetMipmap()->GetWidth()/fWidth, 2*fCursor->GetMipmap()->GetHeight()/fHeight ); } } void plMouseDevice::AddNameToCursor(const char* name) { if (fInstance && name) { plDebugText &txt = plDebugText::Instance(); txt.DrawString(fInstance->fWXPos + 12 ,fInstance->fWYPos - 7,name); } } void plMouseDevice::AddCCRToCursor() { if (fInstance) { plDebugText &txt = plDebugText::Instance(); txt.DrawString(fInstance->fWXPos + 12, fInstance->fWYPos - 17, "CCR"); } } void plMouseDevice::AddIDNumToCursor(UInt32 idNum) { if (fInstance && idNum) { plDebugText &txt = plDebugText::Instance(); char str[256]; sprintf(str, "%d",idNum); txt.DrawString(fInstance->fWXPos + 12 ,fInstance->fWYPos + 3,str); } } void plMouseDevice::SetCursorX(hsScalar x) { /// Set the cursor position if( fCursor == nil && !plMouseDevice::bCursorHidden) CreateCursor( fCursorID ); if (fCursor) fCursor->SetPosition( ( x * 2.0f ) - 1.0f, ( fYPos * 2.0f ) - 1.0f ); // plDebugText &txt = plDebugText::Instance(); // txt.DrawString(fWXPos + 20,fWYPos - 5,"test"); } void plMouseDevice::SetCursorY(hsScalar y) { /// Set the cursor position if( fCursor == nil && !plMouseDevice::bCursorHidden) CreateCursor( fCursorID ); if (fCursor) fCursor->SetPosition( ( fXPos * 2.0f ) - 1.0f, ( y * 2.0f ) - 1.0f ); // plDebugText &txt = plDebugText::Instance(); // txt.DrawString(fWXPos + 20,fWYPos - 10,"test"); } void plMouseDevice::HideCursor(hsBool override) { if( fInstance->fCursor != nil ) fInstance->fCursor->SetVisible( false ); plMouseDevice::bCursorOverride = (override != 0); plMouseDevice::bCursorHidden = true; } void plMouseDevice::ShowCursor(hsBool override) { if( !plMouseDevice::bCursorHidden ) return; if (plMouseDevice::bCursorOverride && !override) return; plMouseDevice::bCursorHidden = false; plMouseDevice::bCursorOverride = false; if( fInstance->fCursor == nil ) fInstance->CreateCursor( fInstance->fCursorID ); fInstance->fCursor->SetVisible( true ); } void plMouseDevice::NewCursor(char* cursor) { fInstance->fCursorID = cursor; fInstance->CreateCursor(cursor); fInstance->SetCursorX(fInstance->GetCursorX()); fInstance->SetCursorY(fInstance->GetCursorY()); } void plMouseDevice::SetCursorOpacity( hsScalar opacity ) { fInstance->fOpacity = opacity; if( fInstance->fCursor != nil ) fInstance->fCursor->SetOpacity( opacity ); } hsBool plMouseDevice::MsgReceive(plMessage* msg) { plEvalMsg* pEMsg = plEvalMsg::ConvertNoRef(msg); if (pEMsg) { if (fXMsg) { plgDispatch::MsgSend(fXMsg); fXMsg = nil; } else { plMouseEventMsg* pMsg = TRACKED_NEW plMouseEventMsg; pMsg->SetXPos( fXPos ); pMsg->SetYPos( fYPos ); pMsg->SetDX(0); pMsg->SetDY(0); plgDispatch::MsgSend(pMsg); } if (fYMsg) { plgDispatch::MsgSend(fYMsg); fYMsg = nil; } else { plMouseEventMsg* pMsg = TRACKED_NEW plMouseEventMsg; pMsg->SetXPos( fXPos ); pMsg->SetYPos( fYPos ); pMsg->SetDX(0); pMsg->SetDY(0); plgDispatch::MsgSend(pMsg); } if( fB2Msg ) { fB2Msg->Send(); fB2Msg = nil; } // look for mouse button events in the queues to be sent now // ...Left mouse button if ( fLeftBMsg[0] != nil) { fLeftBMsg[0]->Send(); // slide queue elements over... get 'em on the next eval fLeftBMsg[0] = fLeftBMsg[1]; fLeftBMsg[1] = nil; } // ...Right mouse button if ( fRightBMsg[0] != nil) { fRightBMsg[0]->Send(); // slide queue elements over... get 'em on the next eval fRightBMsg[0] = fRightBMsg[1]; fRightBMsg[1] = nil; } // ...middle mouse button if ( fMiddleBMsg[0] != nil) { fMiddleBMsg[0]->Send(); // slide queue elements over... get 'em on the next eval fMiddleBMsg[0] = fMiddleBMsg[1]; fMiddleBMsg[1] = nil; } } plIMouseXEventMsg* pXMsg = plIMouseXEventMsg::ConvertNoRef(msg); if (pXMsg) { // send a mouse event plMouseEventMsg* pMsg = TRACKED_NEW plMouseEventMsg; if (pXMsg->fX == 999) pMsg->SetXPos( fXPos + 0.001f ); else if (pXMsg->fX == -999) pMsg->SetXPos( fXPos - 0.001f ); else pMsg->SetXPos(pXMsg->fX); pMsg->SetYPos( fYPos ); pMsg->SetDX( ( fXPos - pMsg->GetXPos()) ); pMsg->SetDY(0); if (pMsg->GetDX() == 0.0f && !plMouseDevice::bMsgAlways) { delete pMsg; return true; } if (fXMsg) delete fXMsg; fXMsg = pMsg; if (pXMsg->fX == 999) fXPos += 0.01; else if (pXMsg->fX == -999) fXPos -= 0.01; else fXPos = pXMsg->fX; SetCursorX(fXPos); fWXPos = pXMsg->fWx; return true; } plIMouseYEventMsg* pYMsg = plIMouseYEventMsg::ConvertNoRef(msg); if (pYMsg) { // send a mouse event plMouseEventMsg* pMsg = TRACKED_NEW plMouseEventMsg; pMsg->SetXPos( fXPos ); if (pYMsg->fY == 999) pMsg->SetYPos( fYPos + 0.01f ); else if (pYMsg->fY == -999) pMsg->SetYPos( fYPos - 0.01f ); else pMsg->SetYPos(pYMsg->fY); pMsg->SetDX(0); pMsg->SetDY(fYPos - pMsg->GetYPos()); if (pMsg->GetDY() == 0.0f && !plMouseDevice::bMsgAlways) { delete pMsg; return true; } if (fYMsg) delete fYMsg; fYMsg = pMsg; if (pYMsg->fY == 999) fYPos += 0.01; else if (pYMsg->fY == -999) fYPos -= 0.01; else fYPos = pYMsg->fY; fWYPos = pYMsg->fWy; SetCursorY(fYPos); return true; } plIMouseBEventMsg* pBMsg = plIMouseBEventMsg::ConvertNoRef(msg); if (pBMsg) { // send a mouse event plMouseEventMsg* pMsg = TRACKED_NEW plMouseEventMsg; pMsg->SetXPos( fXPos ); pMsg->SetYPos( fYPos ); pMsg->SetDX(0); pMsg->SetDY(0); bool deleteMe = true; // which button is different? if (pBMsg->fButton & kLeftButtonDown && !(fButtonState & kLeftButtonDown)) { // left button now down fButtonState |= kLeftButtonDown; pMsg->SetButton( kLeftButtonDown ); deleteMe = false; } else if (pBMsg->fButton & kLeftButtonUp && fButtonState & kLeftButtonDown) { // left button now up fButtonState &= ~kLeftButtonDown; pMsg->SetButton( kLeftButtonUp ); deleteMe = false; } else if (pBMsg->fButton & kRightButtonDown && !(fButtonState & kRightButtonDown)) { // right button now down fButtonState |= kRightButtonDown; pMsg->SetButton( kRightButtonDown ); deleteMe = false; } else if (pBMsg->fButton & kRightButtonUp && fButtonState & kRightButtonDown) { // right button now up fButtonState &= ~kRightButtonDown; pMsg->SetButton( kRightButtonUp ); deleteMe = false; } else if (pBMsg->fButton & kMiddleButtonDown && !(fButtonState & kMiddleButtonDown)) { // mouse wheel button now down fButtonState |= kMiddleButtonDown; pMsg->SetButton( kMiddleButtonDown ); deleteMe = false; } else if (pBMsg->fButton & kMiddleButtonUp && fButtonState & kMiddleButtonDown) { // right button now up fButtonState &= ~kMiddleButtonDown; pMsg->SetButton( kMiddleButtonUp ); deleteMe = false; } if (pBMsg->fButton & kRightButtonDblClk) { // right button dbl clicked, send TWO messages plMouseEventMsg* pMsg2 = TRACKED_NEW plMouseEventMsg; pMsg2->SetXPos( fXPos ); pMsg2->SetYPos( fYPos ); pMsg2->SetDX(0); pMsg2->SetDY(0); pMsg2->SetButton( kRightButtonDblClk ); if( fB2Msg != nil ) delete fB2Msg; fB2Msg = pMsg2; pMsg->SetButton( kRightButtonDown ); deleteMe = false; } else if (pBMsg->fButton & kLeftButtonDblClk) { // left button dbl clicked, send TWO messages plMouseEventMsg* pMsg2 = TRACKED_NEW plMouseEventMsg; pMsg2->SetXPos( fXPos ); pMsg2->SetYPos( fYPos ); pMsg2->SetDX(0); pMsg2->SetDY(0); pMsg2->SetButton( kLeftButtonDblClk ); if( fB2Msg != nil ) delete fB2Msg; fB2Msg = pMsg2; pMsg->SetButton( kLeftButtonDown ); deleteMe = false; } if( deleteMe ) { // mouse button state not changed delete pMsg; return true; } // we are going to save up to two button mouse events per button (left and right) // that will be dispatched on the next eval // which button is this for? if ( pMsg->GetButton() == kLeftButtonDown || pMsg->GetButton() == kLeftButtonUp ) { // see if the queue is just empty if ( fLeftBMsg[0] == nil) { // nothing to think about... goes in first slot fLeftBMsg[0] = pMsg; } else if (fLeftBMsg[1] == nil) { // nothing to think about... goes in second slot fLeftBMsg[1] = pMsg; } else { // else queue if full... need to make some decisions plMouseEventMsg* lastMsg = plMouseEventMsg::ConvertNoRef(pMsg); // ...if this is an up event and [1] is a down then we need to remove both // ...because we can't lose the up event and the down will have no match if ( pMsg->GetButton() == kLeftButtonUp && lastMsg && lastMsg->GetButton() == kLeftButtonDown) { delete pMsg; delete fLeftBMsg[1]; fLeftBMsg[1] = nil; } // ... otherwise ignore this event else { delete pMsg; } } } else if ( pMsg->GetButton() == kRightButtonDown || pMsg->GetButton() == kRightButtonUp ) { // see if the queue is just empty if ( fRightBMsg[0] == nil) { // nothing to think about... goes in first slot fRightBMsg[0] = pMsg; } else if (fRightBMsg[1] == nil) { // nothing to think about... goes in second slot fRightBMsg[1] = pMsg; } else { // else queue if full... need to make some decisions plMouseEventMsg* lastMsg = plMouseEventMsg::ConvertNoRef(pMsg); // ...if this is an up event and [1] is a down then we need to remove both // ...because we can't lose the up event and the down will have no match if ( pMsg->GetButton() == kRightButtonUp && lastMsg && lastMsg->GetButton() == kRightButtonDown) { delete pMsg; delete fRightBMsg[1]; fRightBMsg[1] = nil; } // ... otherwise ignore this event else { delete pMsg; } } } else if ( pMsg->GetButton() == kMiddleButtonDown || pMsg->GetButton() == kMiddleButtonUp ) { // see if the queue is just empty if ( fMiddleBMsg[0] == nil) { // nothing to think about... goes in first slot fMiddleBMsg[0] = pMsg; } else if (fMiddleBMsg[1] == nil) { // nothing to think about... goes in second slot fMiddleBMsg[1] = pMsg; } else { // else queue if full... need to make some decisions plMouseEventMsg* lastMsg = plMouseEventMsg::ConvertNoRef(pMsg); // ...if this is an up event and [1] is a down then we need to remove both // ...because we can't lose the up event and the down will have no match if ( pMsg->GetButton() == kMiddleButtonUp && lastMsg && lastMsg->GetButton() == kMiddleButtonDown) { delete pMsg; delete fMiddleBMsg[1]; fMiddleBMsg[1] = nil; } // ... otherwise ignore this event else { delete pMsg; } } } // we are going to dispatch the mouse button events right away // and not wait for the next eval, because we shouldn't miss one of these return true; } return false; }