summaryrefslogtreecommitdiff
path: root/indra/llwindow
diff options
context:
space:
mode:
Diffstat (limited to 'indra/llwindow')
-rw-r--r--indra/llwindow/llgamecontrol.cpp1638
-rw-r--r--indra/llwindow/llgamecontrol.h156
-rw-r--r--indra/llwindow/llgamecontroltranslator.cpp231
-rw-r--r--indra/llwindow/llgamecontroltranslator.h9
-rw-r--r--indra/llwindow/llkeyboard.cpp18
-rw-r--r--indra/llwindow/llkeyboard.h6
-rw-r--r--indra/llwindow/llkeyboardmacosx.cpp12
-rw-r--r--indra/llwindow/llkeyboardmacosx.h2
-rw-r--r--indra/llwindow/llkeyboardsdl.cpp8
-rw-r--r--indra/llwindow/llkeyboardsdl.h2
-rw-r--r--indra/llwindow/llkeyboardwin32.cpp9
-rw-r--r--indra/llwindow/llkeyboardwin32.h2
-rw-r--r--indra/llwindow/llwindow.cpp2
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,