Index: ps/trunk/source/ps/ConfigDB.cpp =================================================================== --- ps/trunk/source/ps/ConfigDB.cpp (revision 15979) +++ ps/trunk/source/ps/ConfigDB.cpp (revision 15980) @@ -1,322 +1,375 @@ /* Copyright (C) 2014 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ #include "precompiled.h" #include #include "CLogger.h" #include "ConfigDB.h" #include "Filesystem.h" -#include "Parser.h" #include "ThreadUtil.h" #include "lib/allocators/shared_ptr.h" -typedef std::map TConfigMap; +typedef std::map TConfigMap; TConfigMap CConfigDB::m_Map[CFG_LAST]; VfsPath CConfigDB::m_ConfigFile[CFG_LAST]; static pthread_mutex_t cfgdb_mutex = PTHREAD_MUTEX_INITIALIZER; CConfigDB::CConfigDB() { // Recursive mutex needed for WriteFile pthread_mutexattr_t attr; int err; err = pthread_mutexattr_init(&attr); ENSURE(err == 0); err = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); ENSURE(err == 0); err = pthread_mutex_init(&cfgdb_mutex, &attr); ENSURE(err == 0); err = pthread_mutexattr_destroy(&attr); ENSURE(err == 0); } -#define GETVAL(T, type) \ - void CConfigDB::GetValue##T(EConfigNamespace ns, const CStr& name, type& value) \ - { \ - if (ns < 0 || ns >= CFG_LAST) \ - { \ - debug_warn(L"CConfigDB: Invalid ns value"); \ - return; \ - } \ - CScopeLock s(&cfgdb_mutex); \ - TConfigMap::iterator it = m_Map[CFG_COMMAND].find(name); \ - if (it != m_Map[CFG_COMMAND].end()) \ - { \ - it->second[0].Get##T(value); \ - return; \ - } \ - \ - for (int search_ns = ns; search_ns >= 0; search_ns--) \ - { \ - it = m_Map[search_ns].find(name); \ - if (it != m_Map[search_ns].end()) \ - { \ - it->second[0].Get##T(value); \ - return; \ - } \ - } \ - } - -GETVAL(Bool, bool) -GETVAL(Int, int) -GETVAL(Float, float) -GETVAL(Double, double) -GETVAL(String, std::string) +#define CHECK_NS(rval)\ + do {\ + if (ns < 0 || ns >= CFG_LAST)\ + {\ + debug_warn(L"CConfigDB: Invalid ns value");\ + return rval;\ + }\ + } while (false) +namespace { +template void Get(const CStr& value, T& ret) +{ + std::stringstream ss(value); + ss >> ret; +} +template<> void Get<>(const CStr& value, bool& ret) +{ + ret = value == "true"; +} +template<> void Get<>(const CStr& value, std::string& ret) +{ + ret = value; +} +std::string EscapeString(const CStr& str) +{ + std::string ret; + for (size_t i = 0; i < str.length(); ++i) + { + if (str[i] == '\\') + ret += "\\\\"; + else if (str[i] == '"') + ret += "\\\""; + else + ret += str[i]; + } + return ret; +} +} // namespace + +#define GETVAL(type)\ + void CConfigDB::GetValue(EConfigNamespace ns, const CStr& name, type& value)\ + {\ + CHECK_NS();\ + CScopeLock s(&cfgdb_mutex);\ + TConfigMap::iterator it = m_Map[CFG_COMMAND].find(name);\ + if (it != m_Map[CFG_COMMAND].end())\ + {\ + Get(it->second[0], value);\ + return;\ + }\ + for (int search_ns = ns; search_ns >= 0; search_ns--)\ + {\ + it = m_Map[search_ns].find(name);\ + if (it != m_Map[search_ns].end())\ + {\ + Get(it->second[0], value);\ + return;\ + }\ + }\ + } +GETVAL(bool) +GETVAL(int) +GETVAL(float) +GETVAL(double) +GETVAL(std::string) #undef GETVAL void CConfigDB::GetValues(EConfigNamespace ns, const CStr& name, CConfigValueSet& values) { - if (ns < 0 || ns >= CFG_LAST) - { - debug_warn(L"CConfigDB: Invalid ns value"); - return; - } + CHECK_NS(); CScopeLock s(&cfgdb_mutex); TConfigMap::iterator it = m_Map[CFG_COMMAND].find(name); if (it != m_Map[CFG_COMMAND].end()) { values = it->second; return; } for (int search_ns = ns; search_ns >= 0; search_ns--) { it = m_Map[search_ns].find(name); if (it != m_Map[search_ns].end()) { values = it->second; return; } } } EConfigNamespace CConfigDB::GetValueNamespace(EConfigNamespace ns, const CStr& name) { - if (ns < 0 || ns >= CFG_LAST) - { - debug_warn(L"CConfigDB: Invalid ns value"); - return CFG_LAST; - } + CHECK_NS(CFG_LAST); CScopeLock s(&cfgdb_mutex); TConfigMap::iterator it = m_Map[CFG_COMMAND].find(name); if (it != m_Map[CFG_COMMAND].end()) return CFG_COMMAND; for (int search_ns = ns; search_ns >= 0; search_ns--) { it = m_Map[search_ns].find(name); if (it != m_Map[search_ns].end()) return (EConfigNamespace)search_ns; } return CFG_LAST; } std::map CConfigDB::GetValuesWithPrefix(EConfigNamespace ns, const CStr& prefix) { CScopeLock s(&cfgdb_mutex); std::map ret; - if (ns < 0 || ns >= CFG_LAST) - { - debug_warn(L"CConfigDB: Invalid ns value"); - return ret; - } + CHECK_NS(ret); // Loop upwards so that values in later namespaces can override // values in earlier namespaces for (int search_ns = 0; search_ns <= ns; search_ns++) { for (TConfigMap::iterator it = m_Map[search_ns].begin(); it != m_Map[search_ns].end(); ++it) { if (boost::algorithm::starts_with(it->first, prefix)) ret[it->first] = it->second; } } for (TConfigMap::iterator it = m_Map[CFG_COMMAND].begin(); it != m_Map[CFG_COMMAND].end(); ++it) { if (boost::algorithm::starts_with(it->first, prefix)) ret[it->first] = it->second; } return ret; } void CConfigDB::SetValueString(EConfigNamespace ns, const CStr& name, const CStr& value) { - if (ns < 0 || ns >= CFG_LAST) - { - debug_warn(L"CConfigDB: Invalid ns value"); - return; - } + CHECK_NS(); CScopeLock s(&cfgdb_mutex); TConfigMap::iterator it = m_Map[ns].find(name); if (it == m_Map[ns].end()) it = m_Map[ns].insert(m_Map[ns].begin(), make_pair(name, CConfigValueSet(1))); - it->second[0].m_String = value; + it->second[0] = value; } void CConfigDB::SetConfigFile(EConfigNamespace ns, const VfsPath& path) { - if (ns < 0 || ns >= CFG_LAST) - { - debug_warn(L"CConfigDB: Invalid ns value"); - return; - } + CHECK_NS(); CScopeLock s(&cfgdb_mutex); - m_ConfigFile[ns]=path; + m_ConfigFile[ns] = path; } bool CConfigDB::Reload(EConfigNamespace ns) { - if (ns < 0 || ns >= CFG_LAST) - { - debug_warn(L"CConfigDB: Invalid ns value"); - return false; - } + CHECK_NS(false); CScopeLock s(&cfgdb_mutex); - // Set up CParser - CParser parser; - CParserLine parserLine; - parser.InputTaskType("Assignment", "_$ident_=<_[-$arg(_minus)]_$value_,>_[-$arg(_minus)]_$value[[;]$rest]"); - parser.InputTaskType("CommentOrBlank", "_[;[$rest]]"); - - // Open file with VFS - shared_ptr buffer; size_t buflen; + shared_ptr buffer; + size_t buflen; { // Handle missing files quietly if (g_VFS->GetFileInfo(m_ConfigFile[ns], NULL) < 0) { LOGMESSAGE(L"Cannot find config file \"%ls\" - ignoring", m_ConfigFile[ns].string().c_str()); return false; } - else + + LOGMESSAGE(L"Loading config file \"%ls\"", m_ConfigFile[ns].string().c_str()); + Status ret = g_VFS->LoadFile(m_ConfigFile[ns], buffer, buflen); + if (ret != INFO::OK) { - LOGMESSAGE(L"Loading config file \"%ls\"", m_ConfigFile[ns].string().c_str()); - Status ret = g_VFS->LoadFile(m_ConfigFile[ns], buffer, buflen); - if (ret != INFO::OK) - { - LOGERROR(L"CConfigDB::Reload(): vfs_load for \"%ls\" failed: return was %lld", m_ConfigFile[ns].string().c_str(), (long long)ret); - return false; - } + LOGERROR(L"CConfigDB::Reload(): vfs_load for \"%ls\" failed: return was %lld", m_ConfigFile[ns].string().c_str(), (long long)ret); + return false; } } TConfigMap newMap; + char *filebuf = (char*)buffer.get(); + char *filebufend = filebuf+buflen; - char *filebuf=(char *)buffer.get(); - char *filebufend=filebuf+buflen; - - // Read file line by line - char *next=filebuf-1; - do + bool quoted = false; + CStr name; + CStr value; + int line = 1; + std::vector values; + for (char* pos = filebuf; pos < filebufend; ++pos) { - char *pos=next+1; - next=(char *)memchr(pos, '\n', filebufend-pos); - if (!next) next=filebufend; - - char *lend=next; - if (lend > filebuf && *(lend-1) == '\r') lend--; - - // Send line to parser - bool parseOk=parserLine.ParseString(parser, std::string(pos, lend)); - // Get name and value from parser - std::string name; - std::string value; + switch (*pos) + { + case '\n': + case ';': + break; // We finished parsing this line + + case ' ': + case '\r': + continue; // ignore + + case '=': + // Parse parameters (comma separated, possibly quoted) + for (++pos; pos < filebufend && *pos != '\n' && *pos != ';'; ++pos) + { + switch (*pos) + { + case '"': + quoted = true; + // parse until not quoted anymore + for (++pos; pos < filebufend && *pos != '\n' && *pos != '"'; ++pos) + { + if (*pos == '\\' && ++pos == filebufend) + { + LOGERROR(L"Escape character at end of input (line %d in '%ls')", line, m_ConfigFile[ns].string().c_str()); + break; + } + + value.push_back(*pos); + } + if (pos < filebufend && *pos == '"') + quoted = false; + else + --pos; // We should terminate the outer loop too + break; + + case '\r': + case ' ': + break; // ignore + + case ',': + if (!value.empty()) + values.push_back(value); + value.clear(); + break; + + default: + value.push_back(*pos); + break; + } + } + if (quoted) // We ignore the invalid parameter + LOGERROR(L"Unmatched quote while parsing config file '%ls' on line %d", m_ConfigFile[ns].string().c_str(), line); + else if (!value.empty()) + values.push_back(value); + value.clear(); + quoted = false; + break; // We are either at the end of the line, or we still have a comment to parse + + default: + name.push_back(*pos); + continue; + } - if (parseOk && - parserLine.GetArgCount()>=2 && - parserLine.GetArgString(0, name) && - parserLine.GetArgString(1, value)) + // Consume the rest of the line + while (pos < filebufend && *pos != '\n') + ++pos; + // Store the setting + if (!name.empty() && !values.empty()) { - // Add name and value to the map - size_t argCount = parserLine.GetArgCount(); - - newMap[name].clear(); - - for( size_t t = 0; t < argCount; t++ ) + newMap[name] = values; + if (name == "lobby.password") + LOGMESSAGE(L"Loaded config string \"%hs\"", name.c_str()); + else { - if( !parserLine.GetArgString( (int)t + 1, value ) ) - continue; - CConfigValue argument; - argument.m_String = value; - newMap[name].push_back( argument ); - if (name == "lobby.password") - value = "*******"; - LOGMESSAGE(L"Loaded config string \"%hs\" = \"%hs\"", name.c_str(), value.c_str()); + std::string vals; + for (size_t i = 0; i < newMap[name].size() - 1; ++i) + vals += "\"" + EscapeString(newMap[name][i]) + "\", "; + vals += "\"" + EscapeString(newMap[name][values.size()-1]) + "\""; + LOGMESSAGE(L"Loaded config string \"%hs\" = %hs", name.c_str(), vals.c_str()); } } + else if (!name.empty()) + LOGERROR(L"Encountered config setting '%hs' without value while parsing '%ls' on line %d", name.c_str(), m_ConfigFile[ns].string().c_str(), line); + + name.clear(); + values.clear(); + ++line; } - while (next < filebufend); - + + if (!name.empty()) + LOGERROR(L"Config file does not have a new line after the last config setting '%hs'", name.c_str()); + m_Map[ns].swap(newMap); return true; } bool CConfigDB::WriteFile(EConfigNamespace ns) { - if (ns < 0 || ns >= CFG_LAST) - { - debug_warn(L"CConfigDB: Invalid ns value"); - return false; - } + CHECK_NS(false); CScopeLock s(&cfgdb_mutex); return WriteFile(ns, m_ConfigFile[ns]); } bool CConfigDB::WriteFile(EConfigNamespace ns, const VfsPath& path) { - if (ns < 0 || ns >= CFG_LAST) - { - debug_warn(L"CConfigDB: Invalid ns value"); - return false; - } + CHECK_NS(false); CScopeLock s(&cfgdb_mutex); shared_ptr buf; AllocateAligned(buf, 1*MiB, maxSectorSize); char* pos = (char*)buf.get(); - TConfigMap &map=m_Map[ns]; - for(TConfigMap::const_iterator it = map.begin(); it != map.end(); ++it) + TConfigMap &map = m_Map[ns]; + for (TConfigMap::const_iterator it = map.begin(); it != map.end(); ++it) { - pos += sprintf(pos, "%s = \"%s\"\n", it->first.c_str(), it->second[0].m_String.c_str()); + size_t i; + pos += sprintf(pos, "%s = ", it->first.c_str()); + for (i = 0; i < it->second.size() - 1; ++i) + pos += sprintf(pos, "\"%s\", ", EscapeString(it->second[i]).c_str()); + pos += sprintf(pos, "\"%s\"\n", EscapeString(it->second[i]).c_str()); } const size_t len = pos - (char*)buf.get(); Status ret = g_VFS->CreateFile(path, buf, len); - if(ret < 0) + if (ret < 0) { LOGERROR(L"CConfigDB::WriteFile(): CreateFile \"%ls\" failed (error: %d)", path.string().c_str(), (int)ret); return false; } return true; } + +#undef CHECK_NS Index: ps/trunk/source/ps/ConfigDB.h =================================================================== --- ps/trunk/source/ps/ConfigDB.h (revision 15979) +++ ps/trunk/source/ps/ConfigDB.h (revision 15980) @@ -1,150 +1,148 @@ -/* Copyright (C) 2013 Wildfire Games. +/* Copyright (C) 2014 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ /* CConfigDB - Load, access and store configuration variables TDD : http://www.wildfiregames.com/forum/index.php?showtopic=1125 OVERVIEW: JavaScript: Check this documentation: http://trac.wildfiregames.com/wiki/Exposed_ConfigDB_Functions */ #ifndef INCLUDED_CONFIGDB #define INCLUDED_CONFIGDB -#include "Parser.h" #include "CStr.h" #include "Singleton.h" #include "lib/file/vfs/vfs_path.h" // Namespace priorities: User supersedes mod supersedes system. // Command-line arguments override everything. enum EConfigNamespace { CFG_DEFAULT, CFG_SYSTEM, CFG_MOD, CFG_USER, CFG_COMMAND, CFG_LAST }; -typedef CParserValue CConfigValue; -typedef std::vector CConfigValueSet; +typedef std::vector CConfigValueSet; #define g_ConfigDB CConfigDB::GetSingleton() class CConfigDB: public Singleton { - static std::map m_Map[]; + static std::map m_Map[]; static VfsPath m_ConfigFile[]; public: CConfigDB(); /** * Attempt to retrieve the value of a config variable with the given name; * will search CFG_COMMAND first, and then all namespaces from the specified * namespace down. */ - void GetValueBool(EConfigNamespace ns, const CStr& name, bool& value); - ///@copydoc CConfigDB::GetValueBool - void GetValueInt(EConfigNamespace ns, const CStr& name, int& value); - ///@copydoc CConfigDB::GetValueBool - void GetValueFloat(EConfigNamespace ns, const CStr& name, float& value); - ///@copydoc CConfigDB::GetValueBool - void GetValueDouble(EConfigNamespace ns, const CStr& name, double& value); - ///@copydoc CConfigDB::GetValueBool - void GetValueString(EConfigNamespace ns, const CStr& name, std::string& value); + void GetValue(EConfigNamespace ns, const CStr& name, bool& value); + ///@copydoc CConfigDB::GetValue + void GetValue(EConfigNamespace ns, const CStr& name, int& value); + ///@copydoc CConfigDB::GetValue + void GetValue(EConfigNamespace ns, const CStr& name, float& value); + ///@copydoc CConfigDB::GetValue + void GetValue(EConfigNamespace ns, const CStr& name, double& value); + ///@copydoc CConfigDB::GetValue + void GetValue(EConfigNamespace ns, const CStr& name, std::string& value); /** * Attempt to retrieve a vector of values corresponding to the given setting; * will search CFG_COMMAND first, and then all namespaces from the specified * namespace down. */ void GetValues(EConfigNamespace ns, const CStr& name, CConfigValueSet& values); /** * Returns the namespace that the value returned by GetValues was defined in, * or CFG_LAST if it wasn't defined at all. */ EConfigNamespace GetValueNamespace(EConfigNamespace ns, const CStr& name); /** * Retrieve a map of values corresponding to settings whose names begin * with the given prefix; * will search all namespaces from default up to the specified namespace. */ std::map GetValuesWithPrefix(EConfigNamespace ns, const CStr& prefix); /** * Save a config value in the specified namespace. If the config variable * existed the value is replaced. */ void SetValueString(EConfigNamespace ns, const CStr& name, const CStr& value); /** * Set the path to the config file used to populate the specified namespace * Note that this function does not actually load the config file. Use * the Reload() method if you want to read the config file at the same time. * * 'path': The path to the config file. */ void SetConfigFile(EConfigNamespace ns, const VfsPath& path); /** * Reload the config file associated with the specified config namespace * (the last config file path set with SetConfigFile) * * Returns: * true: if the reload succeeded, * false: if the reload failed */ bool Reload(EConfigNamespace); /** * Write the current state of the specified config namespace to the file * specified by 'path' * * Returns: * true: if the config namespace was successfully written to the file * false: if an error occurred */ bool WriteFile(EConfigNamespace ns, const VfsPath& path); /** * Write the current state of the specified config namespace to the file * it was originally loaded from. * * Returns: * true: if the config namespace was successfully written to the file * false: if an error occurred */ bool WriteFile(EConfigNamespace ns); }; // stores the value of the given key into . this quasi-template // convenience wrapper on top of CConfigValue::Get* simplifies user code and // avoids "assignment within condition expression" warnings. #define CFG_GET_VAL(name, type, destination)\ - g_ConfigDB.GetValue##type(CFG_USER, name, destination) + g_ConfigDB.GetValue(CFG_USER, name, destination) #endif Index: ps/trunk/source/ps/Hotkey.cpp =================================================================== --- ps/trunk/source/ps/Hotkey.cpp (revision 15979) +++ ps/trunk/source/ps/Hotkey.cpp (revision 15980) @@ -1,400 +1,367 @@ /* Copyright (C) 2014 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ #include "precompiled.h" #include "Hotkey.h" +#include + #include "lib/input.h" #include "ConfigDB.h" #include "CLogger.h" #include "CConsole.h" #include "CStr.h" #include "ps/Globals.h" #include "KeyName.h" static bool unified[UNIFIED_LAST - UNIFIED_SHIFT]; #if SDL_VERSION_ATLEAST(2, 0, 0) #define SDLKEY SDL_Keycode #else #define SDLKEY SDLKey #endif struct SKey { SDLKEY code; // keycode or MOUSE_ or UNIFIED_ value bool negated; // whether the key must be pressed (false) or unpressed (true) }; // Hotkey data associated with an externally-specified 'primary' keycode struct SHotkeyMapping { CStr name; // name of the hotkey bool negated; // whether the primary key must be pressed (false) or unpressed (true) std::vector requires; // list of non-primary keys that must also be active }; typedef std::vector KeyMapping; // A mapping of keycodes onto the hotkeys that are associated with that key. // (A hotkey triggered by a combination of multiple keys will be in this map // multiple times.) static std::map g_HotkeyMap; // The current pressed status of hotkeys std::map g_HotkeyStatus; // Look up each key binding in the config file and set the mappings for // all key combinations that trigger it. static void LoadConfigBindings() { - std::map bindings = g_ConfigDB.GetValuesWithPrefix( CFG_COMMAND, "hotkey." ); - - CParser multikeyParser; - multikeyParser.InputTaskType( "multikey", "<[~$arg(_negate)]$value_+_>_[~$arg(_negate)]$value" ); - - for( std::map::iterator bindingsIt = bindings.begin(); bindingsIt != bindings.end(); ++bindingsIt ) + std::map bindings = g_ConfigDB.GetValuesWithPrefix(CFG_COMMAND, "hotkey."); + for (std::map::iterator bindingsIt = bindings.begin(); bindingsIt != bindings.end(); ++bindingsIt) { std::string hotkeyName = bindingsIt->first.substr(7); // strip the "hotkey." prefix - - for( CConfigValueSet::iterator it = bindingsIt->second.begin(); it != bindingsIt->second.end(); ++it ) + for (CConfigValueSet::iterator it = bindingsIt->second.begin(); it != bindingsIt->second.end(); ++it) { - std::string hotkey; - if( it->GetString( hotkey ) ) - { - std::vector keyCombination; + const CStr& hotkey = *it; + std::vector keyCombination; - CParserLine multikeyIdentifier; - multikeyIdentifier.ParseString( multikeyParser, hotkey ); - - // Iterate through multiple-key bindings (e.g. Ctrl+I) - - bool negateNext = false; - - for( size_t t = 0; t < multikeyIdentifier.GetArgCount(); t++ ) + // Iterate through multiple-key bindings (e.g. Ctrl+I) + boost::char_separator sep("+"); + typedef boost::tokenizer > tokenizer; + tokenizer tok(hotkey, sep); + for (tokenizer::iterator it = tok.begin(); it != tok.end(); ++it) + { + // Attempt decode as key name + int mapping = FindKeyCode(*it); + if (!mapping) { - - if( multikeyIdentifier.GetArgString( (int)t, hotkey ) ) - { - if( hotkey == "_negate" ) - { - negateNext = true; - continue; - } - - // Attempt decode as key name - int mapping = FindKeyCode( hotkey ); - - // Attempt to decode as a negation of a keyname - // Yes, it's going a bit far, perhaps. - // Too powerful for most uses, probably. - // However, it got some hardcoding out of the engine. - // Thus it makes me happy. - - if( !mapping ) - { - LOGWARNING(L"Hotkey mapping used invalid key '%hs'", hotkey.c_str() ); - continue; - } - - SKey key = { (SDLKEY)mapping, negateNext }; - keyCombination.push_back(key); - - negateNext = false; - - } + LOGWARNING(L"Hotkey mapping used invalid key '%hs'", hotkey.c_str()); + continue; } - std::vector::iterator itKey, itKey2; + SKey key = { (SDLKEY)mapping, false }; + keyCombination.push_back(key); + } - for( itKey = keyCombination.begin(); itKey != keyCombination.end(); ++itKey ) - { - SHotkeyMapping bindCode; + std::vector::iterator itKey, itKey2; + for (itKey = keyCombination.begin(); itKey != keyCombination.end(); ++itKey) + { + SHotkeyMapping bindCode; - bindCode.name = hotkeyName; - bindCode.negated = itKey->negated; + bindCode.name = hotkeyName; + bindCode.negated = itKey->negated; - for( itKey2 = keyCombination.begin(); itKey2 != keyCombination.end(); ++itKey2 ) - { - // Push any auxiliary keys. - if( itKey != itKey2 ) - bindCode.requires.push_back( *itKey2 ); - } + for (itKey2 = keyCombination.begin(); itKey2 != keyCombination.end(); ++itKey2) + if (itKey != itKey2) // Push any auxiliary keys + bindCode.requires.push_back(*itKey2); - g_HotkeyMap[itKey->code].push_back( bindCode ); - } + g_HotkeyMap[itKey->code].push_back(bindCode); } } } } void LoadHotkeys() { InitKeyNameMap(); LoadConfigBindings(); // Set up the state of the hotkeys given no key is down. // i.e. find those hotkeys triggered by all negations. for( std::map::iterator mapIt = g_HotkeyMap.begin(); mapIt != g_HotkeyMap.end(); ++mapIt ) { KeyMapping& hotkeyMap = mapIt->second; for( std::vector::iterator it = hotkeyMap.begin(); it != hotkeyMap.end(); ++it ) { if( !it->negated ) continue; bool allNegated = true; for( std::vector::iterator j = it->requires.begin(); j != it->requires.end(); ++j ) if( !j->negated ) allNegated = false; if( allNegated ) g_HotkeyStatus[it->name] = true; } } } void UnloadHotkeys() { g_HotkeyMap.clear(); g_HotkeyStatus.clear(); } bool isNegated(const SKey& key) { // Normal keycodes are below EXTRA_KEYS_BASE if ((int)key.code < EXTRA_KEYS_BASE && g_keys[key.code] == key.negated) return false; // Mouse 'keycodes' are after the modifier keys else if ((int)key.code > UNIFIED_LAST && g_mouse_buttons[key.code - UNIFIED_LAST] == key.negated) return false; // Modifier keycodes are between the normal keys and the mouse 'keys' else if ((int)key.code < UNIFIED_LAST && (int)key.code > CUSTOM_SDL_KEYCODE && unified[key.code - UNIFIED_SHIFT] == key.negated) return false; else return true; } InReaction HotkeyInputHandler( const SDL_Event_* ev ) { int keycode = 0; switch( ev->ev.type ) { case SDL_KEYDOWN: case SDL_KEYUP: keycode = (int)ev->ev.key.keysym.sym; break; case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: #if SDL_VERSION_ATLEAST(2, 0, 0) // Mousewheel events are no longer buttons, but we want to maintain the order // expected by g_mouse_buttons for compatibility if (ev->ev.button.button >= SDL_BUTTON_X1) keycode = MOUSE_BASE + (int)ev->ev.button.button + 2; else #endif keycode = MOUSE_BASE + (int)ev->ev.button.button; break; #if SDL_VERSION_ATLEAST(2, 0, 0) case SDL_MOUSEWHEEL: if (ev->ev.wheel.y > 0) { keycode = MOUSE_WHEELUP; break; } else if (ev->ev.wheel.y < 0) { keycode = MOUSE_WHEELDOWN; break; } else if (ev->ev.wheel.x > 0) { keycode = MOUSE_X2; break; } else if (ev->ev.wheel.x < 0) { keycode = MOUSE_X1; break; } return IN_PASS; #endif case SDL_HOTKEYDOWN: g_HotkeyStatus[static_cast(ev->ev.user.data1)] = true; return IN_PASS; case SDL_HOTKEYUP: g_HotkeyStatus[static_cast(ev->ev.user.data1)] = false; return IN_PASS; default: return IN_PASS; } #if !SDL_VERSION_ATLEAST(2, 0, 0) // Rather ugly hack to make the '"' key work better on a MacBook Pro on Windows so it doesn't // always close the console. (Maybe this would be better handled in wsdl or something?) if (keycode == SDLK_BACKQUOTE && (ev->ev.key.keysym.unicode == '\'' || ev->ev.key.keysym.unicode == '"')) keycode = ev->ev.key.keysym.unicode; #endif // Somewhat hackish: // Create phantom 'unified-modifier' events when left- or right- modifier keys are pressed // Just send them to this handler; don't let the imaginary event codes leak back to real SDL. SDL_Event_ phantom; phantom.ev.type = ( ( ev->ev.type == SDL_KEYDOWN ) || ( ev->ev.type == SDL_MOUSEBUTTONDOWN ) ) ? SDL_KEYDOWN : SDL_KEYUP; if( ( keycode == SDLK_LSHIFT ) || ( keycode == SDLK_RSHIFT ) ) { phantom.ev.key.keysym.sym = (SDLKEY)UNIFIED_SHIFT; unified[0] = ( phantom.ev.type == SDL_KEYDOWN ); HotkeyInputHandler( &phantom ); } else if( ( keycode == SDLK_LCTRL ) || ( keycode == SDLK_RCTRL ) ) { phantom.ev.key.keysym.sym = (SDLKEY)UNIFIED_CTRL; unified[1] = ( phantom.ev.type == SDL_KEYDOWN ); HotkeyInputHandler( &phantom ); } else if( ( keycode == SDLK_LALT ) || ( keycode == SDLK_RALT ) ) { phantom.ev.key.keysym.sym = (SDLKEY)UNIFIED_ALT; unified[2] = ( phantom.ev.type == SDL_KEYDOWN ); HotkeyInputHandler( &phantom ); } #if SDL_VERSION_ATLEAST(2, 0, 0) else if( ( keycode == SDLK_LGUI ) || ( keycode == SDLK_RGUI ) ) #else // SDL 1.2 else if( ( keycode == SDLK_LSUPER ) || ( keycode == SDLK_RSUPER ) || ( keycode == SDLK_LMETA ) || ( keycode == SDLK_RMETA) ) #endif { phantom.ev.key.keysym.sym = (SDLKEY)UNIFIED_SUPER; unified[3] = ( phantom.ev.type == SDL_KEYDOWN ); HotkeyInputHandler( &phantom ); } // Check whether we have any hotkeys registered for this particular keycode if( g_HotkeyMap.find(keycode) == g_HotkeyMap.end() ) return( IN_PASS ); // Inhibit the dispatch of hotkey events caused by real keys (not fake mouse button // events) while the console is up. bool consoleCapture = false; if( g_Console->IsActive() && keycode < CUSTOM_SDL_KEYCODE ) consoleCapture = true; // Here's an interesting bit: // If you have an event bound to, say, 'F', and another to, say, 'Ctrl+F', pressing // 'F' while control is down would normally fire off both. // To avoid this, set the modifier keys for /all/ events this key would trigger // (Ctrl, for example, is both group-save and bookmark-save) // but only send a HotkeyDown event for the event with bindings most precisely // matching the conditions (i.e. the event with the highest number of auxiliary // keys, providing they're all down) #if SDL_VERSION_ATLEAST(2, 0, 0) bool typeKeyDown = ( ev->ev.type == SDL_KEYDOWN ) || ( ev->ev.type == SDL_MOUSEBUTTONDOWN ) || (ev->ev.type == SDL_MOUSEWHEEL); #else bool typeKeyDown = ( ev->ev.type == SDL_KEYDOWN ) || ( ev->ev.type == SDL_MOUSEBUTTONDOWN ); #endif // -- KEYDOWN SECTION -- std::vector closestMapNames; size_t closestMapMatch = 0; for (std::vector::iterator it = g_HotkeyMap[keycode].begin(); it < g_HotkeyMap[keycode].end(); ++it) { // If a key has been pressed, and this event triggers on its release, skip it. // Similarly, if the key's been released and the event triggers on a keypress, skip it. if (it->negated == typeKeyDown) continue; // Check for no unpermitted keys bool accept = true; for (std::vector::iterator itKey = it->requires.begin(); itKey != it->requires.end() && accept; ++itKey) accept = isNegated(*itKey); if (accept && !(consoleCapture && it->name != "console.toggle")) { // Check if this is an equally precise or more precise match if (it->requires.size() + 1 >= closestMapMatch) { // Check if more precise if (it->requires.size() + 1 > closestMapMatch) { // Throw away the old less-precise matches closestMapNames.clear(); closestMapMatch = it->requires.size() + 1; } closestMapNames.push_back(it->name.c_str()); } } } for (size_t i = 0; i < closestMapNames.size(); ++i) { SDL_Event_ hotkeyNotification; hotkeyNotification.ev.type = SDL_HOTKEYDOWN; hotkeyNotification.ev.user.data1 = const_cast(closestMapNames[i]); in_push_priority_event(&hotkeyNotification); } // -- KEYUP SECTION -- for (std::vector::iterator it = g_HotkeyMap[keycode].begin(); it < g_HotkeyMap[keycode].end(); ++it) { // If it's a keydown event, won't cause HotKeyUps in anything that doesn't // use this key negated => skip them // If it's a keyup event, won't cause HotKeyUps in anything that does use // this key negated => skip them too. if (it->negated != typeKeyDown) continue; // Check for no unpermitted keys bool accept = true; for (std::vector::iterator itKey = it->requires.begin(); itKey != it->requires.end() && accept; ++itKey) accept = isNegated(*itKey); if (accept) { SDL_Event_ hotkeyNotification; hotkeyNotification.ev.type = SDL_HOTKEYUP; hotkeyNotification.ev.user.data1 = const_cast(it->name.c_str()); in_push_priority_event(&hotkeyNotification); } } return IN_PASS; } bool HotkeyIsPressed(const CStr& keyname) { return g_HotkeyStatus[keyname]; } Index: ps/trunk/source/ps/scripting/JSInterface_ConfigDB.cpp =================================================================== --- ps/trunk/source/ps/scripting/JSInterface_ConfigDB.cpp (revision 15979) +++ ps/trunk/source/ps/scripting/JSInterface_ConfigDB.cpp (revision 15980) @@ -1,104 +1,104 @@ -/* Copyright (C) 2013 Wildfire Games. +/* Copyright (C) 2014 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ #include "precompiled.h" #include "JSInterface_ConfigDB.h" #include "ps/ConfigDB.h" #include "ps/CLogger.h" #include "scriptinterface/ScriptInterface.h" bool JSI_ConfigDB::GetConfigNamespace(std::wstring cfgNsString, EConfigNamespace& cfgNs) { if (cfgNsString == L"default") cfgNs = CFG_DEFAULT; else if (cfgNsString == L"system") cfgNs = CFG_SYSTEM; else if (cfgNsString == L"user") cfgNs = CFG_USER; else if (cfgNsString == L"mod") cfgNs = CFG_MOD; else { LOGERROR(L"Invalid namespace name passed to the ConfigDB!"); cfgNs = CFG_DEFAULT; return false; } return true; } std::string JSI_ConfigDB::GetValue(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::wstring cfgNsString, std::string name) { EConfigNamespace cfgNs; if (!GetConfigNamespace(cfgNsString, cfgNs)) return std::string(); std::string value; - g_ConfigDB.GetValueString(cfgNs, name, value); + g_ConfigDB.GetValue(cfgNs, name, value); return value; } bool JSI_ConfigDB::CreateValue(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::wstring cfgNsString, std::string name, std::string value) { EConfigNamespace cfgNs; if (!GetConfigNamespace(cfgNsString, cfgNs)) return false; g_ConfigDB.SetValueString(cfgNs, name, value); return true; } bool JSI_ConfigDB::WriteFile(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::wstring cfgNsString, Path path) { EConfigNamespace cfgNs; if (!GetConfigNamespace(cfgNsString, cfgNs)) return false; bool ret = g_ConfigDB.WriteFile(cfgNs, path); return ret; } bool JSI_ConfigDB::Reload(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::wstring cfgNsString) { EConfigNamespace cfgNs; if (!GetConfigNamespace(cfgNsString, cfgNs)) return false; bool ret = g_ConfigDB.Reload(cfgNs); return ret; } bool JSI_ConfigDB::SetFile(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::wstring cfgNsString, Path path) { EConfigNamespace cfgNs; if (!GetConfigNamespace(cfgNsString, cfgNs)) return false; g_ConfigDB.SetConfigFile(cfgNs, path); return true; } void JSI_ConfigDB::RegisterScriptFunctions(ScriptInterface& scriptInterface) { scriptInterface.RegisterFunction("ConfigDB_GetValue"); scriptInterface.RegisterFunction("ConfigDB_CreateValue"); scriptInterface.RegisterFunction("ConfigDB_WriteFile"); scriptInterface.RegisterFunction("ConfigDB_SetFile"); scriptInterface.RegisterFunction("ConfigDB_Reload"); }