diff options
Diffstat (limited to 'indra/llwindow')
-rw-r--r-- | indra/llwindow/llgamecontrol.cpp | 1638 | ||||
-rw-r--r-- | indra/llwindow/llgamecontrol.h | 156 | ||||
-rw-r--r-- | indra/llwindow/llgamecontroltranslator.cpp | 231 | ||||
-rw-r--r-- | indra/llwindow/llgamecontroltranslator.h | 9 | ||||
-rw-r--r-- | indra/llwindow/llkeyboard.cpp | 18 | ||||
-rw-r--r-- | indra/llwindow/llkeyboard.h | 6 | ||||
-rw-r--r-- | indra/llwindow/llkeyboardmacosx.cpp | 12 | ||||
-rw-r--r-- | indra/llwindow/llkeyboardmacosx.h | 2 | ||||
-rw-r--r-- | indra/llwindow/llkeyboardsdl.cpp | 8 | ||||
-rw-r--r-- | indra/llwindow/llkeyboardsdl.h | 2 | ||||
-rw-r--r-- | indra/llwindow/llkeyboardwin32.cpp | 9 | ||||
-rw-r--r-- | indra/llwindow/llkeyboardwin32.h | 2 | ||||
-rw-r--r-- | indra/llwindow/llwindow.cpp | 2 |
13 files changed, 1587 insertions, 508 deletions
diff --git a/indra/llwindow/llgamecontrol.cpp b/indra/llwindow/llgamecontrol.cpp index a1ab2dd52d..23849aca66 100644 --- a/indra/llwindow/llgamecontrol.cpp +++ b/indra/llwindow/llgamecontrol.cpp @@ -37,80 +37,202 @@ #include "indra_constants.h" #include "llfile.h" #include "llgamecontroltranslator.h" +#include "llsd.h" -constexpr size_t NUM_AXES = 6; - -// util for dumping SDL_GameController info -std::ostream& operator<<(std::ostream& out, SDL_GameController* c) +namespace std { - if (!c) + string to_string(const char* text) + { + return text ? string(text) : LLStringUtil::null; + } + + string to_string(const SDL_JoystickGUID& guid) + { + char buffer[33] = { 0 }; + SDL_JoystickGetGUIDString(guid, buffer, sizeof(guid)); + return buffer; + } + + string to_string(SDL_JoystickType type) + { + switch (type) + { + case SDL_JOYSTICK_TYPE_GAMECONTROLLER: + return "GAMECONTROLLER"; + case SDL_JOYSTICK_TYPE_WHEEL: + return "WHEEL"; + case SDL_JOYSTICK_TYPE_ARCADE_STICK: + return "ARCADE_STICK"; + case SDL_JOYSTICK_TYPE_FLIGHT_STICK: + return "FLIGHT_STICK"; + case SDL_JOYSTICK_TYPE_DANCE_PAD: + return "DANCE_PAD"; + case SDL_JOYSTICK_TYPE_GUITAR: + return "GUITAR"; + case SDL_JOYSTICK_TYPE_DRUM_KIT: + return "DRUM_KIT"; + case SDL_JOYSTICK_TYPE_ARCADE_PAD: + return "ARCADE_PAD"; + case SDL_JOYSTICK_TYPE_THROTTLE: + return "THROTTLE"; + default:; + } + return "UNKNOWN"; + } + + string to_string(SDL_GameControllerType type) { - return out << "nullptr"; + switch (type) + { + case SDL_CONTROLLER_TYPE_XBOX360: + return "XBOX360"; + case SDL_CONTROLLER_TYPE_XBOXONE: + return "XBOXONE"; + case SDL_CONTROLLER_TYPE_PS3: + return "PS3"; + case SDL_CONTROLLER_TYPE_PS4: + return "PS4"; + case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO: + return "NINTENDO_SWITCH_PRO"; + case SDL_CONTROLLER_TYPE_VIRTUAL: + return "VIRTUAL"; + case SDL_CONTROLLER_TYPE_PS5: + return "PS5"; + case SDL_CONTROLLER_TYPE_AMAZON_LUNA: + return "AMAZON_LUNA"; + case SDL_CONTROLLER_TYPE_GOOGLE_STADIA: + return "GOOGLE_STADIA"; + case SDL_CONTROLLER_TYPE_NVIDIA_SHIELD: + return "NVIDIA_SHIELD"; + case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_LEFT: + return "NINTENDO_SWITCH_JOYCON_LEFT"; + case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT: + return "NINTENDO_SWITCH_JOYCON_RIGHT"; + case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_PAIR: + return "NINTENDO_SWITCH_JOYCON_PAIR"; + default:; + } + return "UNKNOWN"; } - out << "{"; - out << " name='" << SDL_GameControllerName(c) << "'"; - out << " type='" << SDL_GameControllerGetType(c) << "'"; - out << " vendor='" << SDL_GameControllerGetVendor(c) << "'"; - out << " product='" << SDL_GameControllerGetProduct(c) << "'"; - out << " version='" << SDL_GameControllerGetProductVersion(c) << "'"; - //CRASH! out << " serial='" << SDL_GameControllerGetSerial(c) << "'"; - out << " }"; - return out; } -// util for dumping SDL_Joystick info -std::ostream& operator<<(std::ostream& out, SDL_Joystick* j) +// Util for dumping SDL_JoystickGUID info +std::ostream& operator<<(std::ostream& out, SDL_JoystickGUID& guid) +{ + return out << std::to_string(guid); +} + +// Util for dumping SDL_JoystickType type name +std::ostream& operator<<(std::ostream& out, SDL_JoystickType type) +{ + return out << std::to_string(type); +} + +// Util for dumping SDL_GameControllerType type name +std::ostream& operator<<(std::ostream& out, SDL_GameControllerType type) +{ + return out << std::to_string(type); +} + +namespace std { - if (!j) + string to_string(SDL_Joystick* joystick) { - return out << "nullptr"; + if (!joystick) + { + return "nullptr"; + } + + std::stringstream ss; + + ss << "{id:" << SDL_JoystickInstanceID(joystick); + SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick); + ss << ",guid:'" << guid << "'"; + ss << ",type:'" << SDL_JoystickGetType(joystick) << "'"; + ss << ",name:'" << std::to_string(SDL_JoystickName(joystick)) << "'"; + ss << ",vendor:" << SDL_JoystickGetVendor(joystick); + ss << ",product:" << SDL_JoystickGetProduct(joystick); + if (U16 version = SDL_JoystickGetProductVersion(joystick)) + { + ss << ",version:" << version; + } + if (U16 firmware = SDL_JoystickGetFirmwareVersion(joystick)) + { + ss << ",firmware:" << firmware; + } + if (const char* serial = SDL_JoystickGetSerial(joystick)) + { + ss << ",serial:'" << serial << "'"; + } + ss << ",num_axes:" << SDL_JoystickNumAxes(joystick); + ss << ",num_balls:" << SDL_JoystickNumBalls(joystick); + ss << ",num_hats:" << SDL_JoystickNumHats(joystick); + ss << ",num_buttons:" << SDL_JoystickNumHats(joystick); + ss << "}"; + + return ss.str(); } - out << "{"; - out << " p=0x" << (void*)(j); - out << " name='" << SDL_JoystickName(j) << "'"; - out << " type='" << SDL_JoystickGetType(j) << "'"; - out << " instance='" << SDL_JoystickInstanceID(j) << "'"; - out << " product='" << SDL_JoystickGetProduct(j) << "'"; - out << " version='" << SDL_JoystickGetProductVersion(j) << "'"; - out << " num_axes=" << SDL_JoystickNumAxes(j); - out << " num_balls=" << SDL_JoystickNumBalls(j); - out << " num_hats=" << SDL_JoystickNumHats(j); - out << " num_buttons=" << SDL_JoystickNumHats(j); - out << " }"; - return out; + + string to_string(SDL_GameController* controller) + { + if (!controller) + { + return "nullptr"; + } + + stringstream ss; + + ss << "{type:'" << SDL_GameControllerGetType(controller) << "'"; + ss << ",name:'" << std::to_string(SDL_GameControllerName(controller)) << "'"; + ss << ",vendor:" << SDL_GameControllerGetVendor(controller); + ss << ",product:" << SDL_GameControllerGetProduct(controller); + if (U16 version = SDL_GameControllerGetProductVersion(controller)) + { + ss << ",version:" << version; + } + if (U16 firmware = SDL_GameControllerGetFirmwareVersion(controller)) + { + ss << ",firmware:" << firmware; + } + if (const char* serial = SDL_GameControllerGetSerial(controller)) + { + ss << ",serial:'" << serial << "'"; + } + ss << "}"; + + return ss.str(); + } +} + +// Util for dumping SDL_Joystick info +std::ostream& operator<<(std::ostream& out, SDL_Joystick* joystick) +{ + return out << std::to_string(joystick); +} + +// Util for dumping SDL_GameController info +std::ostream& operator<<(std::ostream& out, SDL_GameController* controller) +{ + return out << std::to_string(controller); } std::string LLGameControl::InputChannel::getLocalName() const { // HACK: we hard-code English channel names, but // they should be loaded from localized XML config files. - std::string name = " "; - if (mType == LLGameControl::InputChannel::TYPE_AXIS) + + if ((mType == LLGameControl::InputChannel::TYPE_AXIS) && (mIndex < NUM_AXES)) { - if (mIndex < (U8)(NUM_AXES)) - { - name = "AXIS_"; - name.append(std::to_string((S32)(mIndex))); - if (mSign < 0) - { - name.append("-"); - } - else if (mSign > 0) - { - name.append("+"); - } - } + return "AXIS_" + std::to_string((U32)mIndex) + + (mSign < 0 ? "-" : mSign > 0 ? "+" : ""); } - else if (mType == LLGameControl::InputChannel::TYPE_BUTTON) + + if ((mType == LLGameControl::InputChannel::TYPE_BUTTON) && (mIndex < NUM_BUTTONS)) { - constexpr U8 NUM_BUTTONS = 32; - if (mIndex < NUM_BUTTONS) - { - name = "BUTTON_"; - name.append(std::to_string((S32)(mIndex))); - } + return "BUTTON_" + std::to_string((U32)mIndex); } - return name; + + return "NONE"; } std::string LLGameControl::InputChannel::getRemoteName() const @@ -121,7 +243,7 @@ std::string LLGameControl::InputChannel::getRemoteName() const // GAME_CONTROL_AXIS_LEFTX, GAME_CONTROL_BUTTON_A, etc if (mType == LLGameControl::InputChannel::TYPE_AXIS) { - switch(mIndex) + switch (mIndex) { case 0: name = "GAME_CONTROL_AXIS_LEFTX"; @@ -227,7 +349,15 @@ public: using ActionToChannelMap = std::map< std::string, LLGameControl::InputChannel >; LLGameControllerManager(); - void addController(SDL_JoystickID id, SDL_GameController* controller); + static void getDefaultMappings(std::vector<std::pair<std::string, LLGameControl::InputChannel>>& agent_channels, + std::vector<LLGameControl::InputChannel>& flycam_channels); + void getDefaultMappings(std::vector<std::pair<std::string, LLGameControl::InputChannel>>& mappings); + void initializeMappingsByDefault(); + void resetDeviceOptionsToDefaults(); + void loadDeviceOptionsFromSettings(); + void saveDeviceOptionsToSettings() const; + + void addController(SDL_JoystickID id, const std::string& guid, const std::string& name); void removeController(SDL_JoystickID id); void onAxis(SDL_JoystickID id, U8 axis, S16 value); @@ -236,39 +366,56 @@ public: void clearAllStates(); void accumulateInternalState(); - void computeFinalState(LLGameControl::State& state); + void computeFinalState(); - LLGameControl::InputChannel getChannelByActionName(const std::string& action_name) const; - LLGameControl::InputChannel getFlycamChannelByActionName(const std::string& action_name) const; + LLGameControl::ActionNameType getActionNameType(const std::string& action) const; + LLGameControl::InputChannel getChannelByAction(const std::string& action) const; + LLGameControl::InputChannel getFlycamChannelByAction(const std::string& action) const; bool updateActionMap(const std::string& name, LLGameControl::InputChannel channel); U32 computeInternalActionFlags(); void getFlycamInputs(std::vector<F32>& inputs_out); void setExternalInput(U32 action_flags, U32 buttons); + U32 getMappedFlags() const { return mActionTranslator.getMappedFlags(); } + void clear(); + std::string getAnalogMappings() const; + std::string getBinaryMappings() const; + std::string getFlycamMappings() const; + + void setAnalogMappings(const std::string& mappings); + void setBinaryMappings(const std::string& mappings); + void setFlycamMappings(const std::string& mappings); + private: - bool updateFlycamMap(const std::string& action, LLGameControl::InputChannel channel); + void updateFlycamMap(const std::string& action, LLGameControl::InputChannel channel); - std::list<LLGameControl::State> mStates; // one state per device - using state_it = std::list<LLGameControl::State>::iterator; - state_it findState(SDL_JoystickID id) + std::list<LLGameControl::Device> mDevices; // all connected devices + using device_it = std::list<LLGameControl::Device>::iterator; + device_it findDevice(SDL_JoystickID id) { - return std::find_if(mStates.begin(), mStates.end(), - [id](LLGameControl::State& state) + return std::find_if(mDevices.begin(), mDevices.end(), + [id](LLGameControl::Device& device) { - return state.getJoystickID() == id; + return device.getJoystickID() == id; }); } LLGameControl::State mExternalState; LLGameControlTranslator mActionTranslator; + std::map<std::string, LLGameControl::ActionNameType> mActions; + std::vector <std::string> mAnalogActions; + std::vector <std::string> mBinaryActions; + std::vector <std::string> mFlycamActions; std::vector<LLGameControl::InputChannel> mFlycamChannels; std::vector<S32> mAxesAccumulator; U32 mButtonAccumulator { 0 }; U32 mLastActiveFlags { 0 }; U32 mLastFlycamActionFlags { 0 }; + + friend class LLGameControl; }; // local globals @@ -301,8 +448,52 @@ namespace bool g_translateAgentActions = false; LLGameControl::AgentControlMode g_agentControlMode = LLGameControl::CONTROL_MODE_AVATAR; - constexpr U8 MAX_AXIS = 5; - constexpr U8 MAX_BUTTON = 31; + std::map<std::string, std::string> g_deviceOptions; + + std::function<bool(const std::string&)> s_loadBoolean; + std::function<void(const std::string&, bool)> s_saveBoolean; + std::function<std::string(const std::string&)> s_loadString; + std::function<void(const std::string&, const std::string&)> s_saveString; + std::function<LLSD(const std::string&)> s_loadObject; + std::function<void(const std::string&, LLSD&)> s_saveObject; + + std::string SETTING_SENDTOSERVER("GameControlToServer"); + std::string SETTING_CONTROLAGENT("GameControlToAgent"); + std::string SETTING_TRANSLATEACTIONS("AgentToGameControl"); + std::string SETTING_AGENTCONTROLMODE("AgentControlMode"); + std::string SETTING_ANALOGMAPPINGS("AnalogChannelMappings"); + std::string SETTING_BINARYMAPPINGS("BinaryChannelMappings"); + std::string SETTING_FLYCAMMAPPINGS("FlycamChannelMappings"); + std::string SETTING_KNOWNCONTROLLERS("KnownGameControllers"); + + std::string ENUM_AGENTCONTROLMODE_FLYCAM("flycam"); + std::string ENUM_AGENTCONTROLMODE_NONE("none"); + + LLGameControl::AgentControlMode convertStringToAgentControlMode(const std::string& mode) + { + if (mode == ENUM_AGENTCONTROLMODE_NONE) + return LLGameControl::CONTROL_MODE_NONE; + if (mode == ENUM_AGENTCONTROLMODE_FLYCAM) + return LLGameControl::CONTROL_MODE_FLYCAM; + // All values except NONE and FLYCAM are treated as default (AVATAR) + return LLGameControl::CONTROL_MODE_AVATAR; + } + + const std::string& convertAgentControlModeToString(LLGameControl::AgentControlMode mode) + { + if (mode == LLGameControl::CONTROL_MODE_NONE) + return ENUM_AGENTCONTROLMODE_NONE; + if (mode == LLGameControl::CONTROL_MODE_FLYCAM) + return ENUM_AGENTCONTROLMODE_FLYCAM; + // All values except NONE and FLYCAM are treated as default (AVATAR) + return LLStringUtil::null; + } + + const std::string& getDeviceOptionsString(const std::string& guid) + { + const auto& it = g_deviceOptions.find(guid); + return it == g_deviceOptions.end() ? LLStringUtil::null : it->second; + } } LLGameControl::~LLGameControl() @@ -310,18 +501,13 @@ LLGameControl::~LLGameControl() terminate(); } -LLGameControl::State::State() : mButtons(0) +LLGameControl::State::State() +: mButtons(0) { mAxes.resize(NUM_AXES, 0); mPrevAxes.resize(NUM_AXES, 0); } -void LLGameControl::State::setDevice(int joystickID, void* controller) -{ - mJoystickID = joystickID; - mController = controller; -} - void LLGameControl::State::clear() { std::fill(mAxes.begin(), mAxes.end(), 0); @@ -335,7 +521,7 @@ void LLGameControl::State::clear() bool LLGameControl::State::onButton(U8 button, bool pressed) { U32 old_buttons = mButtons; - if (button <= MAX_BUTTON) + if (button < NUM_BUTTONS) { if (pressed) { @@ -346,152 +532,525 @@ bool LLGameControl::State::onButton(U8 button, bool pressed) mButtons &= ~(0x01 << button); } } - bool changed = (old_buttons != mButtons); - return changed; + return mButtons != old_buttons; +} + +LLGameControl::Device::Device(int joystickID, const std::string& guid, const std::string& name) +: mJoystickID(joystickID) +, mGUID(guid) +, mName(name) +{ +} + +LLGameControl::Options::Options() +{ + mAxisOptions.resize(NUM_AXES); + mAxisMap.resize(NUM_AXES); + mButtonMap.resize(NUM_BUTTONS); + + resetToDefaults(); +} + +void LLGameControl::Options::resetToDefaults() +{ + for (size_t i = 0; i < NUM_AXES; ++i) + { + mAxisOptions[i].resetToDefaults(); + mAxisMap[i] = (U8)i; + } + + for (size_t i = 0; i < NUM_BUTTONS; ++i) + { + mButtonMap[i] = (U8)i; + } +} + +U8 LLGameControl::Options::mapAxis(U8 axis) const +{ + if (axis >= NUM_AXES) + { + LL_WARNS("SDL2") << "Invalid input axis: " << axis << LL_ENDL; + return axis; + } + return mAxisMap[axis]; +} + +U8 LLGameControl::Options::mapButton(U8 button) const +{ + if (button >= NUM_BUTTONS) + { + LL_WARNS("SDL2") << "Invalid input button: " << button << LL_ENDL; + return button; + } + return mButtonMap[button]; +} + +S16 LLGameControl::Options::fixAxisValue(U8 axis, S16 value) const +{ + if (axis >= NUM_AXES) + { + LL_WARNS("SDL2") << "Invalid input axis: " << axis << LL_ENDL; + } + else + { + const AxisOptions& options = mAxisOptions[axis]; + S32 new_value = (S32)value + (S32)options.mOffset; + value = (S16)std::clamp(new_value , -32768, 32767); + if ((value > 0 && value < (S16)options.mDeadZone) || + (value < 0 && value > -(S16)options.mDeadZone)) + { + value = 0; + } + else if (options.mInvert) + { + value = -value; + } + } + return value; +} + +std::string LLGameControl::Options::AxisOptions::saveToString() const +{ + std::list<std::string> options; + + if (mInvert) + { + options.push_back("invert:1"); + } + if (mDeadZone) + { + options.push_back(llformat("dead_zone:%u", mDeadZone)); + } + if (mOffset) + { + options.push_back(llformat("offset:%d", mOffset)); + } + + std::string result = LLStringUtil::join(options); + + return result.empty() ? result : "{" + result + "}"; +} + +// Parse string "{key:value,key:{key:value,key:value}}" and fill the map +static bool parse(std::map<std::string, std::string>& result, std::string source) +{ + result.clear(); + + LLStringUtil::trim(source); + if (source.empty()) + return true; + + if (source.front() != '{' || source.back() != '}') + return false; + + source = source.substr(1, source.size() - 2); + + LLStringUtil::trim(source); + if (source.empty()) + return true; + + // Split the string "key:value" and add the pair to the map + auto split = [&](const std::string& pair) -> bool + { + size_t pos = pair.find(':'); + if (!pos || pos == std::string::npos) + return false; + std::string key = pair.substr(0, pos); + std::string value = pair.substr(pos + 1); + LLStringUtil::trim(key); + LLStringUtil::trim(value); + if (key.empty() || value.empty()) + return false; + result[key] = value; + return true; + }; + + U32 depth = 0; + size_t offset = 0; + while (true) + { + size_t pos = source.find_first_of(depth ? "{}" : ",{}", offset); + if (pos == std::string::npos) + { + return !depth && split(source); + } + if (source[pos] == ',') + { + if (!split(source.substr(0, pos))) + return false; + source = source.substr(pos + 1); + offset = 0; + } + else if (source[pos] == '{') + { + depth++; + offset = pos + 1; + } + else if (depth) // Assume '}' here + { + depth--; + offset = pos + 1; + } + else + { + return false; // Extra '}' found + } + } + + return true; +} + +void LLGameControl::Options::AxisOptions::loadFromString(std::string options) +{ + resetToDefaults(); + + if (options.empty()) + return; + + std::map<std::string, std::string> pairs; + if (!parse(pairs, options)) + { + LL_WARNS("SDL2") << "Invalid axis options: '" << options << "'" << LL_ENDL; + } + + std::string invert = pairs["invert"]; + if (!invert.empty()) + { + if (invert != "1") + { + LL_WARNS("SDL2") << "Invalid invert value: '" << invert << "'" << LL_ENDL; + } + else + { + mInvert = true; + } + } + + std::string dead_zone = pairs["dead_zone"]; + if (!dead_zone.empty()) + { + size_t number = std::stoull(dead_zone); + if (number > MAX_AXIS_DEAD_ZONE || std::to_string(number) != dead_zone) + { + LL_WARNS("SDL2") << "Invalid dead_zone value: '" << dead_zone << "'" << LL_ENDL; + } + else + { + mDeadZone = (U16)number; + } + } + + std::string offset = pairs["offset"]; + if (!offset.empty()) + { + S32 number = std::stoi(offset); + if (abs(number) > MAX_AXIS_OFFSET || std::to_string(number) != offset) + { + LL_WARNS("SDL2") << "Invalid offset value: '" << offset << "'" << LL_ENDL; + } + else + { + mOffset = (S16)number; + } + } +} + +std::string LLGameControl::Options::saveToString(const std::string& name, bool force_empty) const +{ + return stringifyDeviceOptions(name, mAxisOptions, mAxisMap, mButtonMap, force_empty); +} + +bool LLGameControl::Options::loadFromString(std::string& name, std::string options) +{ + return LLGameControl::parseDeviceOptions(options, name, mAxisOptions, mAxisMap, mButtonMap); +} + +bool LLGameControl::Options::loadFromString(std::string options) +{ + std::string dummy_name; + return LLGameControl::parseDeviceOptions(options, dummy_name, mAxisOptions, mAxisMap, mButtonMap); } LLGameControllerManager::LLGameControllerManager() { - mAxesAccumulator.resize(NUM_AXES, 0); + mAxesAccumulator.resize(LLGameControl::NUM_AXES, 0); + + mAnalogActions = { "push", "slide", "jump", "turn", "look" }; + mBinaryActions = { "toggle_run", "toggle_fly", "toggle_flycam", "stop" }; + mFlycamActions = { "advance", "pan", "rise", "pitch", "yaw", "zoom" }; + + // Collect all known action names with their types in one container + for (const std::string& name : mAnalogActions) + { + mActions[name] = LLGameControl::ACTION_NAME_ANALOG; + mActions[name + "+"] = LLGameControl::ACTION_NAME_ANALOG_POS; + mActions[name + "-"] = LLGameControl::ACTION_NAME_ANALOG_NEG; + } + for (const std::string& name : mBinaryActions) + { + mActions[name] = LLGameControl::ACTION_NAME_BINARY; + } + for (const std::string& name : mFlycamActions) + { + mActions[name] = LLGameControl::ACTION_NAME_FLYCAM; + } // Here we build an invariant map between the named agent actions - // and control bit sent to the server. This map will be used, + // and control bit sent to the server. This map will be used, // in combination with the action->InputChannel map below, // to maintain an inverse map from control bit masks to GameControl data. - LLGameControlTranslator::ActionToMaskMap actions; - actions["push+"] = AGENT_CONTROL_AT_POS | AGENT_CONTROL_FAST_AT; - actions["push-"] = AGENT_CONTROL_AT_NEG | AGENT_CONTROL_FAST_AT; - actions["slide+"] = AGENT_CONTROL_LEFT_POS | AGENT_CONTROL_FAST_LEFT; - actions["slide-"] = AGENT_CONTROL_LEFT_NEG | AGENT_CONTROL_FAST_LEFT; - actions["jump+"] = AGENT_CONTROL_UP_POS | AGENT_CONTROL_FAST_UP; - actions["jump-"] = AGENT_CONTROL_UP_NEG | AGENT_CONTROL_FAST_UP; - actions["turn+"] = AGENT_CONTROL_YAW_POS; - actions["turn-"] = AGENT_CONTROL_YAW_NEG; - actions["look+"] = AGENT_CONTROL_PITCH_POS; - actions["look-"] = AGENT_CONTROL_PITCH_NEG; - actions["stop"] = AGENT_CONTROL_STOP; + LLGameControlTranslator::ActionToMaskMap actionMasks = + { + // Analog actions (pairs) + { "push+", AGENT_CONTROL_AT_POS | AGENT_CONTROL_FAST_AT }, + { "push-", AGENT_CONTROL_AT_NEG | AGENT_CONTROL_FAST_AT }, + { "slide+", AGENT_CONTROL_LEFT_POS | AGENT_CONTROL_FAST_LEFT }, + { "slide-", AGENT_CONTROL_LEFT_NEG | AGENT_CONTROL_FAST_LEFT }, + { "jump+", AGENT_CONTROL_UP_POS | AGENT_CONTROL_FAST_UP }, + { "jump-", AGENT_CONTROL_UP_NEG | AGENT_CONTROL_FAST_UP }, + { "turn+", AGENT_CONTROL_YAW_POS }, + { "turn-", AGENT_CONTROL_YAW_NEG }, + { "look+", AGENT_CONTROL_PITCH_POS }, + { "look-", AGENT_CONTROL_PITCH_NEG }, + // Button actions + { "stop", AGENT_CONTROL_STOP }, // These are HACKs. We borrow some AGENT_CONTROL bits for "unrelated" features. // Not a problem because these bits are only used internally. - actions["toggle_run"] = AGENT_CONTROL_NUDGE_AT_POS; // HACK - actions["toggle_fly"] = AGENT_CONTROL_FLY; // HACK - actions["toggle_flycam"] = AGENT_CONTROL_NUDGE_AT_NEG; // HACK - mActionTranslator.setAvailableActions(actions); + { "toggle_run", AGENT_CONTROL_NUDGE_AT_POS }, // HACK + { "toggle_fly", AGENT_CONTROL_FLY }, // HACK + { "toggle_flycam", AGENT_CONTROL_NUDGE_AT_NEG }, // HACK + }; + mActionTranslator.setAvailableActionMasks(actionMasks); + + initializeMappingsByDefault(); +} +// static +void LLGameControllerManager::getDefaultMappings( + std::vector<std::pair<std::string, LLGameControl::InputChannel>>& agent_channels, + std::vector<LLGameControl::InputChannel>& flycam_channels) +{ // Here we build a list of pairs between named agent actions and // GameControl channels. Note: we only supply the non-signed names // (e.g. "push" instead of "push+" and "push-") because mActionTranslator // automatially expands action names as necessary. using type = LLGameControl::InputChannel::Type; - std::vector< std::pair< std::string, LLGameControl::InputChannel> > agent_defaults = - { - { "push", { type::TYPE_AXIS, (U8)(LLGameControl::AXIS_LEFTY), 1 } }, - { "slide", { type::TYPE_AXIS, (U8)(LLGameControl::AXIS_LEFTX), 1 } }, - { "jump", { type::TYPE_AXIS, (U8)(LLGameControl::AXIS_TRIGGERLEFT), 1 } }, - { "turn", { type::TYPE_AXIS, (U8)(LLGameControl::AXIS_RIGHTX), 1 } }, - { "look", { type::TYPE_AXIS, (U8)(LLGameControl::AXIS_RIGHTY), 1 } }, - { "toggle_run", { type::TYPE_BUTTON, (U8)(LLGameControl::BUTTON_LEFTSHOULDER) } }, - { "toggle_fly", { type::TYPE_BUTTON, (U8)(LLGameControl::BUTTON_DPAD_UP) } }, - { "toggle_flycam", { type::TYPE_BUTTON, (U8)(LLGameControl::BUTTON_RIGHTSHOULDER) } }, - { "stop", { type::TYPE_BUTTON, (U8)(LLGameControl::BUTTON_LEFTSTICK) } } + agent_channels = + { + // Analog actions (associated by common name - without '+' or '-') + { "push", { type::TYPE_AXIS, LLGameControl::AXIS_LEFTY, 1 } }, + { "slide", { type::TYPE_AXIS, LLGameControl::AXIS_LEFTX, 1 } }, + { "jump", { type::TYPE_AXIS, LLGameControl::AXIS_TRIGGERLEFT, 1 } }, + { "turn", { type::TYPE_AXIS, LLGameControl::AXIS_RIGHTX, 1 } }, + { "look", { type::TYPE_AXIS, LLGameControl::AXIS_RIGHTY, 1 } }, + // Button actions (associated by name) + { "toggle_run", { type::TYPE_BUTTON, LLGameControl::BUTTON_LEFTSHOULDER } }, + { "toggle_fly", { type::TYPE_BUTTON, LLGameControl::BUTTON_DPAD_UP } }, + { "toggle_flycam", { type::TYPE_BUTTON, LLGameControl::BUTTON_RIGHTSHOULDER } }, + { "stop", { type::TYPE_BUTTON, LLGameControl::BUTTON_LEFTSTICK } } }; - mActionTranslator.setMappings(agent_defaults); // Flycam actions don't need bitwise translation, so we maintain the map // of channels here directly rather than using an LLGameControlTranslator. - mFlycamChannels = { - { type::TYPE_AXIS, (U8)(LLGameControl::AXIS_LEFTY), 1 }, // advance - { type::TYPE_AXIS, (U8)(LLGameControl::AXIS_LEFTX), 1 }, // pan - { type::TYPE_AXIS, (U8)(LLGameControl::AXIS_TRIGGERRIGHT), 1 }, // rise - { type::TYPE_AXIS, (U8)(LLGameControl::AXIS_RIGHTY), -1 }, // pitch - { type::TYPE_AXIS, (U8)(LLGameControl::AXIS_RIGHTX), 1 }, // yaw - { type::TYPE_NONE, 0 } // zoom + flycam_channels = + { + // Flycam actions (associated just by an order index) + { type::TYPE_AXIS, LLGameControl::AXIS_LEFTY, 1 }, // advance + { type::TYPE_AXIS, LLGameControl::AXIS_LEFTX, 1 }, // pan + { type::TYPE_AXIS, LLGameControl::AXIS_TRIGGERRIGHT, 1 }, // rise + { type::TYPE_AXIS, LLGameControl::AXIS_RIGHTY, -1 }, // pitch + { type::TYPE_AXIS, LLGameControl::AXIS_RIGHTX, 1 }, // yaw + { type::TYPE_NONE, 0 } // zoom }; } -void LLGameControllerManager::addController(SDL_JoystickID id, SDL_GameController* controller) +void LLGameControllerManager::getDefaultMappings(std::vector<std::pair<std::string, LLGameControl::InputChannel>>& mappings) { - LL_INFOS("GameController") << "joystick id: " << id << ", controller: " << controller << LL_ENDL; + // Join two different data structures into the one + std::vector<LLGameControl::InputChannel> flycam_channels; + getDefaultMappings(mappings, flycam_channels); + for (size_t i = 0; i < flycam_channels.size(); ++i) + { + mappings.emplace_back(mFlycamActions[i], flycam_channels[i]); + } +} + +void LLGameControllerManager::initializeMappingsByDefault() +{ + std::vector<std::pair<std::string, LLGameControl::InputChannel>> agent_channels; + getDefaultMappings(agent_channels, mFlycamChannels); + mActionTranslator.setMappings(agent_channels); +} + +void LLGameControllerManager::resetDeviceOptionsToDefaults() +{ + for (LLGameControl::Device& device : mDevices) + { + device.resetOptionsToDefaults(); + } +} + +void LLGameControllerManager::loadDeviceOptionsFromSettings() +{ + for (LLGameControl::Device& device : mDevices) + { + device.loadOptionsFromString(getDeviceOptionsString(device.getGUID())); + } +} + +void LLGameControllerManager::saveDeviceOptionsToSettings() const +{ + for (const LLGameControl::Device& device : mDevices) + { + std::string options = device.saveOptionsToString(); + if (options.empty()) + { + g_deviceOptions.erase(device.getGUID()); + } + else + { + g_deviceOptions[device.getGUID()] = options; + } + } +} +void LLGameControllerManager::addController(SDL_JoystickID id, const std::string& guid, const std::string& name) +{ llassert(id >= 0); - llassert(controller); - if (findState(id) != mStates.end()) + for (const LLGameControl::Device& device : mDevices) { - LL_WARNS("GameController") << "device already added" << LL_ENDL; - return; + if (device.getJoystickID() == id) + { + LL_WARNS("SDL2") << "device with id=" << id << " was already added" + << ", guid: '" << device.getGUID() << "'" + << ", name: '" << device.getName() << "'" + << LL_ENDL; + return; + } } - mStates.emplace_back().setDevice(id, controller); - LL_DEBUGS("SDL2") << "joystick=0x" << std::hex << id << std::dec - << " controller=" << controller - << LL_ENDL; + mDevices.emplace_back(id, guid, name).loadOptionsFromString(getDeviceOptionsString(guid)); } void LLGameControllerManager::removeController(SDL_JoystickID id) { - LL_INFOS("GameController") << "joystick id: " << id << LL_ENDL; + LL_INFOS("SDL2") << "joystick id: " << id << LL_ENDL; - mStates.remove_if([id](LLGameControl::State& state) + mDevices.remove_if([id](LLGameControl::Device& device) { - return state.getJoystickID() == id; + return device.getJoystickID() == id; }); } void LLGameControllerManager::onAxis(SDL_JoystickID id, U8 axis, S16 value) { - if (axis > MAX_AXIS) + device_it it = findDevice(id); + if (it == mDevices.end()) { + LL_WARNS("SDL2") << "Unknown device: joystick=0x" << std::hex << id << std::dec + << " axis=" << (S32)axis + << " value=" << (S32)value << LL_ENDL; return; } - state_it it = findState(id); - if (it != mStates.end()) + // Map axis using device-specific settings + // or leave the value unchanged + U8 mapped_axis = it->mOptions.mapAxis(axis); + if (mapped_axis != axis) { - // Note: the RAW analog joysticks provide NEGATIVE X,Y values for LEFT,FORWARD - // whereas those directions are actually POSITIVE in SL's local right-handed - // reference frame. Therefore we implicitly negate those axes here where - // they are extracted from SDL, before being used anywhere. - if (axis < SDL_CONTROLLER_AXIS_TRIGGERLEFT) - { - // Note: S16 value is in range [-32768, 32767] which means - // the negative range has an extra possible value. We need - // to add (or subtract) one during negation. - if (value < 0) - { - value = - (value + 1); - } - else if (value > 0) - { - value = (-value) - 1; - } - } + LL_DEBUGS("SDL2") << "Axis mapped: joystick=0x" << std::hex << id << std::dec + << " input axis i=" << (S32)axis + << " mapped axis i=" << (S32)mapped_axis << LL_ENDL; + axis = mapped_axis; + } - LL_DEBUGS("SDL2") << "joystick=0x" << std::hex << id << std::dec + if (axis >= LLGameControl::NUM_AXES) + { + LL_WARNS("SDL2") << "Unknown axis: joystick=0x" << std::hex << id << std::dec << " axis=" << (S32)(axis) << " value=" << (S32)(value) << LL_ENDL; - it->mAxes[axis] = value; + return; } + + // Fix value using device-specific settings + // or leave the value unchanged + S16 fixed_value = it->mOptions.fixAxisValue(axis, value); + if (fixed_value != value) + { + LL_DEBUGS("SDL2") << "Value fixed: joystick=0x" << std::hex << id << std::dec + << " axis i=" << (S32)axis + << " input value=" << (S32)value + << " fixed value=" << (S32)fixed_value << LL_ENDL; + value = fixed_value; + } + + // Note: the RAW analog joysticks provide NEGATIVE X,Y values for LEFT,FORWARD + // whereas those directions are actually POSITIVE in SL's local right-handed + // reference frame. Therefore we implicitly negate those axes here where + // they are extracted from SDL, before being used anywhere. + if (axis < SDL_CONTROLLER_AXIS_TRIGGERLEFT) + { + // Note: S16 value is in range [-32768, 32767] which means + // the negative range has an extra possible value. We need + // to add (or subtract) one during negation. + if (value < 0) + { + value = - (value + 1); + } + else if (value > 0) + { + value = (-value) - 1; + } + } + + LL_DEBUGS("SDL2") << "joystick=0x" << std::hex << id << std::dec + << " axis=" << (S32)(axis) + << " value=" << (S32)(value) << LL_ENDL; + it->mState.mAxes[axis] = value; } void LLGameControllerManager::onButton(SDL_JoystickID id, U8 button, bool pressed) { - state_it it = findState(id); - if (it != mStates.end()) + device_it it = findDevice(id); + if (it == mDevices.end()) { - if (it->onButton(button, pressed)) - { - LL_DEBUGS("SDL2") << "joystick=0x" << std::hex << id << std::dec - << " button i=" << (S32)(button) - << " pressed=" << pressed << LL_ENDL; - } + LL_WARNS("SDL2") << "Unknown device: joystick=0x" << std::hex << id << std::dec + << " button i=" << (S32)button << LL_ENDL; + return; + } + + // Map button using device-specific settings + // or leave the value unchanged + U8 mapped_button = it->mOptions.mapButton(button); + if (mapped_button != button) + { + LL_DEBUGS("SDL2") << "Button mapped: joystick=0x" << std::hex << id << std::dec + << " input button i=" << (S32)button + << " mapped button i=" << (S32)mapped_button << LL_ENDL; + button = mapped_button; + } + + if (button >= LLGameControl::NUM_BUTTONS) + { + LL_WARNS("SDL2") << "Unknown button: joystick=0x" << std::hex << id << std::dec + << " button i=" << (S32)button << LL_ENDL; + return; + } + + if (it->mState.onButton(button, pressed)) + { + LL_DEBUGS("SDL2") << "joystick=0x" << std::hex << id << std::dec + << " button i=" << (S32)button + << " pressed=" << pressed << LL_ENDL; } } void LLGameControllerManager::clearAllStates() { - for (auto& state : mStates) + for (auto& device : mDevices) { - state.clear(); + device.mState.clear(); } mExternalState.clear(); mLastActiveFlags = 0; @@ -505,36 +1064,36 @@ void LLGameControllerManager::accumulateInternalState() mButtonAccumulator = 0; // accumulate the controllers - for (const auto& state : mStates) + for (const auto& device : mDevices) { - mButtonAccumulator |= state.mButtons; - for (size_t i = 0; i < NUM_AXES; ++i) + mButtonAccumulator |= device.mState.mButtons; + for (size_t i = 0; i < LLGameControl::NUM_AXES; ++i) { // Note: we don't bother to clamp the axes yet // because at this stage we haven't yet accumulated the "inner" state. - mAxesAccumulator[i] += (S32)(state.mAxes[i]); + mAxesAccumulator[i] += (S32)device.mState.mAxes[i]; } } } -void LLGameControllerManager::computeFinalState(LLGameControl::State& final_state) +void LLGameControllerManager::computeFinalState() { // We assume accumulateInternalState() has already been called and we will // finish by accumulating "external" state (if enabled) - U32 old_buttons = final_state.mButtons; - final_state.mButtons = mButtonAccumulator; + U32 old_buttons = g_finalState.mButtons; + g_finalState.mButtons = mButtonAccumulator; if (g_translateAgentActions) { // accumulate from mExternalState - final_state.mButtons |= mExternalState.mButtons; + g_finalState.mButtons |= mExternalState.mButtons; } - if (old_buttons != final_state.mButtons) + if (old_buttons != g_finalState.mButtons) { g_nextResendPeriod = 0; // packet needs to go out ASAP } // clamp the accumulated axes - for (size_t i = 0; i < NUM_AXES; ++i) + for (size_t i = 0; i < LLGameControl::NUM_AXES; ++i) { S32 axis = mAxesAccumulator[i]; if (g_translateAgentActions) @@ -543,108 +1102,211 @@ void LLGameControllerManager::computeFinalState(LLGameControl::State& final_stat // rather than onto mAxisAccumulator[i] because the internal // accumulated value is also used to drive the Flycam, and // we don't want any external state leaking into that value. - axis += (S32)(mExternalState.mAxes[i]); + axis += (S32)mExternalState.mAxes[i]; } - axis = (S16)(std::min(std::max(axis, -32768), 32767)); + axis = (S16)std::min(std::max(axis, -32768), 32767); // check for change - if (final_state.mAxes[i] != axis) + if (g_finalState.mAxes[i] != axis) { // When axis changes we explicitly update the corresponding prevAxis // prior to storing axis. The only other place where prevAxis // is updated in updateResendPeriod() which is explicitly called after // a packet is sent. The result is: unchanged axes are included in // first resend but not later ones. - final_state.mPrevAxes[i] = final_state.mAxes[i]; - final_state.mAxes[i] = axis; + g_finalState.mPrevAxes[i] = g_finalState.mAxes[i]; + g_finalState.mAxes[i] = axis; g_nextResendPeriod = 0; // packet needs to go out ASAP } } } -LLGameControl::InputChannel LLGameControllerManager::getChannelByActionName(const std::string& name) const +LLGameControl::ActionNameType LLGameControllerManager::getActionNameType(const std::string& action) const +{ + auto it = mActions.find(action); + return it == mActions.end() ? LLGameControl::ACTION_NAME_UNKNOWN : it->second; +} + +LLGameControl::InputChannel LLGameControllerManager::getChannelByAction(const std::string& action) const { - LLGameControl::InputChannel channel = mActionTranslator.getChannelByAction(name); - if (channel.isNone()) + LLGameControl::InputChannel channel; + auto action_it = mActions.find(action); + if (action_it != mActions.end()) { - // maybe we're looking for a flycam action - channel = getFlycamChannelByActionName(name); + if (action_it->second == LLGameControl::ACTION_NAME_FLYCAM) + { + channel = getFlycamChannelByAction(action); + } + else + { + channel = mActionTranslator.getChannelByAction(action); + } } return channel; } -// helper -S32 get_flycam_index_by_name(const std::string& name) +LLGameControl::InputChannel LLGameControllerManager::getFlycamChannelByAction(const std::string& action) const { - // the Flycam action<-->channel relationship - // is implicitly stored in std::vector in a known order - S32 index = -1; - if (name.rfind("advance", 0) == 0) - { - index = 0; - } - else if (name.rfind("pan", 0) == 0) - { - index = 1; - } - else if (name.rfind("rise", 0) == 0) - { - index = 2; - } - else if (name.rfind("pitch", 0) == 0) - { - index = 3; - } - else if (name.rfind("yaw", 0) == 0) - { - index = 4; - } - else if (name.rfind("zoom", 0) == 0) + auto flycam_it = std::find(mFlycamActions.begin(), mFlycamActions.end(), action); + llassert(flycam_it != mFlycamActions.end()); + std::ptrdiff_t index = std::distance(mFlycamActions.begin(), flycam_it); + return mFlycamChannels[(std::size_t)index]; +} + +// Common implementation of getAnalogMappings(), getBinaryMappings() and getFlycamMappings() +static std::string getMappings(const std::vector<std::string>& actions, LLGameControl::InputChannel::Type type, + std::function<LLGameControl::InputChannel(const std::string& action)> getChannel) +{ + std::list<std::string> mappings; + + std::vector<std::pair<std::string, LLGameControl::InputChannel>> default_mappings; + LLGameControl::getDefaultMappings(default_mappings); + + // Walk through the all known actions of the chosen type + for (const std::string& action : actions) { - index = 5; + LLGameControl::InputChannel channel = getChannel(action); + // Only channels of the expected type should be stored + if (channel.mType == type) + { + bool mapping_differs = false; + for (const auto& pair : default_mappings) + { + if (pair.first == action) + { + mapping_differs = !channel.isEqual(pair.second); + break; + } + } + // Only mappings different from the default should be stored + if (mapping_differs) + { + mappings.push_back(action + ":" + channel.getLocalName()); + } + } } - return index; + + std::string result = LLStringUtil::join(mappings); + + return result; } -LLGameControl::InputChannel LLGameControllerManager::getFlycamChannelByActionName(const std::string& name) const +std::string LLGameControllerManager::getAnalogMappings() const { - // the Flycam channels are stored in a strict order - LLGameControl::InputChannel channel; - S32 index = get_flycam_index_by_name(name); - if (index != -1) + return getMappings(mAnalogActions, LLGameControl::InputChannel::TYPE_AXIS, + [&](const std::string& action) -> LLGameControl::InputChannel + { + return mActionTranslator.getChannelByAction(action + "+"); + }); +} + +std::string LLGameControllerManager::getBinaryMappings() const +{ + return getMappings(mBinaryActions, LLGameControl::InputChannel::TYPE_BUTTON, + [&](const std::string& action) -> LLGameControl::InputChannel + { + return mActionTranslator.getChannelByAction(action); + }); +} + +std::string LLGameControllerManager::getFlycamMappings() const +{ + return getMappings(mFlycamActions, LLGameControl::InputChannel::TYPE_AXIS, + [&](const std::string& action) -> LLGameControl::InputChannel + { + return getFlycamChannelByAction(action); + }); +} + +// Common implementation of setAnalogMappings(), setBinaryMappings() and setFlycamMappings() +static void setMappings(const std::string& mappings, + const std::vector<std::string>& actions, LLGameControl::InputChannel::Type type, + std::function<void(const std::string& action, LLGameControl::InputChannel channel)> updateMap) +{ + if (mappings.empty()) + return; + + std::map<std::string, std::string> pairs; + LLStringOps::splitString(mappings, ',', [&](const std::string& mapping) + { + std::size_t pos = mapping.find(':'); + if (pos > 0 && pos != std::string::npos) + { + pairs[mapping.substr(0, pos)] = mapping.substr(pos + 1); + } + }); + + static const LLGameControl::InputChannel channelNone; + + for (const std::string& action : actions) { - channel = mFlycamChannels[index]; + auto it = pairs.find(action); + if (it != pairs.end()) + { + LLGameControl::InputChannel channel = LLGameControl::getChannelByName(it->second); + if (channel.isNone() || channel.mType == type) + { + updateMap(action, channel); + continue; + } + } + updateMap(action, channelNone); } - return channel; } -bool LLGameControllerManager::updateActionMap(const std::string& action, LLGameControl::InputChannel channel) +void LLGameControllerManager::setAnalogMappings(const std::string& mappings) +{ + setMappings(mappings, mAnalogActions, LLGameControl::InputChannel::TYPE_AXIS, + [&](const std::string& action, LLGameControl::InputChannel channel) + { + mActionTranslator.updateMap(action, channel); + }); +} + +void LLGameControllerManager::setBinaryMappings(const std::string& mappings) +{ + setMappings(mappings, mBinaryActions, LLGameControl::InputChannel::TYPE_BUTTON, + [&](const std::string& action, LLGameControl::InputChannel channel) + { + mActionTranslator.updateMap(action, channel); + }); +} + +void LLGameControllerManager::setFlycamMappings(const std::string& mappings) { - bool success = mActionTranslator.updateMap(action, channel); - if (success) + setMappings(mappings, mFlycamActions, LLGameControl::InputChannel::TYPE_AXIS, + [&](const std::string& action, LLGameControl::InputChannel channel) + { + updateFlycamMap(action, channel); + }); +} + +bool LLGameControllerManager::updateActionMap(const std::string& action, LLGameControl::InputChannel channel) +{ + auto action_it = mActions.find(action); + if (action_it == mActions.end()) { - mLastActiveFlags = 0; + LL_WARNS("SDL2") << "unmappable action='" << action << "'" << LL_ENDL; + return false; } - else + + if (action_it->second == LLGameControl::ACTION_NAME_FLYCAM) { - // maybe we're looking for a flycam action - success = updateFlycamMap(action, channel); + updateFlycamMap(action, channel); } - if (!success) + else { - LL_WARNS("GameControl") << "unmappable action='" << action << "'" << LL_ENDL; + mActionTranslator.updateMap(action, channel); } - return success; + return true; } -bool LLGameControllerManager::updateFlycamMap(const std::string& action, LLGameControl::InputChannel channel) +void LLGameControllerManager::updateFlycamMap(const std::string& action, LLGameControl::InputChannel channel) { - S32 index = get_flycam_index_by_name(action); - if (index != -1) - { - mFlycamChannels[index] = channel; - return true; - } - return false; + auto flycam_it = std::find(mFlycamActions.begin(), mFlycamActions.end(), action); + llassert(flycam_it != mFlycamActions.end()); + std::ptrdiff_t index = std::distance(mFlycamActions.begin(), flycam_it); + llassert(index >= 0 && (std::size_t)index < mFlycamChannels.size()); + mFlycamChannels[(std::size_t)index] = channel; } U32 LLGameControllerManager::computeInternalActionFlags() @@ -672,13 +1334,13 @@ void LLGameControllerManager::getFlycamInputs(std::vector<F32>& inputs) for (const auto& channel: mFlycamChannels) { S16 axis; - if (channel.mIndex == (U8)(LLGameControl::AXIS_TRIGGERLEFT) - || channel.mIndex == (U8)(LLGameControl::AXIS_TRIGGERRIGHT)) + if (channel.mIndex == LLGameControl::AXIS_TRIGGERLEFT || + channel.mIndex == LLGameControl::AXIS_TRIGGERRIGHT) { // TIED TRIGGER HACK: we assume the two triggers are paired together - S32 total_axis = mAxesAccumulator[(U8)(LLGameControl::AXIS_TRIGGERLEFT)] - - mAxesAccumulator[(U8)(LLGameControl::AXIS_TRIGGERRIGHT)]; - if (channel.mIndex == (U8)(LLGameControl::AXIS_TRIGGERRIGHT)) + S32 total_axis = mAxesAccumulator[LLGameControl::AXIS_TRIGGERLEFT] + - mAxesAccumulator[LLGameControl::AXIS_TRIGGERRIGHT]; + if (channel.mIndex == LLGameControl::AXIS_TRIGGERRIGHT) { // negate previous math when TRIGGERRIGHT is positive channel total_axis *= -1; @@ -692,7 +1354,7 @@ void LLGameControllerManager::getFlycamInputs(std::vector<F32>& inputs) // value arrives as S16 in range [-32768, 32767] // so we scale positive and negative values by slightly different factors // to try to map it to [-1, 1] - F32 input = F32(axis) * ((axis > 0.0f) ? 3.051850476e-5 : 3.0517578125e-5f) * channel.mSign; + F32 input = F32(axis) / ((axis > 0.0f) ? 32767 : 32768) * channel.mSign; inputs.push_back(input); } } @@ -737,7 +1399,7 @@ void LLGameControllerManager::setExternalInput(U32 action_flags, U32 buttons) void LLGameControllerManager::clear() { - mStates.clear(); + mDevices.clear(); } U64 get_now_nsec() @@ -748,47 +1410,67 @@ U64 get_now_nsec() void onJoystickDeviceAdded(const SDL_Event& event) { - LL_INFOS("GameController") << "device index: " << event.cdevice.which << LL_ENDL; + SDL_JoystickGUID guid(SDL_JoystickGetDeviceGUID(event.cdevice.which)); + SDL_JoystickType type(SDL_JoystickGetDeviceType(event.cdevice.which)); + std::string name(std::to_string(SDL_JoystickNameForIndex(event.cdevice.which))); + std::string path(std::to_string(SDL_JoystickPathForIndex(event.cdevice.which))); + + LL_INFOS("SDL2") << "joystick {id:" << event.cdevice.which + << ",guid:'" << guid << "'" + << ",type:'" << type << "'" + << ",name:'" << name << "'" + << ",path:'" << path << "'" + << "}" << LL_ENDL; if (SDL_Joystick* joystick = SDL_JoystickOpen(event.cdevice.which)) { - LL_INFOS("GameController") << "joystick: " << joystick << LL_ENDL; + LL_INFOS("SDL2") << "joystick " << joystick << LL_ENDL; } else { - LL_WARNS("GameController") << "Can't open joystick: " << SDL_GetError() << LL_ENDL; + LL_WARNS("SDL2") << "Can't open joystick: " << SDL_GetError() << LL_ENDL; } } void onJoystickDeviceRemoved(const SDL_Event& event) { - LL_INFOS("GameController") << "joystick id: " << event.cdevice.which << LL_ENDL; + LL_INFOS("SDL2") << "joystick id: " << event.cdevice.which << LL_ENDL; } void onControllerDeviceAdded(const SDL_Event& event) { - LL_INFOS("GameController") << "device index: " << event.cdevice.which << LL_ENDL; + std::string guid(std::to_string(SDL_JoystickGetDeviceGUID(event.cdevice.which))); + SDL_GameControllerType type(SDL_GameControllerTypeForIndex(event.cdevice.which)); + std::string name(std::to_string(SDL_GameControllerNameForIndex(event.cdevice.which))); + std::string path(std::to_string(SDL_GameControllerPathForIndex(event.cdevice.which))); + + LL_INFOS("SDL2") << "controller {id:" << event.cdevice.which + << ",guid:'" << guid << "'" + << ",type:'" << type << "'" + << ",name:'" << name << "'" + << ",path:'" << path << "'" + << "}" << LL_ENDL; SDL_JoystickID id = SDL_JoystickGetDeviceInstanceID(event.cdevice.which); if (id < 0) { - LL_WARNS("GameController") << "Can't get device instance ID: " << SDL_GetError() << LL_ENDL; + LL_WARNS("SDL2") << "Can't get device instance ID: " << SDL_GetError() << LL_ENDL; return; } SDL_GameController* controller = SDL_GameControllerOpen(event.cdevice.which); if (!controller) { - LL_WARNS("GameController") << "Can't open game controller: " << SDL_GetError() << LL_ENDL; + LL_WARNS("SDL2") << "Can't open game controller: " << SDL_GetError() << LL_ENDL; return; } - g_manager.addController(id, controller); + g_manager.addController(id, guid, name); } void onControllerDeviceRemoved(const SDL_Event& event) { - LL_INFOS("GameController") << "joystick id=" << event.cdevice.which << LL_ENDL; + LL_INFOS("SDL2") << "joystick id=" << event.cdevice.which << LL_ENDL; SDL_JoystickID id = event.cdevice.which; g_manager.removeController(id); @@ -819,40 +1501,62 @@ void sdl_logger(void *userdata, int category, SDL_LogPriority priority, const ch } // static -void LLGameControl::init(const std::string& gamecontrollerdb_path) +void LLGameControl::init(const std::string& gamecontrollerdb_path, + std::function<bool(const std::string&)> loadBoolean, + std::function<void(const std::string&, bool)> saveBoolean, + std::function<std::string(const std::string&)> loadString, + std::function<void(const std::string&, const std::string&)> saveString, + std::function<LLSD(const std::string&)> loadObject, + std::function<void(const std::string&, const LLSD&)> saveObject) { - if (!g_gameControl) + if (g_gameControl) + return; + + llassert(loadBoolean); + llassert(saveBoolean); + llassert(loadString); + llassert(saveString); + llassert(loadObject); + llassert(saveObject); + + int result = SDL_InitSubSystem(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER); + if (result < 0) { - int result = SDL_InitSubSystem(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER); - if (result < 0) - { - // This error is critical, we stop working with SDL and return - LL_WARNS("GameController") << "Error initializing the subsystems : " << SDL_GetError() << LL_ENDL; - return; - } + // This error is critical, we stop working with SDL and return + LL_WARNS("SDL2") << "Error initializing the subsystems : " << SDL_GetError() << LL_ENDL; + return; + } - SDL_LogSetOutputFunction(&sdl_logger, nullptr); + SDL_LogSetOutputFunction(&sdl_logger, nullptr); - // The inability to read this file is not critical, we can continue working - if (!LLFile::isfile(gamecontrollerdb_path.c_str())) + // The inability to read this file is not critical, we can continue working + if (!LLFile::isfile(gamecontrollerdb_path.c_str())) + { + LL_WARNS("SDL2") << "Device mapping db file not found: " << gamecontrollerdb_path << LL_ENDL; + } + else + { + int count = SDL_GameControllerAddMappingsFromFile(gamecontrollerdb_path.c_str()); + if (count < 0) { - LL_WARNS("GameController") << "Device mapping db file not found: " << gamecontrollerdb_path << LL_ENDL; + LL_WARNS("SDL2") << "Error adding mappings from " << gamecontrollerdb_path << " : " << SDL_GetError() << LL_ENDL; } else { - int count = SDL_GameControllerAddMappingsFromFile(gamecontrollerdb_path.c_str()); - if (count < 0) - { - LL_WARNS("GameController") << "Error adding mappings from " << gamecontrollerdb_path << " : " << SDL_GetError() << LL_ENDL; - } - else - { - LL_INFOS("GameController") << "Total " << count << " mappings added from " << gamecontrollerdb_path << LL_ENDL; - } + LL_INFOS("SDL2") << "Total " << count << " mappings added from " << gamecontrollerdb_path << LL_ENDL; } - - g_gameControl = LLGameControl::getInstance(); } + + g_gameControl = LLGameControl::getInstance(); + + s_loadBoolean = loadBoolean; + s_saveBoolean = saveBoolean; + s_loadString = loadString; + s_saveString = saveString; + s_loadObject = loadObject; + s_saveObject = saveObject; + + loadFromSettings(); } // static @@ -862,6 +1566,18 @@ void LLGameControl::terminate() SDL_Quit(); } +// static +const std::list<LLGameControl::Device>& LLGameControl::getDevices() +{ + return g_manager.mDevices; +} + +//static +const std::map<std::string, std::string>& LLGameControl::getDeviceOptions() +{ + return g_deviceOptions; +} + //static // returns 'true' if GameControlInput message needs to go out, // which will be the case for new data or resend. Call this right @@ -870,7 +1586,7 @@ void LLGameControl::terminate() bool LLGameControl::computeFinalStateAndCheckForChanges() { // Note: LLGameControllerManager::computeFinalState() modifies g_nextResendPeriod as a side-effect - g_manager.computeFinalState(g_finalState); + g_manager.computeFinalState(); // should send input when: // sending is enabled and @@ -938,32 +1654,109 @@ const LLGameControl::State& LLGameControl::getState() } // static +LLGameControl::InputChannel LLGameControl::getActiveInputChannel() +{ + InputChannel input; + + State state = g_finalState; + if (state.mButtons > 0) + { + // check buttons + input.mType = LLGameControl::InputChannel::TYPE_BUTTON; + for (U8 i = 0; i < 32; ++i) + { + if ((0x1 << i) & state.mButtons) + { + input.mIndex = i; + break; + } + } + } + else + { + // scan axes + S16 threshold = std::numeric_limits<S16>::max() / 2; + for (U8 i = 0; i < 6; ++i) + { + if (abs(state.mAxes[i]) > threshold) + { + input.mType = LLGameControl::InputChannel::TYPE_AXIS; + // input.mIndex ultimately translates to a LLGameControl::KeyboardAxis + // which distinguishes between negative and positive directions + // so we must translate to axis index "i" according to the sign + // of the axis value. + input.mIndex = i; + input.mSign = state.mAxes[i] > 0 ? 1 : -1; + break; + } + } + } + + return input; +} + +// static void LLGameControl::getFlycamInputs(std::vector<F32>& inputs_out) { return g_manager.getFlycamInputs(inputs_out); } // static -void LLGameControl::enableSendToServer(bool enable) +void LLGameControl::setSendToServer(bool enable) { g_sendToServer = enable; + s_saveBoolean(SETTING_SENDTOSERVER, g_sendToServer); } // static -void LLGameControl::enableControlAgent(bool enable) +void LLGameControl::setControlAgent(bool enable) { g_controlAgent = enable; + s_saveBoolean(SETTING_CONTROLAGENT, g_controlAgent); } // static -void LLGameControl::enableTranslateAgentActions(bool enable) +void LLGameControl::setTranslateAgentActions(bool enable) { g_translateAgentActions = enable; + s_saveBoolean(SETTING_TRANSLATEACTIONS, g_translateAgentActions); } +// static void LLGameControl::setAgentControlMode(LLGameControl::AgentControlMode mode) { g_agentControlMode = mode; + s_saveString(SETTING_AGENTCONTROLMODE, convertAgentControlModeToString(mode)); +} + +// static +bool LLGameControl::getSendToServer() +{ + return g_sendToServer; +} + +// static +bool LLGameControl::getControlAgent() +{ + return g_controlAgent; +} + +// static +bool LLGameControl::getTranslateAgentActions() +{ + return g_translateAgentActions; +} + +// static +LLGameControl::AgentControlMode LLGameControl::getAgentControlMode() +{ + return g_agentControlMode; +} + +// static +LLGameControl::ActionNameType LLGameControl::getActionNameType(const std::string& action) +{ + return g_manager.getActionNameType(action); } // static @@ -979,58 +1772,39 @@ bool LLGameControl::willControlAvatar() LLGameControl::InputChannel LLGameControl::getChannelByName(const std::string& name) { LLGameControl::InputChannel channel; + // 'name' has two acceptable formats: AXIS_<index>[sign] or BUTTON_<index> - if (name.length() < 6) - { - // name must be at least as long as 'AXIS_n' - return channel; - } - if (name.rfind("AXIS_", 0) == 0) + if (LLStringUtil::startsWith(name, "AXIS_")) { - char c = name[5]; - if (c >= '0') - { - channel.mType = LLGameControl::InputChannel::Type::TYPE_AXIS; - channel.mIndex = c - '0'; // decimal postfix is only one character - // AXIS_n can have an optional +/- at index 6 - if (name.length() >= 6) - { - channel.mSign = (name[6] == '-') ? -1 : 1; - } - else - { - // assume positive axis when sign not provided - channel.mSign = 1; - } - } + channel.mType = LLGameControl::InputChannel::Type::TYPE_AXIS; + // Decimal postfix is only one character + channel.mIndex = atoi(name.substr(5, 1).c_str()); + // AXIS_n can have an optional +/- at index 6 + // Assume positive axis when sign not provided + channel.mSign = name.back() == '-' ? -1 : 1; } - else if (name.rfind("BUTTON_", 0) == 0) + else if (LLStringUtil::startsWith(name, "BUTTON_")) { - // the BUTTON_ decimal postfix can be up to two characters wide - size_t i = 6; - U8 index = 0; - while (i < name.length() && i < 8 && name[i] >= '0') - { - index = index * 10 + name[i] - '0'; - } channel.mType = LLGameControl::InputChannel::Type::TYPE_BUTTON; - channel.mIndex = index; + // Decimal postfix is only one or two characters + channel.mIndex = atoi(name.substr(7).c_str()); } + return channel; } // static // Given an action_name like "push+", or "strafe-", returns the InputChannel // mapped to it if found, else channel.isNone() will be true. -LLGameControl::InputChannel LLGameControl::getChannelByActionName(const std::string& name) +LLGameControl::InputChannel LLGameControl::getChannelByAction(const std::string& action) { - return g_manager.getChannelByActionName(name); + return g_manager.getChannelByAction(action); } // static -bool LLGameControl::updateActionMap(const std::string& action_name, LLGameControl::InputChannel channel) +bool LLGameControl::updateActionMap(const std::string& action, LLGameControl::InputChannel channel) { - return g_manager.updateActionMap(action_name, channel); + return g_manager.updateActionMap(action, channel); } // static @@ -1070,3 +1844,239 @@ void LLGameControl::updateResendPeriod() } } +// static +std::string LLGameControl::stringifyAnalogMappings(getChannel_t getChannel) +{ + return getMappings(g_manager.mAnalogActions, InputChannel::TYPE_AXIS, getChannel); +} + +// static +std::string LLGameControl::stringifyBinaryMappings(getChannel_t getChannel) +{ + return getMappings(g_manager.mBinaryActions, InputChannel::TYPE_BUTTON, getChannel); +} + +// static +std::string LLGameControl::stringifyFlycamMappings(getChannel_t getChannel) +{ + return getMappings(g_manager.mFlycamActions, InputChannel::TYPE_AXIS, getChannel); +} + +// static +void LLGameControl::getDefaultMappings(std::vector<std::pair<std::string, LLGameControl::InputChannel>>& mappings) +{ + g_manager.getDefaultMappings(mappings); +} + +// static +bool LLGameControl::parseDeviceOptions(const std::string& options, std::string& name, + std::vector<LLGameControl::Options::AxisOptions>& axis_options, + std::vector<U8>& axis_map, std::vector<U8>& button_map) +{ + if (options.empty()) + return false; + + name.clear(); + axis_options.resize(NUM_AXES); + axis_map.resize(NUM_AXES); + button_map.resize(NUM_BUTTONS); + + for (size_t i = 0; i < NUM_AXES; ++i) + { + axis_options[i].resetToDefaults(); + axis_map[i] = (U8)i; + } + + for (size_t i = 0; i < NUM_BUTTONS; ++i) + { + button_map[i] = (U8)i; + } + + std::map<std::string, std::string> pairs; + if (!parse(pairs, options)) + { + LL_WARNS("SDL2") << "Invalid options: '" << options << "'" << LL_ENDL; + return false; + } + + std::map<std::string, std::string> axis_string_options; + if (!parse(axis_string_options, pairs["axis_options"])) + { + LL_WARNS("SDL2") << "Invalid axis_options: '" << pairs["axis_options"] << "'" << LL_ENDL; + return false; + } + + std::map<std::string, std::string> axis_string_map; + if (!parse(axis_string_map, pairs["axis_map"])) + { + LL_WARNS("SDL2") << "Invalid axis_map: '" << pairs["axis_map"] << "'" << LL_ENDL; + return false; + } + + std::map<std::string, std::string> button_string_map; + if (!parse(button_string_map, pairs["button_map"])) + { + LL_WARNS("SDL2") << "Invalid button_map: '" << pairs["button_map"] << "'" << LL_ENDL; + return false; + } + + name = pairs["name"]; + + for (size_t i = 0; i < NUM_AXES; ++i) + { + std::string key = std::to_string(i); + + std::string one_axis_options = axis_string_options[key]; + if (!one_axis_options.empty()) + { + axis_options[i].loadFromString(one_axis_options); + } + + std::string value = axis_string_map[key]; + if (!value.empty()) + { + size_t number = std::stoull(value); + if (number >= NUM_AXES || std::to_string(number) != value) + { + LL_WARNS("SDL2") << "Invalid axis mapping: " << i << "->" << value << LL_ENDL; + } + else + { + axis_map[i] = (U8)number; + } + } + } + + for (size_t i = 0; i < NUM_BUTTONS; ++i) + { + std::string value = button_string_map[std::to_string(i)]; + if (!value.empty()) + { + size_t number = std::stoull(value); + if (number >= NUM_BUTTONS || std::to_string(number) != value) + { + LL_WARNS("SDL2") << "Invalid button mapping: " << i << "->" << value << LL_ENDL; + } + else + { + button_map[i] = (U8)number; + } + } + } + + return true; +} + +// static +std::string LLGameControl::stringifyDeviceOptions(const std::string& name, + const std::vector<LLGameControl::Options::AxisOptions>& axis_options, + const std::vector<U8>& axis_map, const std::vector<U8>& button_map, + bool force_empty) +{ + std::list<std::string> options; + + auto opts2str = [](size_t i, const Options::AxisOptions& options) -> std::string + { + std::string string = options.saveToString(); + return string.empty() ? string : llformat("%u:%s", i, string.c_str()); + }; + + std::string axis_options_string = LLStringUtil::join<std::vector<Options::AxisOptions>, Options::AxisOptions>(axis_options, opts2str); + if (!axis_options_string.empty()) + { + options.push_back("axis_options:{" + axis_options_string + "}"); + } + + auto map2str = [](size_t index, const U8& value) -> std::string + { + return value == index ? LLStringUtil::null : llformat("%u:%u", index, value); + }; + + std::string axis_map_string = LLStringUtil::join<std::vector<U8>, U8>(axis_map, map2str); + if (!axis_map_string.empty()) + { + options.push_back("axis_map:{" + axis_map_string + "}"); + } + + std::string button_map_string = LLStringUtil::join<std::vector<U8>, U8>(button_map, map2str); + if (!button_map_string.empty()) + { + options.push_back("button_map:{" + button_map_string + "}"); + } + + if (!force_empty && options.empty()) + return LLStringUtil::null; + + // Remove control characters [',', '{', '}'] from name + std::string safe_name; + safe_name.reserve(name.size()); + for (char c : name) + { + if (c != ',' && c != '{' && c != '}') + { + safe_name.push_back(c); + } + } + options.push_front(llformat("name:%s", safe_name.c_str())); + + std::string result = LLStringUtil::join(options); + + return "{" + result + "}"; +} + +// static +void LLGameControl::initByDefault() +{ + g_sendToServer = false; + g_controlAgent = false; + g_translateAgentActions = false; + g_agentControlMode = CONTROL_MODE_AVATAR; + g_manager.initializeMappingsByDefault(); + g_manager.resetDeviceOptionsToDefaults(); + g_deviceOptions.clear(); +} + +// static +void LLGameControl::loadFromSettings() +{ + // In case of absence of the required setting the default value is assigned + g_sendToServer = s_loadBoolean(SETTING_SENDTOSERVER); + g_controlAgent = s_loadBoolean(SETTING_CONTROLAGENT); + g_translateAgentActions = s_loadBoolean(SETTING_TRANSLATEACTIONS); + g_agentControlMode = convertStringToAgentControlMode(s_loadString(SETTING_AGENTCONTROLMODE)); + + g_manager.initializeMappingsByDefault(); + + // Load action-to-channel mappings + std::string analogMappings = s_loadString(SETTING_ANALOGMAPPINGS); + std::string binaryMappings = s_loadString(SETTING_BINARYMAPPINGS); + std::string flycamMappings = s_loadString(SETTING_FLYCAMMAPPINGS); + g_manager.setAnalogMappings(analogMappings); + g_manager.setBinaryMappings(binaryMappings); + g_manager.setFlycamMappings(flycamMappings); + + // Load device-specific settings + g_deviceOptions.clear(); + LLSD options = s_loadObject(SETTING_KNOWNCONTROLLERS); + for (auto it = options.beginMap(); it != options.endMap(); ++it) + { + g_deviceOptions.emplace(it->first, it->second); + } + g_manager.loadDeviceOptionsFromSettings(); +} + +// static +void LLGameControl::saveToSettings() +{ + s_saveBoolean(SETTING_SENDTOSERVER, g_sendToServer); + s_saveBoolean(SETTING_CONTROLAGENT, g_controlAgent); + s_saveBoolean(SETTING_TRANSLATEACTIONS, g_translateAgentActions); + s_saveString(SETTING_AGENTCONTROLMODE, convertAgentControlModeToString(g_agentControlMode)); + s_saveString(SETTING_ANALOGMAPPINGS, g_manager.getAnalogMappings()); + s_saveString(SETTING_BINARYMAPPINGS, g_manager.getBinaryMappings()); + s_saveString(SETTING_FLYCAMMAPPINGS, g_manager.getFlycamMappings()); + + g_manager.saveDeviceOptionsToSettings(); + LLSD deviceOptions(g_deviceOptions, true); + s_saveObject(SETTING_KNOWNCONTROLLERS, deviceOptions); +} diff --git a/indra/llwindow/llgamecontrol.h b/indra/llwindow/llgamecontrol.h index 104ae3a2c6..d75aa3a018 100644 --- a/indra/llwindow/llgamecontrol.h +++ b/indra/llwindow/llgamecontrol.h @@ -79,20 +79,30 @@ public: CONTROL_MODE_NONE }; - enum KeyboardAxis + enum ActionNameType { - AXIS_LEFTX = 0, + ACTION_NAME_UNKNOWN, + ACTION_NAME_ANALOG, // E.g., "push" + ACTION_NAME_ANALOG_POS, // E.g., "push+" + ACTION_NAME_ANALOG_NEG, // E.g., "push-" + ACTION_NAME_BINARY, // E.g., "stop" + ACTION_NAME_FLYCAM // E.g., "zoom" + }; + + enum KeyboardAxis : U8 + { + AXIS_LEFTX, AXIS_LEFTY, AXIS_RIGHTX, AXIS_RIGHTY, AXIS_TRIGGERLEFT, AXIS_TRIGGERRIGHT, - AXIS_LAST + NUM_AXES }; enum Button { - BUTTON_A = 0, + BUTTON_A, BUTTON_B, BUTTON_X, BUTTON_Y, @@ -123,17 +133,21 @@ public: BUTTON_28, BUTTON_29, BUTTON_30, - BUTTON_31 + BUTTON_31, + NUM_BUTTONS }; + static const U16 MAX_AXIS_DEAD_ZONE = 16384; + static const U16 MAX_AXIS_OFFSET = 16384; + class InputChannel { public: enum Type { + TYPE_NONE, TYPE_AXIS, - TYPE_BUTTON, - TYPE_NONE + TYPE_BUTTON }; InputChannel() {} @@ -141,11 +155,16 @@ public: InputChannel(Type type, U8 index, S32 sign) : mType(type), mSign(sign), mIndex(index) {} // these methods for readability + bool isNone() const { return mType == TYPE_NONE; } bool isAxis() const { return mType == TYPE_AXIS; } bool isButton() const { return mType == TYPE_BUTTON; } - bool isNone() const { return mType == TYPE_NONE; } - std::string getLocalName() const; // AXIS_0-, AXIS_0+, BUTTON_0, etc + bool isEqual(const InputChannel& other) + { + return mType == other.mType && mSign == other.mSign && mIndex == other.mIndex; + } + + std::string getLocalName() const; // AXIS_0-, AXIS_0+, BUTTON_0, NONE etc. std::string getRemoteName() const; // GAME_CONTROL_AXIS_LEFTX, GAME_CONTROL_BUTTON_A, etc Type mType { TYPE_NONE }; @@ -153,15 +172,58 @@ public: U8 mIndex { 255 }; }; + // Options is a data structure for storing device-specific settings + class Options + { + public: + struct AxisOptions + { + bool mInvert { false }; + U16 mDeadZone { 0 }; + S16 mOffset { 0 }; + + void resetToDefaults() + { + mInvert = false; + mDeadZone = 0; + mOffset = 0; + } + + std::string saveToString() const; + void loadFromString(std::string options); + }; + + Options(); + + void resetToDefaults(); + + U8 mapAxis(U8 axis) const; + U8 mapButton(U8 button) const; + + S16 fixAxisValue(U8 axis, S16 value) const; + + std::string saveToString(const std::string& name, bool force_empty = false) const; + bool loadFromString(std::string& name, std::string options); + bool loadFromString(std::string options); + + const std::vector<AxisOptions>& getAxisOptions() const { return mAxisOptions; } + std::vector<AxisOptions>& getAxisOptions() { return mAxisOptions; } + const std::vector<U8>& getAxisMap() const { return mAxisMap; } + std::vector<U8>& getAxisMap() { return mAxisMap; } + const std::vector<U8>& getButtonMap() const { return mButtonMap; } + std::vector<U8>& getButtonMap() { return mButtonMap; } + + private: + std::vector<AxisOptions> mAxisOptions; + std::vector<U8> mAxisMap; + std::vector<U8> mButtonMap; + }; + // State is a minimal class for storing axes and buttons values class State { - int mJoystickID { -1 }; - void* mController { nullptr }; public: State(); - void setDevice(int joystickID, void* controller); - int getJoystickID() const { return mJoystickID; } void clear(); bool onButton(U8 button, bool pressed); std::vector<S16> mAxes; // [ -32768, 32767 ] @@ -169,10 +231,43 @@ public: U32 mButtons; }; + // Device is a data structure for describing any detected controller + class Device + { + const int mJoystickID { -1 }; + const std::string mGUID; + const std::string mName; + Options mOptions; + State mState; + + public: + Device(int joystickID, const std::string& guid, const std::string& name); + int getJoystickID() const { return mJoystickID; } + std::string getGUID() const { return mGUID; } + std::string getName() const { return mName; } + const Options& getOptions() const { return mOptions; } + const State& getState() const { return mState; } + + void resetOptionsToDefaults() { mOptions.resetToDefaults(); } + std::string saveOptionsToString(bool force_empty = false) const { return mOptions.saveToString(mName, force_empty); } + void loadOptionsFromString(const std::string& options) { mOptions.loadFromString(options); } + + friend class LLGameControllerManager; + }; + static bool isInitialized(); - static void init(const std::string& gamecontrollerdb_path); + static void init(const std::string& gamecontrollerdb_path, + std::function<bool(const std::string&)> loadBoolean, + std::function<void(const std::string&, bool)> saveBoolean, + std::function<std::string(const std::string&)> loadString, + std::function<void(const std::string&, const std::string&)> saveString, + std::function<LLSD(const std::string&)> loadObject, + std::function<void(const std::string&, const LLSD&)> saveObject); static void terminate(); + static const std::list<LLGameControl::Device>& getDevices(); + static const std::map<std::string, std::string>& getDeviceOptions(); + // returns 'true' if GameControlInput message needs to go out, // which will be the case for new data or resend. Call this right // before deciding to put a GameControlInput packet on the wire @@ -183,14 +278,21 @@ public: static void processEvents(bool app_has_focus = true); static const State& getState(); + static InputChannel getActiveInputChannel(); static void getFlycamInputs(std::vector<F32>& inputs_out); // these methods for accepting input from keyboard - static void enableSendToServer(bool enable); - static void enableControlAgent(bool enable); - static void enableTranslateAgentActions(bool enable); + static void setSendToServer(bool enable); + static void setControlAgent(bool enable); + static void setTranslateAgentActions(bool enable); static void setAgentControlMode(AgentControlMode mode); + static bool getSendToServer(); + static bool getControlAgent(); + static bool getTranslateAgentActions(); + static AgentControlMode getAgentControlMode(); + static ActionNameType getActionNameType(const std::string& action); + static bool willControlAvatar(); // Given a name like "AXIS_1-" or "BUTTON_5" returns the corresponding InputChannel @@ -198,7 +300,7 @@ public: static LLGameControl::InputChannel getChannelByName(const std::string& name); // action_name = push+, strafe-, etc - static LLGameControl::InputChannel getChannelByActionName(const std::string& name); + static LLGameControl::InputChannel getChannelByAction(const std::string& action); static bool updateActionMap(const std::string& action_name, LLGameControl::InputChannel channel); @@ -210,5 +312,23 @@ public: // call this after putting a GameControlInput packet on the wire static void updateResendPeriod(); + + using getChannel_t = std::function<LLGameControl::InputChannel(const std::string& action)>; + static std::string stringifyAnalogMappings(getChannel_t getChannel); + static std::string stringifyBinaryMappings(getChannel_t getChannel); + static std::string stringifyFlycamMappings(getChannel_t getChannel); + static void getDefaultMappings(std::vector<std::pair<std::string, LLGameControl::InputChannel>>& mappings); + + static bool parseDeviceOptions(const std::string& options, std::string& name, + std::vector<LLGameControl::Options::AxisOptions>& axis_options, + std::vector<U8>& axis_map, std::vector<U8>& button_map); + static std::string stringifyDeviceOptions(const std::string& name, + const std::vector<LLGameControl::Options::AxisOptions>& axis_options, + const std::vector<U8>& axis_map, const std::vector<U8>& button_map, + bool force_empty = false); + + static void initByDefault(); + static void loadFromSettings(); + static void saveToSettings(); }; diff --git a/indra/llwindow/llgamecontroltranslator.cpp b/indra/llwindow/llgamecontroltranslator.cpp index 9654cb04f1..84468ab657 100644 --- a/indra/llwindow/llgamecontroltranslator.cpp +++ b/indra/llwindow/llgamecontroltranslator.cpp @@ -40,7 +40,7 @@ LLGameControlTranslator::LLGameControlTranslator() { } -void LLGameControlTranslator::setAvailableActions(ActionToMaskMap& action_to_mask) +void LLGameControlTranslator::setAvailableActionMasks(ActionToMaskMap& action_to_mask) { mActionToMask = std::move(action_to_mask); } @@ -48,11 +48,11 @@ void LLGameControlTranslator::setAvailableActions(ActionToMaskMap& action_to_mas LLGameControl::InputChannel LLGameControlTranslator::getChannelByAction(const std::string& action) const { LLGameControl::InputChannel channel; - ActionToMaskMap::const_iterator mask_itr = mActionToMask.find(action); + auto mask_itr = mActionToMask.find(action); if (mask_itr != mActionToMask.end()) { U32 mask = mask_itr->second; - LLGameControlTranslator::MaskToChannelMap::const_iterator channel_itr = mMaskToChannel.find(mask); + auto channel_itr = mMaskToChannel.find(mask); if (channel_itr != mMaskToChannel.end()) { channel = channel_itr->second; @@ -68,7 +68,7 @@ LLGameControl::InputChannel LLGameControlTranslator::getChannelByAction(const st if (mask_itr != mActionToMask.end()) { U32 mask = mask_itr->second; - LLGameControlTranslator::MaskToChannelMap::const_iterator channel_itr = mMaskToChannel.find(mask); + auto channel_itr = mMaskToChannel.find(mask); if (channel_itr != mMaskToChannel.end()) { channel = channel_itr->second; @@ -78,140 +78,95 @@ LLGameControl::InputChannel LLGameControlTranslator::getChannelByAction(const st return channel; } -void LLGameControlTranslator::setMappings(LLGameControlTranslator::NamedChannels& list) +void LLGameControlTranslator::setMappings(LLGameControlTranslator::NamedChannels& named_channels) { mMaskToChannel.clear(); mMappedFlags = 0; mPrevActiveFlags = 0; mCachedState.clear(); - for (auto& name_channel : list) + for (const auto& named_channel : named_channels) { - updateMap(name_channel.first, name_channel.second); + updateMap(named_channel.first, named_channel.second); } } -bool LLGameControlTranslator::updateMap(const std::string& name, const LLGameControl::InputChannel& channel) +void LLGameControlTranslator::updateMap(const std::string& action, const LLGameControl::InputChannel& channel) { - bool map_changed = false; - size_t name_length = name.length(); - if (name_length > 1) + // First, get the action name type + LLGameControl::ActionNameType actionNameType = LLGameControl::getActionNameType(action); + if (actionNameType == LLGameControl::ACTION_NAME_UNKNOWN || + actionNameType == LLGameControl::ACTION_NAME_FLYCAM) { - if (channel.isButton()) - { - map_changed = updateMapInternal(name, channel); - } - else if (channel.isAxis()) + LL_WARNS("SDL2") << "unmappable action='" << action << "' (type=" << actionNameType << ")" << LL_ENDL; + return; + } + + // Second, get the expected associated channel type (except of TYPE_NONE) + LLGameControl::InputChannel::Type expectedChannelType = + actionNameType == LLGameControl::ACTION_NAME_BINARY ? + LLGameControl::InputChannel::TYPE_BUTTON : + LLGameControl::InputChannel::TYPE_AXIS; + if (!channel.isNone() && (channel.mType != expectedChannelType)) + { + LL_WARNS("SDL2") << "unmappable channel (type=" << channel.mType << ")" + << " for action='" << action << "' (type=" << actionNameType << ")" << LL_ENDL; + return; + } + + if (actionNameType == LLGameControl::ACTION_NAME_ANALOG) // E.g., "push" + { + // Special (double) processing for analog action names + // sequentially adding '+' and '-' to the given action + updateMapInternal(action + "+", channel); + + // For the channel type TYPE_NONE we can map the same channel + // In fact, the mapping will be removed from the mapping list + if (channel.isNone()) { - U8 last_char = name.at(name_length - 1); - if (last_char == '+' || last_char == '-') - { - map_changed = updateMapInternal(name, channel); - } - else - { - // try to map both "name+" and "name-" - std::string new_name = name; - new_name.append("+"); - bool success = updateMapInternal(new_name, channel); - if (success) - { - new_name.data()[name_length] = '-'; - LLGameControl::InputChannel other_channel(channel.mType, channel.mIndex, -channel.mSign); - // TIED TRIGGER HACK: this works for XBox and similar controllers, - // and those are pretty much the only supported devices right now - // however TODO: figure out how to do this better. - // - // AXIS_TRIGGERLEFT and AXIS_TRIGGERRIGHT are separate axes and most devices - // only allow them to read positive, not negative. When used for motion control - // they are typically paired together. We assume as much here when computing - // the other_channel. - if (channel.mIndex == LLGameControl::AXIS_TRIGGERLEFT) - { - other_channel.mIndex = LLGameControl::AXIS_TRIGGERRIGHT; - other_channel.mSign = 1; - } - else if (channel.mIndex == LLGameControl::AXIS_TRIGGERRIGHT) - { - other_channel.mIndex = LLGameControl::AXIS_TRIGGERLEFT; - other_channel.mSign = 1; - } - updateMapInternal(new_name, other_channel); - map_changed = true; - } - } + updateMapInternal(action + "-", channel); } else { - // channel type is NONE, which means the action needs to be removed from the map - // but we don't know if it mapped to button or axis which is important because - // it if it axis then we need to also remove the other entry. - // So we try to look it up - ActionToMaskMap::iterator mask_itr = mActionToMask.find(name); - if (mask_itr != mActionToMask.end()) - { - // we found the action --> was it mapped to an axis? - bool is_axis = false; - U32 mask = mask_itr->second; - LLGameControlTranslator::MaskToChannelMap::iterator channel_itr = mMaskToChannel.find(mask); - if (channel_itr != mMaskToChannel.end()) - { - if (channel_itr->second.isAxis()) - { - // yes, it is an axis - is_axis = true; - } - } - // remove from map, whether button or axis - updateMapInternal(name, channel); + // For the channel type except of TYPE_NONE we construct the other channel + // with the same type and index but with the opposite sign + LLGameControl::InputChannel other_channel(channel.mType, channel.mIndex, -channel.mSign); - if (is_axis) - { - // also need to remove the other entry - std::string other_name = name; - if (other_name.data()[name.length() - 1] == '-') - { - other_name.data()[name.length() - 1] = '+'; - } - else - { - other_name.data()[name.length() - 1] = '-'; - } - // remove from map - updateMapInternal(other_name, channel); - } + // TIED TRIGGER HACK: this works for XBox and similar controllers, + // and those are pretty much the only supported devices right now + // however TODO: figure out how to do this better. + // + // AXIS_TRIGGERLEFT and AXIS_TRIGGERRIGHT are separate axes and most devices + // only allow them to read positive, not negative. When used for motion control + // they are typically paired together. We assume as much here when computing + // the other_channel. + if (channel.mIndex == LLGameControl::AXIS_TRIGGERLEFT) + { + other_channel.mIndex = LLGameControl::AXIS_TRIGGERRIGHT; + other_channel.mSign = 1; } - else if (name.data()[name.length() - 1] == '+' - || name.data()[name.length() - 1] == '-') + else if (channel.mIndex == LLGameControl::AXIS_TRIGGERRIGHT) { - // action was not found but name doesn't end with +/- - // maybe it is an axis-name sans the +/- on the end - // postfix with '+' and try again - std::string other_name = name; - other_name.append("+"); - map_changed = updateMapInternal(other_name, channel); - if (map_changed) - { - // that worked! now do the other one - other_name.data()[name.length()] = '-'; - updateMapInternal(other_name, channel); - } + other_channel.mIndex = LLGameControl::AXIS_TRIGGERLEFT; + other_channel.mSign = 1; } + updateMapInternal(action + "-", other_channel); } } + else + { + // Simple (single) processing for other action name types + updateMapInternal(action, channel); + } - if (map_changed) + // recompute mMappedFlags + mMappedFlags = 0; + for (auto& pair : mMaskToChannel) { - // recompute mMappedFlags - mMappedFlags = 0; - for (auto& pair : mMaskToChannel) - { - mMappedFlags |= pair.first; - } - mPrevActiveFlags = 0; - mCachedState.clear(); + mMappedFlags |= pair.first; } - return map_changed; + mPrevActiveFlags = 0; + mCachedState.clear(); } // Given external action_flags (i.e. raw avatar input) @@ -249,7 +204,7 @@ const LLGameControl::State& LLGameControlTranslator::computeStateFromFlags(U32 a } else if (channel.isButton()) { - mCachedState.mButtons |= (0x01U << channel.mIndex); + mCachedState.mButtons |= 0x01U << channel.mIndex; } } } @@ -295,46 +250,26 @@ U32 LLGameControlTranslator::computeFlagsFromState(const std::vector<S32>& axes, return action_flags; } -bool LLGameControlTranslator::updateMapInternal(const std::string& name, const LLGameControl::InputChannel& channel) +void LLGameControlTranslator::updateMapInternal(const std::string& name, const LLGameControl::InputChannel& channel) { - bool something_changed = false; - ActionToMaskMap::iterator mask_itr = mActionToMask.find(name); - if (mask_itr != mActionToMask.end()) + auto action_it = mActionToMask.find(name); + llassert(action_it != mActionToMask.end()); + U32 mask = action_it->second; + auto channel_it = mMaskToChannel.find(mask); + if (channel_it == mMaskToChannel.end()) { - U32 mask = mask_itr->second; - something_changed = addOrRemoveMaskMapping(mask, channel); + // create new mapping + mMaskToChannel[mask] = channel; } - return something_changed; -} - -bool LLGameControlTranslator::addOrRemoveMaskMapping(U32 mask, const LLGameControl::InputChannel& channel) -{ - bool success = false; - LLGameControlTranslator::MaskToChannelMap::iterator channel_itr = mMaskToChannel.find(mask); - if (channel_itr != mMaskToChannel.end()) + else if (channel.isNone()) { - LLGameControl::InputChannel old_channel = channel_itr->second; - if (old_channel.mType != channel.mType || old_channel.mIndex != channel.mIndex || old_channel.mSign != channel.mSign) - { - if (channel.isNone()) - { - // remove old mapping - mMaskToChannel.erase(channel_itr); - } - else - { - // update old mapping - channel_itr->second = channel; - } - success = true; - } + // remove old mapping + mMaskToChannel.erase(channel_it); } - else if (! channel.isNone()) + else { - // create new mapping - mMaskToChannel[mask] = channel; - success = true; + // update old mapping + channel_it->second = channel; } - return success; } diff --git a/indra/llwindow/llgamecontroltranslator.h b/indra/llwindow/llgamecontroltranslator.h index f47c4f4a5f..533408014c 100644 --- a/indra/llwindow/llgamecontroltranslator.h +++ b/indra/llwindow/llgamecontroltranslator.h @@ -54,10 +54,10 @@ public: LLGameControlTranslator(); - void setAvailableActions(ActionToMaskMap& action_to_mask); + void setAvailableActionMasks(ActionToMaskMap& action_to_mask); LLGameControl::InputChannel getChannelByAction(const std::string& action) const; - void setMappings(NamedChannels& list); - bool updateMap(const std::string& name, const LLGameControl::InputChannel& channel); + void setMappings(NamedChannels& named_channels); + void updateMap(const std::string& action, const LLGameControl::InputChannel& channel); // Note: to remove a mapping you can call updateMap() with a TYPE_NONE channel // Given external action_flags (i.e. raw avatar input) @@ -72,8 +72,7 @@ public: U32 getMappedFlags() const { return mMappedFlags; } private: - bool updateMapInternal(const std::string& name, const LLGameControl::InputChannel& channel); - bool addOrRemoveMaskMapping(U32 mask, const LLGameControl::InputChannel& channel); + void updateMapInternal(const std::string& name, const LLGameControl::InputChannel& channel); private: // mActionToMask is an invarient map between the possible actions diff --git a/indra/llwindow/llkeyboard.cpp b/indra/llwindow/llkeyboard.cpp index 7784e6c32a..44679d3843 100644 --- a/indra/llwindow/llkeyboard.cpp +++ b/indra/llwindow/llkeyboard.cpp @@ -276,40 +276,58 @@ bool LLKeyboard::handleTranslatedKeyUp(KEY translated_key, U32 translated_mask) return handled; } +<<<<<<< HEAD bool LLKeyboard::handleKeyDown(const U16 key, const U32 mask) { U32 translated_mask = updateModifiers(mask); +======= +bool LLKeyboard::handleKeyDown(const U16 key, const MASK mask) +{ + U32 translated_mask = updateModifiers(mask); +>>>>>>> 7733b56eab (Add GameControl UI for per device settings) KEY translated_key = 0; bool handled = false; if(translateKey(key, &translated_key)) { handled = handleTranslatedKeyDown(translated_key, translated_mask); } +<<<<<<< HEAD if (!handled) { LLGameControl::onKeyDown(translated_key, translated_mask); } +======= +>>>>>>> 7733b56eab (Add GameControl UI for per device settings) return handled; } +<<<<<<< HEAD bool LLKeyboard::handleKeyUp(const U16 key, const U32 mask) { U32 translated_mask = updateModifiers(mask); +======= +bool LLKeyboard::handleKeyUp(const U16 key, const MASK mask) +{ + U32 translated_mask = updateModifiers(mask); +>>>>>>> 7733b56eab (Add GameControl UI for per device settings) KEY translated_key = 0; bool handled = false; if(translateKey(key, &translated_key)) { handled = handleTranslatedKeyUp(translated_key, translated_mask); } +<<<<<<< HEAD if (!handled) { LLGameControl::onKeyUp(translated_key, translated_mask); } +======= +>>>>>>> 7733b56eab (Add GameControl UI for per device settings) return handled; } diff --git a/indra/llwindow/llkeyboard.h b/indra/llwindow/llkeyboard.h index d3c35b1ed4..d91e023b85 100644 --- a/indra/llwindow/llkeyboard.h +++ b/indra/llwindow/llkeyboard.h @@ -80,10 +80,7 @@ public: virtual bool handleKeyUp(const NATIVE_KEY_TYPE key, MASK mask) = 0; virtual bool handleKeyDown(const NATIVE_KEY_TYPE key, MASK mask) = 0; -#ifdef LL_DARWIN - // We only actually use this for macOS. - virtual void handleModifier(MASK mask) = 0; -#endif // LL_DARWIN + virtual void handleModifier(MASK mask) { } // Asynchronously poll the control, alt, and shift keys and set the // appropriate internal key masks. @@ -113,6 +110,7 @@ public: protected: void addKeyName(KEY key, const std::string& name); + virtual MASK updateModifiers(const MASK mask) { return mask; } protected: std::map<NATIVE_KEY_TYPE, KEY> mTranslateKeyMap; // Map of translations from OS keys to Linden KEYs diff --git a/indra/llwindow/llkeyboardmacosx.cpp b/indra/llwindow/llkeyboardmacosx.cpp index 4ce98ee32b..1a403e5d94 100644 --- a/indra/llwindow/llkeyboardmacosx.cpp +++ b/indra/llwindow/llkeyboardmacosx.cpp @@ -162,7 +162,7 @@ LLKeyboardMacOSX::LLKeyboardMacOSX() void LLKeyboardMacOSX::resetMaskKeys() { - U32 mask = getModifiers(); + MASK mask = getModifiers(); // MBW -- XXX -- This mirrors the operation of the Windows version of resetMaskKeys(). // It looks a bit suspicious, as it won't correct for keys that have been released. @@ -187,7 +187,7 @@ void LLKeyboardMacOSX::resetMaskKeys() } /* -static bool translateKeyMac(const U16 key, const U32 mask, KEY &outKey, U32 &outMask) +static bool translateKeyMac(const U16 key, const MASK mask, KEY &outKey, U32 &outMask) { // Translate the virtual keycode into the keycodes the keyboard system expects. U16 virtualKey = (mask >> 24) & 0x0000007F; @@ -203,7 +203,7 @@ void LLKeyboardMacOSX::handleModifier(MASK mask) updateModifiers(mask); } -MASK LLKeyboardMacOSX::updateModifiers(U32 mask) +MASK LLKeyboardMacOSX::updateModifiers(const MASK mask) { // translate the mask MASK out_mask = 0; @@ -226,7 +226,7 @@ MASK LLKeyboardMacOSX::updateModifiers(U32 mask) return out_mask; } -bool LLKeyboardMacOSX::handleKeyDown(const U16 key, const U32 mask) +bool LLKeyboardMacOSX::handleKeyDown(const U16 key, MASK mask) { KEY translated_key = 0; U32 translated_mask = 0; @@ -243,7 +243,7 @@ bool LLKeyboardMacOSX::handleKeyDown(const U16 key, const U32 mask) } -bool LLKeyboardMacOSX::handleKeyUp(const U16 key, const U32 mask) +bool LLKeyboardMacOSX::handleKeyUp(const U16 key, MASK mask) { KEY translated_key = 0; U32 translated_mask = 0; @@ -262,7 +262,7 @@ bool LLKeyboardMacOSX::handleKeyUp(const U16 key, const U32 mask) MASK LLKeyboardMacOSX::currentMask(bool for_mouse_event) { MASK result = MASK_NONE; - U32 mask = getModifiers(); + MASK mask = getModifiers(); if (mask & MAC_SHIFT_KEY) result |= MASK_SHIFT; if (mask & MAC_CTRL_KEY) result |= MASK_CONTROL; diff --git a/indra/llwindow/llkeyboardmacosx.h b/indra/llwindow/llkeyboardmacosx.h index af8a626db8..a5f59f3fba 100644 --- a/indra/llwindow/llkeyboardmacosx.h +++ b/indra/llwindow/llkeyboardmacosx.h @@ -52,7 +52,7 @@ public: void handleModifier(MASK mask) override; protected: - MASK updateModifiers(const U32 mask); + MASK updateModifiers(const MASK mask) override; void setModifierKeyLevel( KEY key, bool new_state ); bool translateNumpadKey( const U16 os_key, KEY *translated_key ); U16 inverseTranslateNumpadKey(const KEY translated_key); diff --git a/indra/llwindow/llkeyboardsdl.cpp b/indra/llwindow/llkeyboardsdl.cpp index 636eaa5491..b8b2b311f7 100644 --- a/indra/llwindow/llkeyboardsdl.cpp +++ b/indra/llwindow/llkeyboardsdl.cpp @@ -177,7 +177,7 @@ void LLKeyboardSDL::resetMaskKeys() } -MASK LLKeyboardSDL::updateModifiers(const U32 mask) +MASK LLKeyboardSDL::updateModifiers(const MASK mask) { // translate the mask MASK out_mask = MASK_NONE; @@ -201,7 +201,7 @@ MASK LLKeyboardSDL::updateModifiers(const U32 mask) } -static U32 adjustNativekeyFromUnhandledMask(const U32 key, const U32 mask) +static U32 adjustNativekeyFromUnhandledMask(const U16 key, const MASK mask) { // SDL doesn't automatically adjust the keysym according to // whether NUMLOCK is engaged, so we massage the keysym manually. @@ -226,7 +226,7 @@ static U32 adjustNativekeyFromUnhandledMask(const U32 key, const U32 mask) } -bool LLKeyboardSDL::handleKeyDown(const U32 key, const U32 mask) +bool LLKeyboardSDL::handleKeyDown(const U32 key, const MASK mask) { U32 adjusted_nativekey; KEY translated_key = 0; @@ -246,7 +246,7 @@ bool LLKeyboardSDL::handleKeyDown(const U32 key, const U32 mask) } -bool LLKeyboardSDL::handleKeyUp(const U32 key, const U32 mask) +bool LLKeyboardSDL::handleKeyUp(const U32 key, const MASK mask) { U32 adjusted_nativekey; KEY translated_key = 0; diff --git a/indra/llwindow/llkeyboardsdl.h b/indra/llwindow/llkeyboardsdl.h index 9ebff66865..fb08fd218b 100644 --- a/indra/llwindow/llkeyboardsdl.h +++ b/indra/llwindow/llkeyboardsdl.h @@ -42,7 +42,7 @@ public: /*virtual*/ void scanKeyboard(); protected: - MASK updateModifiers(const U32 mask); + MASK updateModifiers(const MASK mask) override; void setModifierKeyLevel( KEY key, bool new_state ); bool translateNumpadKey( const U32 os_key, KEY *translated_key ); U16 inverseTranslateNumpadKey(const KEY translated_key); diff --git a/indra/llwindow/llkeyboardwin32.cpp b/indra/llwindow/llkeyboardwin32.cpp index 756caf6fc3..c31ef5c9a3 100644 --- a/indra/llwindow/llkeyboardwin32.cpp +++ b/indra/llwindow/llkeyboardwin32.cpp @@ -182,7 +182,7 @@ void LLKeyboardWin32::resetMaskKeys() //} -MASK LLKeyboardWin32::updateModifiers(U32 mask) +MASK LLKeyboardWin32::updateModifiers(const U32 mask) { //RN: this seems redundant, as we should have already received the appropriate // messages for the modifier keys @@ -191,8 +191,7 @@ MASK LLKeyboardWin32::updateModifiers(U32 mask) // (keydown encoded in high order bit of short) mKeyLevel[KEY_CAPSLOCK] = (GetKeyState(VK_CAPITAL) & 0x0001) != 0; // Low order bit carries the toggle state. // Get mask for keyboard events - MASK mask = currentMask(false); - return mask; + return currentMask(false); } @@ -203,7 +202,7 @@ bool LLKeyboardWin32::handleKeyDown(const U16 key, MASK mask) U32 translated_mask; bool handled = false; - translated_mask = updateModifiers(); + translated_mask = updateModifiers(mask); if (translateExtendedKey(key, mask, &translated_key)) { @@ -220,7 +219,7 @@ bool LLKeyboardWin32::handleKeyUp(const U16 key, MASK mask) U32 translated_mask; bool handled = false; - translated_mask = updateModifiers(); + translated_mask = updateModifiers(mask); if (translateExtendedKey(key, mask, &translated_key)) { diff --git a/indra/llwindow/llkeyboardwin32.h b/indra/llwindow/llkeyboardwin32.h index d3dc65d9aa..ea7bb4d866 100644 --- a/indra/llwindow/llkeyboardwin32.h +++ b/indra/llwindow/llkeyboardwin32.h @@ -49,7 +49,7 @@ public: U16 inverseTranslateExtendedKey(const KEY translated_key); protected: - MASK updateModifiers(); + MASK updateModifiers(const MASK mask) override; //void setModifierKeyLevel( KEY key, bool new_state ); private: std::map<U16, KEY> mTranslateNumpadMap; diff --git a/indra/llwindow/llwindow.cpp b/indra/llwindow/llwindow.cpp index a4b7a65cb2..861fd10e30 100644 --- a/indra/llwindow/llwindow.cpp +++ b/indra/llwindow/llwindow.cpp @@ -418,7 +418,7 @@ LLWindow* LLWindowManager::createWindow( #if LL_WINDOWS new_window = new LLWindowWin32(callbacks, title, name, x, y, width, height, flags, - fullscreen, clearBg, enable_vsync, use_gl, ignore_pixel_depth, fsaa_samples, max_cores, max_vram, max_gl_version); + fullscreen, clearBg, enable_vsync, use_gl, ignore_pixel_depth, fsaa_samples, max_cores, max_gl_version); #elif LL_DARWIN new_window = new LLWindowMacOSX(callbacks, title, name, x, y, width, height, flags, |