// Ryzom - MMORPG Framework // Copyright (C) 2010 Winch Gate Property Limited // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero 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 Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . #include "stdpch.h" #include "nel/misc/i_xml.h" #include "nel/misc/file.h" #include "nel/misc/game_device_events.h" #include "game_share/xml_auto_ptr.h" #include "input_handler_manager.h" #include "interface_manager.h" #include "../actions.h" #include "../input.h" #include "../client_cfg.h" #include "custom_mouse.h" #include "../motion/user_controls.h" #include "../init.h" #include "../release.h" #include "../r2/editor.h" /////////// // USING // /////////// using namespace std; using namespace NLMISC; //////////// // GLOBAL // //////////// extern CActionsManager Actions; extern CActionsManager EditActions; // Hierachical timer H_AUTO_DECL ( RZ_Client_Pump_Events ) CInputHandlerManager* CInputHandlerManager::_Instance = NULL; // *************************************************************************** CInputHandlerManager::CInputHandlerManager() { _EventServer= NULL; _MouseButtonsReleased = noButton; _MouseButtonsDown = noButton; _MouseButtonsState = noButton; _MouseX = _MouseY = _MouseLastX = _MouseLastY = 0; _Focus = true; _MouseWheel = 0; _SkipInterfaceManager=false; _RecoverFocusLost = false; } // *************************************************************************** CInputHandlerManager::~CInputHandlerManager() { } // ******************************************************************************************** void CInputHandlerManager::releaseInstance() { if( _Instance ) { delete _Instance; _Instance = NULL; } } // *************************************************************************** void CInputHandlerManager::addToServer(NLMISC::CEventServer * server) { _EventServer = server; // System server->addListener(EventGDMouseMove, this); server->addListener(EventDestroyWindowId, this); server->addListener(EventSetFocusId, this); server->addListener(EventDisplayChangeId, this); // Mouse server->addListener(EventMouseMoveId, this); server->addListener(EventMouseDownId, this); server->addListener(EventMouseUpId, this); server->addListener(EventMouseWheelId, this); server->addListener(EventMouseDblClkId, this); // Keyboard server->addListener(EventCharId, this); server->addListener(EventKeyDownId, this); server->addListener(EventKeyUpId, this); } // *************************************************************************** void CInputHandlerManager::release() { // System _EventServer->removeListener(EventGDMouseMove, this); _EventServer->removeListener(EventDestroyWindowId, this); _EventServer->removeListener(EventSetFocusId, this); _EventServer->removeListener(EventDisplayChangeId, this); // Mouse _EventServer->removeListener(EventMouseMoveId, this); _EventServer->removeListener(EventMouseDownId, this); _EventServer->removeListener(EventMouseUpId, this); _EventServer->removeListener(EventMouseWheelId, this); _EventServer->removeListener(EventMouseDblClkId, this); // Keyboard _EventServer->removeListener(EventCharId, this); _EventServer->removeListener(EventKeyDownId, this); _EventServer->removeListener(EventKeyUpId, this); } // *************************************************************************** void CInputHandlerManager::operator ()(const NLMISC::CEvent &event) { HandleSystemCursorCapture(event); if (event == EventDisplayChangeId) { switch (getCurrentColorDepth()) { case 16: CustomMouse.setColorDepth(CCustomMouse::ColorDepth16); break; case 24: case 32: CustomMouse.setColorDepth(CCustomMouse::ColorDepth32); break; default: release(); ExitClientError(CI18N::get("uiUnsupportedNewColorDepth").toUtf8().c_str()); break; } } // Process message to InterfaceManager CInterfaceManager *pIM = CInterfaceManager::getInstance(); if (event==EventSetFocusId) { CEventSetFocus *pEvent=(CEventSetFocus *)&event; if (!pEvent->Get) { // Disactive all keys _MouseButtonsDown = noButton; _MouseButtonsReleased = noButton; _MouseButtonsState = noButton; _Focus = false; if (!_SkipInterfaceManager) { // if there was some control capturing the mouse, warn them that they lost the focus if (pIM->getCapturePointerLeft()) { pIM->getCapturePointerLeft()->handleEvent(CEventDescriptorSetFocus(pEvent->Get)); } pIM->setCapturePointerLeft(NULL); if (pIM->getCapturePointerRight()) { pIM->getCapturePointerRight()->handleEvent(CEventDescriptorSetFocus(pEvent->Get)); } pIM->setCapturePointerRight(NULL); UserControls.stopFreeLook(); } // be nice with other app : let the mouse reappear (useful in direct 3D mode with no hardware cursor) Driver->showCursor(true); CustomMouse.setSystemArrow(); } else { _RecoverFocusLost = true; // force to update mouse pos on next click or move Driver->showCursor(IsMouseCursorHardware()); _Focus = true; } if(!_SkipInterfaceManager) { // if (R2::getEditor().isInitialized() && (ClientCfg.R2EDEnabled || R2::getEditor().getCurrentTool()) ) { R2::getEditor().handleEvent(CEventDescriptorSetFocus(pEvent->Get)); } } // re-post FilteredEventServer.postEvent( event.clone() ); } // If want to skip the interface Manager if(_SkipInterfaceManager) { // redirect to FilteredEventServer FilteredEventServer.postEvent( event.clone() ); /* Invalidate Actions managers by simulate keyUp on all. Else can have this scenario: - keyDown ("forward" action valide) before teleporting - keyUp while teleporting (=> _SkipInterfaceManager==true) - action still valide when teleported */ EditActions.releaseAllKeyNoRunning(); Actions.releaseAllKeyNoRunning(); return; } // **** Event Focus // **** Event Keyboard if( event == EventKeyDownId || event == EventKeyUpId || event == EventCharId) { // if not handled, post to Action Manager if( !pIM->handleEvent( CEventDescriptorKey((const CEventKey &) event) ) ) { // See if handled by editor bool handled = false; if (R2::getEditor().isInitialized() && (ClientCfg.R2EDEnabled || R2::getEditor().getCurrentTool()) ) { handled = R2::getEditor().handleEvent(CEventDescriptorKey((const CEventKey &) event) ); } if (!handled) { // Event from the Keyboard (DOWN KEYS) if(event == EventKeyDownId) { CEventKeyDown* downEvent=(CEventKeyDown*)&event; if (!pIM->getCaptureKeyboard () || !EditActions.keyPushed (*downEvent)) Actions.keyPushed (*downEvent); } // Event from the Keyboard (UP KEYS) else if(event == EventKeyUpId) { CEventKeyUp* upEvent=(CEventKeyUp*)&event; EditActions.keyReleased (*upEvent); Actions.keyReleased (*upEvent); } } } /* if handled, still post released key for the following bug: - I press 'Z' to go 'forward' before entering chat - I enter chat, keeping Z pressed - I release 'Z' in chat (thus handled by pIM->handleEvent()) - the player continue running... */ else { // don't send keydown to action managers if(event==EventKeyUpId) { CEventKeyUp* upEvent=(CEventKeyUp*)&event; EditActions.keyReleased (*upEvent); Actions.keyReleased (*upEvent); } } } // **** Event Mouse else if(pIM->getPointer() && _Focus /* && pIM->isMouseHandlingEnabled() */ && ( event == EventMouseMoveId || event == EventMouseDownId || event == EventMouseUpId || event == EventMouseWheelId || event == EventMouseDblClkId ) ) { CViewPointer &rIP = *pIM->getPointer(); CEventDescriptorMouse eventDesc; sint32 x,y; rIP.getPointerDispPos (x, y); eventDesc.setX (x); eventDesc.setY (y); bool handled= false; // button down ? static volatile bool doTest = false; if (!doTest || (doTest && pIM->isMouseHandlingEnabled())) { if (event==EventMouseDownId) { if (_RecoverFocusLost) { handled |= updateMousePos((CEventMouse&)event, eventDesc); // must update mouse pos here, // because when app window focus is gained by a mouse click, this is // the only place where we can retrieve mouse pos before a mouse move _RecoverFocusLost = false; } if (!handled) { if (R2::getEditor().isInitialized() && (R2::isEditionCurrent() || R2::getEditor().getCurrentTool()) ) { handled |= R2::getEditor().handleEvent(eventDesc); } } CEventMouseDown *pEvent=(CEventMouseDown*)&event; // update states _MouseButtonsDown = (TMouseButton) (_MouseButtonsDown | pEvent->Button); _MouseButtonsReleased =(TMouseButton) (_MouseButtonsReleased & ~(pEvent->Button)); _MouseButtonsState = (TMouseButton) (_MouseButtonsState | pEvent->Button); rIP.setButtonState(_MouseButtonsState); updateMousePos((CEventMouse&)event, eventDesc); // handle Event if(pEvent->Button & leftButton) { eventDesc.setEventTypeExtended(CEventDescriptorMouse::mouseleftdown); handled|= pIM->handleEvent (eventDesc); } if(pEvent->Button & rightButton) { eventDesc.setEventTypeExtended(CEventDescriptorMouse::mouserightdown); handled|= pIM->handleEvent (eventDesc); } } // button up ? else if (event==EventMouseUpId) { CEventMouseUp *pEvent=(CEventMouseUp*)&event; // update states _MouseButtonsReleased = (TMouseButton) (_MouseButtonsReleased | pEvent->Button); _MouseButtonsDown =(TMouseButton) (_MouseButtonsDown & ~(pEvent->Button)); _MouseButtonsState = (TMouseButton) (_MouseButtonsState & ~(pEvent->Button)); rIP.setButtonState(_MouseButtonsState); updateMousePos((CEventMouse&)event, eventDesc); // handle Event if(pEvent->Button & leftButton) { eventDesc.setEventTypeExtended(CEventDescriptorMouse::mouseleftup); handled|= pIM->handleEvent (eventDesc); } if(pEvent->Button & rightButton) { eventDesc.setEventTypeExtended(CEventDescriptorMouse::mouserightup); handled|= pIM->handleEvent (eventDesc); } } // db click ? else if (event == EventMouseDblClkId ) { // TODO: yoyo make it work if needed (for now, seems preferable to manage in each ActionHandler) CEventMouseDblClk* pEvent=(CEventMouseDblClk*)&event; updateMousePos((CEventMouse&)event, eventDesc); // handle Event if(pEvent->Button & leftButton) { eventDesc.setEventTypeExtended(CEventDescriptorMouse::mouseleftdblclk); handled|= pIM->handleEvent (eventDesc); } if(pEvent->Button & rightButton) { eventDesc.setEventTypeExtended(CEventDescriptorMouse::mouserightdblclk); handled|= pIM->handleEvent (eventDesc); } } // mouse move? else if(event == EventMouseMoveId) { handled |= updateMousePos((CEventMouse&)event, eventDesc); } else if (event == EventMouseWheelId) { CEventMouseWheel *pEvent=(CEventMouseWheel*)&event; if (pEvent->Direction) _MouseWheel += 1; else _MouseWheel -= 1; updateMousePos((CEventMouse&)event, eventDesc); // handle Event now. if (_MouseWheel != 0) { eventDesc.setEventTypeExtended(CEventDescriptorMouse::mousewheel); eventDesc.setWheel(_MouseWheel); handled|= pIM->handleEvent (eventDesc); _MouseWheel = 0; } } } // if Event not handled, post to Action Manager if( !handled ) { bool handled = false; if (R2::getEditor().isInitialized() && (R2::isEditionCurrent() || R2::getEditor().getCurrentTool()) ) { handled = R2::getEditor().handleEvent(eventDesc); } if (!handled) { // post to Action Manager FilteredEventServer.postEvent( event.clone() ); } } } // **** Others else { FilteredEventServer.postEvent( event.clone() ); } } // *************************************************************************** bool CInputHandlerManager::updateMousePos(NLMISC::CEventMouse &event, CEventDescriptorMouse &eventDesc) { if (!IsMouseFreeLook()) { CEventMouseMove* mouseEvent=(CEventMouseMove*)&event; uint32 w, h; CInterfaceManager::getInstance()->getViewRenderer().getScreenSize(w, h); // compute new coords _MouseLastX = _MouseX; _MouseLastY = _MouseY; _MouseX = (sint32)(mouseEvent->X*w + 0.5f); _MouseY = (sint32)(mouseEvent->Y*h + 0.5f); // Process Move message only if not Null move if(_MouseX!=_MouseLastX || _MouseY!=_MouseLastY) { // Move the pointer //pIM->movePointer (_MouseX-_MouseLastX, _MouseY-_MouseLastY); CInterfaceManager *pIM = CInterfaceManager::getInstance(); pIM->movePointerAbs(_MouseX, _MouseY); CViewPointer &rIP = *pIM->getPointer(); // get new pointer pos. sint32 x,y; rIP.getPointerDispPos (x, y); eventDesc.setX (x); eventDesc.setY (y); // handle Event now. eventDesc.setEventTypeExtended(CEventDescriptorMouse::mousemove); return pIM->handleEvent (eventDesc); } } return false; } // *************************************************************************** void CInputHandlerManager::CComboKey::init(const CEventDescriptorKey &rDK) { Key= rDK.getKey(); CtrlFlags= 0; if( rDK.getKeyCtrl() ) CtrlFlags|= CtrlKey; if( rDK.getKeyShift() ) CtrlFlags|= ShiftKey; if( rDK.getKeyAlt() ) CtrlFlags|= AltKey; } // *************************************************************************** bool CInputHandlerManager::CComboKey::operator<(const CComboKey &c) const { if(Key!=c.Key) return Keyname,"input_config") ) { nlinfo("in a xml input config, the root node must be "); return false; } //get all system nodes xmlNodePtr cur = root->xmlChildrenNode; while (cur) { // Read all combo_key_chat setup. if ( !strcmp((char*)cur->name,"combo_key_chat") ) parseComboKeyChat(cur); cur= cur->next; } return true; } // *************************************************************************** void CInputHandlerManager::pumpEvents() { H_AUTO_USE ( RZ_Client_Pump_Events ) nlassert(_EventServer); // pump the src EventServer _EventServer->pump(true); // pump the filtered ones FilteredEventServer.pump(); } // *************************************************************************** void CInputHandlerManager::pumpEventsNoIM() { nlassert(_EventServer); // Set Mode to skip InterfaceManager Handling _SkipInterfaceManager= true; // pump the src EventServer _EventServer->pump(true); // pump the filtered ones FilteredEventServer.pump(true); // reset _SkipInterfaceManager= false; } // *************************************************************************** void CInputHandlerManager::resetPos (sint x, sint y) { _MouseLastX = x; _MouseX = x; _MouseY = y; _MouseLastY = y; } // *************************************************************************** void CInputHandlerManager::parseComboKeyChat(xmlNodePtr cur) { xmlNodePtr keySon = cur->xmlChildrenNode; while (keySon) { // check its a key if ( strcmp((char*)keySon->name,"key") ) { keySon= keySon->next; continue; } vector v; parseKey(keySon, v); for (uint i = 0; i < v.size(); ++i) _ComboKeyChat.insert(v[i]); // next keySon= keySon->next; } } // *************************************************************************** void CInputHandlerManager::parseKey(xmlNodePtr cur, std::vector &out) { // read it CXMLAutoPtr prop; string keyStr; string flagStr; // read id prop= (char*) xmlGetProp( cur, (xmlChar*) "id" ); if(prop) keyStr= (const char*)prop; // read flags prop= (char*) xmlGetProp( cur, (xmlChar*) "mod" ); if(prop) flagStr= (const char*)prop; // Build the Key CComboKey comboKey; // If an hexa code, translate if( keyStr.find("0x")!=string::npos ) { sint value; sscanf(keyStr.c_str(), "%x", &value); comboKey.Key= (TKey)value; } // else translate from enum else { comboKey.Key= CEventKey::getKeyFromString(keyStr); } // If valid key if(comboKey.Key==NLMISC::KeyCount) { nlwarning("Unknown Key Id in xml input file: %s", keyStr.c_str()); } else { // Test All flags if match flagStr for(uint i=0;i<8;i++) { comboKey.CtrlFlags= i; // Test if Control match if(comboKey.CtrlFlags & CComboKey::CtrlKey) { // If don't find c or C, abort if( flagStr.find('c')== string::npos && flagStr.find('C')== string::npos ) continue; } else { // if find 'c', then abort if( flagStr.find('c')!= string::npos ) continue; } // Test if Shift match if(comboKey.CtrlFlags & CComboKey::ShiftKey) { // If don't find s or S, abort if( flagStr.find('s')== string::npos && flagStr.find('S')== string::npos ) continue; } else { // if find 's', then abort if( flagStr.find('s')!= string::npos ) continue; } // Test if Alt match if(comboKey.CtrlFlags & CComboKey::AltKey) { // If don't find a or A, abort if( flagStr.find('a')== string::npos && flagStr.find('A')== string::npos ) continue; } else { // if find 'a', then abort if( flagStr.find('a')!= string::npos ) continue; } // If pass, Insert this combo out.push_back(comboKey); } } }