diff options
Diffstat (limited to 'indra/llwindow')
27 files changed, 3755 insertions, 1352 deletions
diff --git a/indra/llwindow/CMakeLists.txt b/indra/llwindow/CMakeLists.txt index 075e17235a..e86ef2d578 100644 --- a/indra/llwindow/CMakeLists.txt +++ b/indra/llwindow/CMakeLists.txt @@ -18,11 +18,15 @@ include(LLWindow) include(UI) include(ViewerMiscLibs) include(GLM) +include(SDL2) set(llwindow_SOURCE_FILES llcursortypes.cpp + llgamecontrol.cpp + llgamecontroltranslator.cpp llkeyboard.cpp llkeyboardheadless.cpp + llsdl.cpp llwindowheadless.cpp llwindowcallbacks.cpp llwindow.cpp @@ -32,8 +36,11 @@ set(llwindow_HEADER_FILES CMakeLists.txt llcursortypes.h + llgamecontrol.h + llgamecontroltranslator.h llkeyboard.h llkeyboardheadless.h + llsdl.h llwindowheadless.h llwindowcallbacks.h ) @@ -58,7 +65,7 @@ set(llwindow_LINK_LIBRARIES ll::glm ll::glext ll::uilibraries - ll::SDL + ll::SDL2 ll::zlib-ng ) @@ -173,11 +180,11 @@ endif (llwindow_HEADER_FILES) ${viewer_SOURCE_FILES} ) -if (SDL_FOUND) +if (SDL2_FOUND) set_property(TARGET llwindow PROPERTY COMPILE_DEFINITIONS LL_SDL=1 ) -endif (SDL_FOUND) +endif (SDL2_FOUND) target_link_libraries (llwindow ${llwindow_LINK_LIBRARIES}) target_include_directories(llwindow INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/indra/llwindow/llgamecontrol.cpp b/indra/llwindow/llgamecontrol.cpp new file mode 100644 index 0000000000..9d3c854ca2 --- /dev/null +++ b/indra/llwindow/llgamecontrol.cpp @@ -0,0 +1,2122 @@ +/** + * @file llgamecontrol.h + * @brief GameController detection and management + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llgamecontrol.h" + +#include <algorithm> +#include <chrono> +#include <unordered_map> + +#include "SDL2/SDL.h" +#include "SDL2/SDL_gamecontroller.h" +#include "SDL2/SDL_joystick.h" + +#include "indra_constants.h" +#include "llfile.h" +#include "llgamecontroltranslator.h" +#include "llsd.h" + +namespace std +{ + 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) + { + 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"; + } +} + +// 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 +{ + string to_string(SDL_Joystick* joystick) + { + 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:'" << ll_safe_string(SDL_JoystickName(joystick)) << "'"; + ss << ",vendor:" << SDL_JoystickGetVendor(joystick); + ss << ",product:" << SDL_JoystickGetProduct(joystick); + if (U16 version = SDL_JoystickGetProductVersion(joystick)) + { + ss << ",version:" << version; + } + 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(); + } + + string to_string(SDL_GameController* controller) + { + if (!controller) + { + return "nullptr"; + } + + stringstream ss; + + ss << "{type:'" << SDL_GameControllerGetType(controller) << "'"; + ss << ",name:'" << ll_safe_string(SDL_GameControllerName(controller)) << "'"; + ss << ",vendor:" << SDL_GameControllerGetVendor(controller); + ss << ",product:" << SDL_GameControllerGetProduct(controller); + if (U16 version = SDL_GameControllerGetProductVersion(controller)) + { + ss << ",version:" << version; + } + 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. + + if ((mType == LLGameControl::InputChannel::TYPE_AXIS) && (mIndex < NUM_AXES)) + { + return "AXIS_" + std::to_string((U32)mIndex) + + (mSign < 0 ? "-" : mSign > 0 ? "+" : ""); + } + + if ((mType == LLGameControl::InputChannel::TYPE_BUTTON) && (mIndex < NUM_BUTTONS)) + { + return "BUTTON_" + std::to_string((U32)mIndex); + } + + return "NONE"; +} + +std::string LLGameControl::InputChannel::getRemoteName() const +{ + // HACK: we hard-code English channel names, but + // they should be loaded from localized XML config files. + std::string name = " "; + // GAME_CONTROL_AXIS_LEFTX, GAME_CONTROL_BUTTON_A, etc + if (mType == LLGameControl::InputChannel::TYPE_AXIS) + { + switch (mIndex) + { + case 0: + name = "GAME_CONTROL_AXIS_LEFTX"; + break; + case 1: + name = "GAME_CONTROL_AXIS_LEFTY"; + break; + case 2: + name = "GAME_CONTROL_AXIS_RIGHTX"; + break; + case 3: + name = "GAME_CONTROL_AXIS_RIGHTY"; + break; + case 4: + name = "GAME_CONTROL_AXIS_PADDLELEFT"; + break; + case 5: + name = "GAME_CONTROL_AXIS_PADDLERIGHT"; + break; + default: + break; + } + } + else if (mType == LLGameControl::InputChannel::TYPE_BUTTON) + { + switch(mIndex) + { + case 0: + name = "GAME_CONTROL_BUTTON_A"; + break; + case 1: + name = "GAME_CONTROL_BUTTON_B"; + break; + case 2: + name = "GAME_CONTROL_BUTTON_X"; + break; + case 3: + name = "GAME_CONTROL_BUTTON_Y"; + break; + case 4: + name = "GAME_CONTROL_BUTTON_BACK"; + break; + case 5: + name = "GAME_CONTROL_BUTTON_GUIDE"; + break; + case 6: + name = "GAME_CONTROL_BUTTON_START"; + break; + case 7: + name = "GAME_CONTROL_BUTTON_LEFTSTICK"; + break; + case 8: + name = "GAME_CONTROL_BUTTON_RIGHTSTICK"; + break; + case 9: + name = "GAME_CONTROL_BUTTON_LEFTSHOULDER"; + break; + case 10: + name = "GAME_CONTROL_BUTTON_RIGHTSHOULDER"; + break; + case 11: + name = "GAME_CONTROL_BUTTON_DPAD_UP"; + break; + case 12: + name = "GAME_CONTROL_BUTTON_DPAD_DOWN"; + break; + case 13: + name = "GAME_CONTROL_BUTTON_DPAD_LEFT"; + break; + case 14: + name = "GAME_CONTROL_BUTTON_DPAD_RIGHT"; + break; + case 15: + name = "GAME_CONTROL_BUTTON_MISC1"; + break; + case 16: + name = "GAME_CONTROL_BUTTON_PADDLE1"; + break; + case 17: + name = "GAME_CONTROL_BUTTON_PADDLE2"; + break; + case 18: + name = "GAME_CONTROL_BUTTON_PADDLE3"; + break; + case 19: + name = "GAME_CONTROL_BUTTON_PADDLE4"; + break; + case 20: + name = "GAME_CONTROL_BUTTON_TOUCHPAD"; + break; + default: + break; + } + } + return name; +} + + +// internal class for managing list of controllers and per-controller state +class LLGameControllerManager +{ +public: + using ActionToChannelMap = std::map< std::string, LLGameControl::InputChannel >; + LLGameControllerManager(); + + 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 setDeviceOptions(const std::string& guid, const LLGameControl::Options& options); + + 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); + void onButton(SDL_JoystickID id, U8 button, bool pressed); + + void clearAllStates(); + + void accumulateInternalState(); + void computeFinalState(); + + 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: + void updateFlycamMap(const std::string& action, LLGameControl::InputChannel channel); + + 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(mDevices.begin(), mDevices.end(), + [id](LLGameControl::Device& device) + { + 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 +namespace +{ + LLGameControl* g_gameControl = nullptr; + LLGameControllerManager g_manager; + + // The GameControlInput message is sent via UDP which is lossy. + // Since we send the only the list of pressed buttons the receiving + // side can compute the difference between subsequent states to + // find button-down/button-up events. + // + // To reduce the likelihood of buttons being stuck "pressed" forever + // on the receiving side (for lost final packet) we resend the last + // data state. However, to keep the ambient resend bandwidth low we + // expand the resend period at a geometric rate. + // + constexpr U64 MSEC_PER_NSEC = 1000000; + constexpr U64 FIRST_RESEND_PERIOD = 100 * MSEC_PER_NSEC; + constexpr U64 RESEND_EXPANSION_RATE = 10; + LLGameControl::State g_outerState; // from controller devices + LLGameControl::State g_innerState; // state from gAgent + LLGameControl::State g_finalState; // sum of inner and outer + U64 g_lastSend = 0; + U64 g_nextResendPeriod = FIRST_RESEND_PERIOD; + + bool g_enabled = false; + bool g_sendToServer = false; + bool g_controlAgent = false; + bool g_translateAgentActions = false; + LLGameControl::AgentControlMode g_agentControlMode = LLGameControl::CONTROL_MODE_AVATAR; + + 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::function<void()> s_updateUI; + + std::string SETTING_ENABLE("EnableGameControl"); + 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() +{ + terminate(); +} + +LLGameControl::State::State() +: mButtons(0) +{ + mAxes.resize(NUM_AXES, 0); + mPrevAxes.resize(NUM_AXES, 0); +} + +void LLGameControl::State::clear() +{ + std::fill(mAxes.begin(), mAxes.end(), 0); + + // DO NOT clear mPrevAxes because those are managed by external logic. + //std::fill(mPrevAxes.begin(), mPrevAxes.end(), 0); + + mButtons = 0; +} + +bool LLGameControl::State::onButton(U8 button, bool pressed) +{ + U32 old_buttons = mButtons; + if (button < NUM_BUTTONS) + { + if (pressed) + { + mButtons |= (0x01 << button); + } + else + { + mButtons &= ~(0x01 << button); + } + } + 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 + { + value = mAxisOptions[axis].computeModifiedValue(value); + } + return value; +} + +std::string LLGameControl::Options::AxisOptions::saveToString() const +{ + std::list<std::string> options; + + if (mMultiplier == -1) + { + 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; + } + + mMultiplier = 1; + std::string invert = pairs["invert"]; + if (!invert.empty()) + { + if (invert == "1") + { + mMultiplier = -1; + } + else + { + LL_WARNS("SDL2") << "Invalid invert value: '" << invert << "'" << LL_ENDL; + } + } + + 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) + { + mDeadZone = (U16)number; + } + else + { + LL_WARNS("SDL2") << "Invalid dead_zone value: '" << dead_zone << "'" << LL_ENDL; + } + } + + 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) +{ + resetToDefaults(); + return LLGameControl::parseDeviceOptions(options, name, mAxisOptions, mAxisMap, mButtonMap); +} + +bool LLGameControl::Options::loadFromString(std::string options) +{ + resetToDefaults(); + std::string dummy_name; + return LLGameControl::parseDeviceOptions(options, dummy_name, mAxisOptions, mAxisMap, mButtonMap); +} + +LLGameControllerManager::LLGameControllerManager() +{ + 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, + // in combination with the action->InputChannel map below, + // to maintain an inverse map from control bit masks to GameControl data. + 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. + { "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; + 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 } } + }; + + // Flycam actions don't need bitwise translation, so we maintain the map + // of channels here directly rather than using an LLGameControlTranslator. + 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::getDefaultMappings(std::vector<std::pair<std::string, LLGameControl::InputChannel>>& mappings) +{ + // 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::setDeviceOptions(const std::string& guid, const LLGameControl::Options& options) +{ + // find Device by name + for (LLGameControl::Device& device : mDevices) + { + if (device.getGUID() == guid) + { + device.mOptions = options; + return; + } + } +} + +void LLGameControllerManager::addController(SDL_JoystickID id, const std::string& guid, const std::string& name) +{ + llassert(id >= 0); + + for (const LLGameControl::Device& device : mDevices) + { + if (device.getJoystickID() == id) + { + LL_WARNS("SDL2") << "device with id=" << id << " was already added" + << ", guid: '" << device.getGUID() << "'" + << ", name: '" << device.getName() << "'" + << LL_ENDL; + return; + } + } + + mDevices.emplace_back(id, guid, name).loadOptionsFromString(getDeviceOptionsString(guid)); +} + +void LLGameControllerManager::removeController(SDL_JoystickID id) +{ + LL_INFOS("SDL2") << "joystick id: " << id << LL_ENDL; + + mDevices.remove_if([id](LLGameControl::Device& device) + { + return device.getJoystickID() == id; + }); +} + +void LLGameControllerManager::onAxis(SDL_JoystickID id, U8 axis, S16 value) +{ + 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; + } + + // Map axis using device-specific settings + // or leave the value unchanged + U8 mapped_axis = it->mOptions.mapAxis(axis); + if (mapped_axis != axis) + { + 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; + } + + 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; + 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) +{ + device_it it = findDevice(id); + if (it == mDevices.end()) + { + 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& device : mDevices) + { + device.mState.clear(); + } + mExternalState.clear(); + mLastActiveFlags = 0; + mLastFlycamActionFlags = 0; +} + +void LLGameControllerManager::accumulateInternalState() +{ + // clear the old state + std::fill(mAxesAccumulator.begin(), mAxesAccumulator.end(), 0); + mButtonAccumulator = 0; + + // accumulate the controllers + for (const auto& device : mDevices) + { + 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)device.mState.mAxes[i]; + } + } +} + +void LLGameControllerManager::computeFinalState() +{ + // We assume accumulateInternalState() has already been called and we will + // finish by accumulating "external" state (if enabled) + U32 old_buttons = g_finalState.mButtons; + g_finalState.mButtons = mButtonAccumulator; + if (g_translateAgentActions) + { + // accumulate from mExternalState + g_finalState.mButtons |= mExternalState.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 < LLGameControl::NUM_AXES; ++i) + { + S32 axis = mAxesAccumulator[i]; + if (g_translateAgentActions) + { + // Note: we accumulate mExternalState onto local 'axis' variable + // 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 = (S16)std::min(std::max(axis, -32768), 32767); + // check for change + 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. + g_finalState.mPrevAxes[i] = g_finalState.mAxes[i]; + g_finalState.mAxes[i] = axis; + g_nextResendPeriod = 0; // packet needs to go out ASAP + } + } +} + +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; + auto action_it = mActions.find(action); + if (action_it != mActions.end()) + { + if (action_it->second == LLGameControl::ACTION_NAME_FLYCAM) + { + channel = getFlycamChannelByAction(action); + } + else + { + channel = mActionTranslator.getChannelByAction(action); + } + } + return channel; +} + +LLGameControl::InputChannel LLGameControllerManager::getFlycamChannelByAction(const std::string& action) const +{ + 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) + { + 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()); + } + } + } + + std::string result = LLStringUtil::join(mappings); + + return result; +} + +std::string LLGameControllerManager::getAnalogMappings() const +{ + 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) + { + 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); + } +} + +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) +{ + 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()) + { + LL_WARNS("SDL2") << "unmappable action='" << action << "'" << LL_ENDL; + return false; + } + + if (action_it->second == LLGameControl::ACTION_NAME_FLYCAM) + { + updateFlycamMap(action, channel); + } + else + { + mActionTranslator.updateMap(action, channel); + } + return true; +} + +void LLGameControllerManager::updateFlycamMap(const std::string& action, LLGameControl::InputChannel channel) +{ + 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() +{ + // add up device inputs + accumulateInternalState(); + if (g_controlAgent) + { + return mActionTranslator.computeFlagsFromState(mAxesAccumulator, mButtonAccumulator); + } + return 0; +} + +void LLGameControllerManager::getFlycamInputs(std::vector<F32>& inputs) +{ + // The inputs are packed in the same order as they exist in mFlycamChannels: + // + // advance + // pan + // rise + // pitch + // yaw + // zoom + // + for (const auto& channel: mFlycamChannels) + { + S16 axis; + 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[LLGameControl::AXIS_TRIGGERLEFT] + - mAxesAccumulator[LLGameControl::AXIS_TRIGGERRIGHT]; + if (channel.mIndex == LLGameControl::AXIS_TRIGGERRIGHT) + { + // negate previous math when TRIGGERRIGHT is positive channel + total_axis *= -1; + } + axis = S16(std::min(std::max(total_axis, -32768), 32767)); + } + else + { + axis = S16(std::min(std::max(mAxesAccumulator[channel.mIndex], -32768), 32767)); + } + // 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) ? 32767 : 32768) * channel.mSign; + inputs.push_back(input); + } +} + +void LLGameControllerManager::setExternalInput(U32 action_flags, U32 buttons) +{ + if (g_translateAgentActions) + { + // HACK: these are the bits we can safely translate from control flags to GameControl + // Extracting LLGameControl::InputChannels that are mapped to other bits is a WIP. + // TODO: translate other bits to GameControl, which might require measure of gAgent + // state changes (e.g. sitting <--> standing, flying <--> not-flying, etc) + const U32 BITS_OF_INTEREST = + AGENT_CONTROL_AT_POS | AGENT_CONTROL_AT_NEG + | AGENT_CONTROL_LEFT_POS | AGENT_CONTROL_LEFT_NEG + | AGENT_CONTROL_UP_POS | AGENT_CONTROL_UP_NEG + | AGENT_CONTROL_YAW_POS | AGENT_CONTROL_YAW_NEG + | AGENT_CONTROL_PITCH_POS | AGENT_CONTROL_PITCH_NEG + | AGENT_CONTROL_STOP + | AGENT_CONTROL_FAST_AT + | AGENT_CONTROL_FAST_LEFT + | AGENT_CONTROL_FAST_UP; + action_flags &= BITS_OF_INTEREST; + + U32 active_flags = action_flags & mActionTranslator.getMappedFlags(); + if (active_flags != mLastActiveFlags) + { + mLastActiveFlags = active_flags; + mExternalState = mActionTranslator.computeStateFromFlags(action_flags); + mExternalState.mButtons |= buttons; + } + else + { + mExternalState.mButtons = buttons; + } + } + else + { + mExternalState.mButtons = buttons; + } +} + +void LLGameControllerManager::clear() +{ + mDevices.clear(); +} + +U64 get_now_nsec() +{ + std::chrono::time_point<std::chrono::steady_clock> t0; + return (std::chrono::steady_clock::now() - t0).count(); +} + +void onJoystickDeviceAdded(const SDL_Event& event) +{ + SDL_JoystickGUID guid(SDL_JoystickGetDeviceGUID(event.cdevice.which)); + SDL_JoystickType type(SDL_JoystickGetDeviceType(event.cdevice.which)); + std::string name(ll_safe_string(SDL_JoystickNameForIndex(event.cdevice.which))); + + LL_INFOS("SDL2") << "joystick {id:" << event.cdevice.which + << ",guid:'" << guid << "'" + << ",type:'" << type << "'" + << ",name:'" << name << "'" + << "}" << LL_ENDL; + + if (SDL_Joystick* joystick = SDL_JoystickOpen(event.cdevice.which)) + { + LL_INFOS("SDL2") << "joystick " << joystick << LL_ENDL; + } + else + { + LL_WARNS("SDL2") << "Can't open joystick: " << SDL_GetError() << LL_ENDL; + } +} + +void onJoystickDeviceRemoved(const SDL_Event& event) +{ + LL_INFOS("SDL2") << "joystick id: " << event.cdevice.which << LL_ENDL; +} + +void onControllerDeviceAdded(const SDL_Event& event) +{ + std::string guid(std::to_string(SDL_JoystickGetDeviceGUID(event.cdevice.which))); + SDL_GameControllerType type(SDL_GameControllerTypeForIndex(event.cdevice.which)); + std::string name(ll_safe_string(SDL_GameControllerNameForIndex(event.cdevice.which))); + + LL_INFOS("SDL2") << "controller {id:" << event.cdevice.which + << ",guid:'" << guid << "'" + << ",type:'" << type << "'" + << ",name:'" << name << "'" + << "}" << LL_ENDL; + + SDL_JoystickID id = SDL_JoystickGetDeviceInstanceID(event.cdevice.which); + if (id < 0) + { + 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("SDL2") << "Can't open game controller: " << SDL_GetError() << LL_ENDL; + return; + } + + g_manager.addController(id, guid, name); + + // this event could happen while the preferences UI is open + // in which case we need to force it to update + s_updateUI(); +} + +void onControllerDeviceRemoved(const SDL_Event& event) +{ + LL_INFOS("SDL2") << "joystick id=" << event.cdevice.which << LL_ENDL; + + SDL_JoystickID id = event.cdevice.which; + g_manager.removeController(id); + + // this event could happen while the preferences UI is open + // in which case we need to force it to update + s_updateUI(); +} + +void onControllerButton(const SDL_Event& event) +{ + g_manager.onButton(event.cbutton.which, event.cbutton.button, event.cbutton.state == SDL_PRESSED); +} + +void onControllerAxis(const SDL_Event& event) +{ + LL_DEBUGS("SDL2") << "joystick=0x" << std::hex << event.caxis.which << std::dec + << " axis=" << (S32)(event.caxis.axis) + << " value=" << (S32)(event.caxis.value) << LL_ENDL; + g_manager.onAxis(event.caxis.which, event.caxis.axis, event.caxis.value); +} + +// static +bool LLGameControl::isEnabled() +{ + return g_enabled; +} + +void LLGameControl::setEnabled(bool enable) +{ + if (enable != g_enabled) + { + g_enabled = enable; + s_saveBoolean(SETTING_ENABLE, g_enabled); + } +} + +// static +bool LLGameControl::isInitialized() +{ + return g_gameControl != nullptr; +} + +// static +// TODO: find a cleaner way to provide callbacks to LLGameControl +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, + std::function<void()> updateUI) +{ + if (g_gameControl) + return; + + llassert(loadBoolean); + llassert(saveBoolean); + llassert(loadString); + llassert(saveString); + llassert(loadObject); + llassert(saveObject); + llassert(updateUI); + + int result = SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER | SDL_INIT_SENSOR); + if (result < 0) + { + // This error is critical, we stop working with SDL and return + LL_WARNS("SDL2") << "Error initializing GameController subsystems : " << SDL_GetError() << LL_ENDL; + return; + } + + // 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("SDL2") << "Error adding mappings from " << gamecontrollerdb_path << " : " << SDL_GetError() << LL_ENDL; + } + else + { + LL_INFOS("SDL2") << "Total " << count << " mappings added from " << gamecontrollerdb_path << LL_ENDL; + } + } + + g_gameControl = LLGameControl::getInstance(); + + s_loadBoolean = loadBoolean; + s_saveBoolean = saveBoolean; + s_loadString = loadString; + s_saveString = saveString; + s_loadObject = loadObject; + s_saveObject = saveObject; + s_updateUI = updateUI; + + loadFromSettings(); +} + +// static +void LLGameControl::terminate() +{ + g_manager.clear(); +} + +// 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 +// before deciding to put a GameControlInput packet on the wire +// or not. +bool LLGameControl::computeFinalStateAndCheckForChanges() +{ + // Note: LLGameControllerManager::computeFinalState() modifies g_nextResendPeriod as a side-effect + g_manager.computeFinalState(); + + // should send input when: + // sending is enabled and + // g_lastSend has "expired" + // either because g_nextResendPeriod has been zeroed + // or the last send really has expired. + return g_enabled && g_sendToServer && (g_lastSend + g_nextResendPeriod < get_now_nsec()); +} + +// static +void LLGameControl::clearAllStates() +{ + g_manager.clearAllStates(); +} + +// static +void LLGameControl::processEvents(bool app_has_focus) +{ + // This method used by non-linux platforms which only use SDL for GameController input + SDL_Event event; + if (!app_has_focus) + { + // when SL window lacks focus: pump SDL events but ignore them + while (g_gameControl && SDL_PollEvent(&event)) + { + // do nothing: SDL_PollEvent() is the operator + } + g_manager.clearAllStates(); + return; + } + + while (g_gameControl && SDL_PollEvent(&event)) + { + handleEvent(event, app_has_focus); + } +} + +void LLGameControl::handleEvent(const SDL_Event& event, bool app_has_focus) +{ + switch (event.type) + { + case SDL_JOYDEVICEADDED: + onJoystickDeviceAdded(event); + break; + case SDL_JOYDEVICEREMOVED: + onJoystickDeviceRemoved(event); + break; + case SDL_CONTROLLERDEVICEADDED: + onControllerDeviceAdded(event); + break; + case SDL_CONTROLLERDEVICEREMOVED: + onControllerDeviceRemoved(event); + break; + case SDL_CONTROLLERBUTTONDOWN: + /* FALLTHROUGH */ + case SDL_CONTROLLERBUTTONUP: + if (app_has_focus) + { + onControllerButton(event); + } + break; + case SDL_CONTROLLERAXISMOTION: + if (app_has_focus) + { + onControllerAxis(event); + } + break; + default: + break; + } +} + +// static +const LLGameControl::State& LLGameControl::getState() +{ + return g_finalState; +} + +// 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 + constexpr 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::setSendToServer(bool enable) +{ + g_sendToServer = enable; + s_saveBoolean(SETTING_SENDTOSERVER, g_sendToServer); +} + +// static +void LLGameControl::setControlAgent(bool enable) +{ + g_controlAgent = enable; + s_saveBoolean(SETTING_CONTROLAGENT, g_controlAgent); +} + +// static +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 +bool LLGameControl::willControlAvatar() +{ + return g_enabled && g_controlAgent && g_agentControlMode == CONTROL_MODE_AVATAR; +} + +// static +// +// Given a name like "AXIS_1-" or "BUTTON_5" returns the corresponding InputChannel +// If the axis name lacks the +/- postfix it assumes '+' postfix. +LLGameControl::InputChannel LLGameControl::getChannelByName(const std::string& name) +{ + LLGameControl::InputChannel channel; + + // 'name' has two acceptable formats: AXIS_<index>[sign] or BUTTON_<index> + if (LLStringUtil::startsWith(name, "AXIS_")) + { + 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 (LLStringUtil::startsWith(name, "BUTTON_")) + { + channel.mType = LLGameControl::InputChannel::Type::TYPE_BUTTON; + // 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::getChannelByAction(const std::string& action) +{ + return g_manager.getChannelByAction(action); +} + +// static +bool LLGameControl::updateActionMap(const std::string& action, LLGameControl::InputChannel channel) +{ + return g_manager.updateActionMap(action, channel); +} + +// static +U32 LLGameControl::computeInternalActionFlags() +{ + return g_manager.computeInternalActionFlags(); +} + +// static +void LLGameControl::setExternalInput(U32 action_flags, U32 buttons) +{ + g_manager.setExternalInput(action_flags, buttons); +} + +//static +void LLGameControl::updateResendPeriod() +{ + // we expect this method to be called right after data is sent + g_lastSend = get_now_nsec(); + if (g_nextResendPeriod == 0) + { + g_nextResendPeriod = FIRST_RESEND_PERIOD; + } + else + { + // Reset mPrevAxes only on second resend or higher + // because when the joysticks are being used we expect a steady stream + // of recorrection data rather than sparse changes. + // + // (The above assumption is not necessarily true for "Actions" input + // (e.g. keyboard events). TODO: figure out what to do about this.) + // + // In other words: we want to include changed axes in the first resend + // so we only overwrite g_finalState.mPrevAxes on higher resends. + g_finalState.mPrevAxes = g_finalState.mAxes; + g_nextResendPeriod *= RESEND_EXPANSION_RATE; + } +} + +// 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_enabled = s_loadBoolean(SETTING_ENABLE); + 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_ENABLE, g_enabled); + 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(); + + // construct LLSD version of g_deviceOptions but only include non-empty values + LLSD deviceOptions = LLSD::emptyMap(); + for (const auto& data_pair : g_deviceOptions) + { + if (!data_pair.second.empty()) + { + LLSD value(data_pair.second); + deviceOptions.insert(data_pair.first, value); + } + } + + s_saveObject(SETTING_KNOWNCONTROLLERS, deviceOptions); +} + +// static +void LLGameControl::setDeviceOptions(const std::string& guid, const Options& options) +{ + g_manager.setDeviceOptions(guid, options); +} diff --git a/indra/llwindow/llgamecontrol.h b/indra/llwindow/llgamecontrol.h new file mode 100644 index 0000000000..a4b47cc47f --- /dev/null +++ b/indra/llwindow/llgamecontrol.h @@ -0,0 +1,347 @@ +/** + * @file llgamecontrol.h + * @brief GameController detection and management + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#pragma once + +#include <vector> + + +#include "llerror.h" +#include "llsingleton.h" +#include "stdtypes.h" +#include "SDL2/SDL_events.h" + +// For reference, here are the RAW indices of the various input channels +// of a standard XBox controller. Button (N) is numbered in parentheses, +// whereas axisN has N+ and N- labels. +// +// leftpaddle rightpaddle +// _______ _______ +// / 4+ '-. .-' 5+ \. +// leftshoulder _(9)_________'-.____ ____.-'_________(10) rightshoulder +// / _________ \_________/ \. +// / / 1- \ (3) \. +// | | | (4) (5) (6) Y | +// | |0- (7) 0+| _________ (2)X B(1) | +// | | | / 3- \ A | +// | | 1+ | | | (0) | +// | \_________/ |2- (8) 2+| | +// | leftstick (11) | | | +// | (13) (14) | 3+ | | +// | (12) \_________/ | +// | d-pad rightstick | +// | ____________________ | +// | / \ | +// | / \ | +// | / \ | +// \__________/ \__________/ +// +// Note: the analog joysticks provide NEGATIVE X,Y values for LEFT,FORWARD +// whereas those directions are actually POSITIVE in SL's local right-handed +// reference frame. This is why we implicitly negate those axes the moment +// they are extracted from SDL, before being used anywhere. See the +// implementation in LLGameControllerManager::onAxis(). + +// LLGameControl is a singleton with pure static public interface +class LLGameControl : public LLSingleton<LLGameControl> +{ + LLSINGLETON_EMPTY_CTOR(LLGameControl); + virtual ~LLGameControl(); + LOG_CLASS(LLGameControl); + +public: + enum AgentControlMode + { + CONTROL_MODE_AVATAR, + CONTROL_MODE_FLYCAM, + CONTROL_MODE_NONE + }; + + enum ActionNameType + { + 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, + NUM_AXES + }; + + enum Button + { + BUTTON_A, + BUTTON_B, + BUTTON_X, + BUTTON_Y, + BUTTON_BACK, + BUTTON_GUIDE, + BUTTON_START, + BUTTON_LEFTSTICK, + BUTTON_RIGHTSTICK, + BUTTON_LEFTSHOULDER, + BUTTON_RIGHTSHOULDER, // 10 + BUTTON_DPAD_UP, + BUTTON_DPAD_DOWN, + BUTTON_DPAD_LEFT, + BUTTON_DPAD_RIGHT, + BUTTON_MISC1, + BUTTON_PADDLE1, + BUTTON_PADDLE2, + BUTTON_PADDLE3, + BUTTON_PADDLE4, + BUTTON_TOUCHPAD, // 20 + BUTTON_21, + BUTTON_22, + BUTTON_23, + BUTTON_24, + BUTTON_25, + BUTTON_26, + BUTTON_27, + BUTTON_28, + BUTTON_29, + BUTTON_30, + 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 + }; + + InputChannel() {} + InputChannel(Type type, U8 index) : mType(type), mIndex(index) {} + 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 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 }; + S32 mSign { 0 }; + U8 mIndex { 255 }; + }; + + // Options is a data structure for storing device-specific settings + class Options + { + public: + struct AxisOptions + { + S32 mMultiplier = 1; + U16 mDeadZone { 0 }; + S16 mOffset { 0 }; + + void resetToDefaults() + { + mMultiplier = 1; + mDeadZone = 0; + mOffset = 0; + } + + S16 computeModifiedValue(S16 raw_value) const + { + S32 new_value = ((S32)raw_value + S32(mOffset)) * mMultiplier; + return (S16)(std::clamp(new_value, -32768, 32767)); + } + + 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 + { + public: + State(); + void clear(); + bool onButton(U8 button, bool pressed); + std::vector<S16> mAxes; // [ -32768, 32767 ] + std::vector<S16> mPrevAxes; // value in last outgoing packet + 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 isEnabled(); + static void setEnabled(bool enabled); + + static bool isInitialized(); + 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, + std::function<void()> updateUI); + 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 + // or not. + static bool computeFinalStateAndCheckForChanges(); + + static void clearAllStates(); + + static void processEvents(bool app_has_focus = true); + static void handleEvent(const SDL_Event& event, bool app_has_focus); + static const State& getState(); + static InputChannel getActiveInputChannel(); + static void getFlycamInputs(std::vector<F32>& inputs_out); + + // these methods for accepting input from keyboard + 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 + // If the axis name lacks the +/- postfix it assumes '+' postfix. + static LLGameControl::InputChannel getChannelByName(const std::string& name); + + // action_name = push+, strafe-, etc + static LLGameControl::InputChannel getChannelByAction(const std::string& action); + + static bool updateActionMap(const std::string& action_name, LLGameControl::InputChannel channel); + + // Keyboard presses produce action_flags which can be translated into State + // and game_control devices produce State which can be translated into action_flags. + // These methods help exchange such translations. + static U32 computeInternalActionFlags(); + static void setExternalInput(U32 action_flags, U32 buttons_from_keys); + + // 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(); + static void setDeviceOptions(const std::string& guid, const Options& options); +}; + diff --git a/indra/llwindow/llgamecontroltranslator.cpp b/indra/llwindow/llgamecontroltranslator.cpp new file mode 100644 index 0000000000..84468ab657 --- /dev/null +++ b/indra/llwindow/llgamecontroltranslator.cpp @@ -0,0 +1,275 @@ +/** + * @file llgamecontroltranslator.cpp + * @brief LLGameControlTranslator class implementation + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +/* + * App-wide preferences. Note that these are not per-user, + * because we need to load many preferences before we have + * a login name. + */ + +#include "llgamecontroltranslator.h" +#include "llsd.h" + + +using ActionToMaskMap = LLGameControlTranslator::ActionToMaskMap; + +LLGameControlTranslator::LLGameControlTranslator() +{ +} + +void LLGameControlTranslator::setAvailableActionMasks(ActionToMaskMap& action_to_mask) +{ + mActionToMask = std::move(action_to_mask); +} + +LLGameControl::InputChannel LLGameControlTranslator::getChannelByAction(const std::string& action) const +{ + LLGameControl::InputChannel channel; + auto mask_itr = mActionToMask.find(action); + if (mask_itr != mActionToMask.end()) + { + U32 mask = mask_itr->second; + auto channel_itr = mMaskToChannel.find(mask); + if (channel_itr != mMaskToChannel.end()) + { + channel = channel_itr->second; + } + } + else + { + // It is expected that sometimes 'action' lacks the postfix '+' or '-'. + // When it is missing we append '+' and try again. + std::string action_plus = action; + action_plus.append("+"); + mask_itr = mActionToMask.find(action_plus); + if (mask_itr != mActionToMask.end()) + { + U32 mask = mask_itr->second; + auto channel_itr = mMaskToChannel.find(mask); + if (channel_itr != mMaskToChannel.end()) + { + channel = channel_itr->second; + } + } + } + return channel; +} + +void LLGameControlTranslator::setMappings(LLGameControlTranslator::NamedChannels& named_channels) +{ + mMaskToChannel.clear(); + mMappedFlags = 0; + mPrevActiveFlags = 0; + mCachedState.clear(); + + for (const auto& named_channel : named_channels) + { + updateMap(named_channel.first, named_channel.second); + } +} + +void LLGameControlTranslator::updateMap(const std::string& action, const LLGameControl::InputChannel& channel) +{ + // First, get the action name type + LLGameControl::ActionNameType actionNameType = LLGameControl::getActionNameType(action); + if (actionNameType == LLGameControl::ACTION_NAME_UNKNOWN || + actionNameType == LLGameControl::ACTION_NAME_FLYCAM) + { + 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()) + { + updateMapInternal(action + "-", channel); + } + else + { + // 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); + + // 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(action + "-", other_channel); + } + } + else + { + // Simple (single) processing for other action name types + updateMapInternal(action, channel); + } + + // recompute mMappedFlags + mMappedFlags = 0; + for (auto& pair : mMaskToChannel) + { + mMappedFlags |= pair.first; + } + mPrevActiveFlags = 0; + mCachedState.clear(); +} + +// Given external action_flags (i.e. raw avatar input) +// compute the corresponding LLGameControl::State that would have produced those flags. +// Note: "action flags" are similar to, but not quite the same as, "control flags". +// "Action flags" are the raw input of avatar movement intent, whereas "control flags" +// are the consequential set of instructions that are sent to the server for moving +// the avatar character. +const LLGameControl::State& LLGameControlTranslator::computeStateFromFlags(U32 action_flags) +{ + // translate action_flag bits to equivalent game controller state + // according to data in mMaskToChannel + + // only bother to update mCachedState if active_flags have changed + U32 active_flags = action_flags & mMappedFlags; + if (active_flags != mPrevActiveFlags) + { + mCachedState.clear(); + for (const auto& pair : mMaskToChannel) + { + U32 mask = pair.first; + if (mask == (mask & action_flags)) + { + LLGameControl::InputChannel channel = pair.second; + if (channel.isAxis()) + { + if (channel.mSign < 0) + { + mCachedState.mAxes[channel.mIndex] = std::numeric_limits<S16>::min(); + } + else + { + mCachedState.mAxes[channel.mIndex] = std::numeric_limits<S16>::max(); + } + } + else if (channel.isButton()) + { + mCachedState.mButtons |= 0x01U << channel.mIndex; + } + } + } + mPrevActiveFlags = active_flags; + } + return mCachedState; +} + +// Given LLGameControl::State (i.e. from a real controller) +// compute corresponding action flags (e.g. for moving the avatar around) +U32 LLGameControlTranslator::computeFlagsFromState(const std::vector<S32>& axes, U32 buttons) +{ + // HACK: supply hard-coded threshold for ON/OFF zones + constexpr S32 AXIS_THRESHOLD = 32768 / 8; + U32 action_flags = 0; + for (const auto& pair : mMaskToChannel) + { + // pair = { mask, channel } + const LLGameControl::InputChannel& channel = pair.second; + if (channel.isAxis()) + { + if (channel.mSign < 0) + { + if (axes[channel.mIndex] < -AXIS_THRESHOLD) + { + action_flags |= pair.first; + } + } + else if (axes[channel.mIndex] > AXIS_THRESHOLD) + { + action_flags |= pair.first; + } + } + else if (channel.isButton()) + { + U32 bit_set = buttons & (0x01U << channel.mIndex); + if (bit_set) + { + action_flags |= pair.first; + } + } + } + return action_flags; +} + +void LLGameControlTranslator::updateMapInternal(const std::string& name, const LLGameControl::InputChannel& channel) +{ + 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()) + { + // create new mapping + mMaskToChannel[mask] = channel; + } + else if (channel.isNone()) + { + // remove old mapping + mMaskToChannel.erase(channel_it); + } + else + { + // update old mapping + channel_it->second = channel; + } +} + diff --git a/indra/llwindow/llgamecontroltranslator.h b/indra/llwindow/llgamecontroltranslator.h new file mode 100644 index 0000000000..533408014c --- /dev/null +++ b/indra/llwindow/llgamecontroltranslator.h @@ -0,0 +1,93 @@ +/** + * @file llgamecontroltranslator.h + * @brief LLGameControlTranslator class definition + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#pragma once + +#include <map> + +#include "stdtypes.h" +#include "llgamecontrol.h" + + +// GameControl data is sent to the server to expose game controller input to LSL scripts, +// however not everyone will have a game controller device. To allow keyboard users to provide +// GameControl data we allow the User to configure equivalences between avatar actions +// (i.e. "push forward", "strafe left", etc) and keyboard buttons to GameControl axes +// and buttons. +// +// The LLGameControlTranslator stores the equivalences and translates avatar action_flags +// and keyboard state into GameContrl data, and in some cases the other direction: from +// LLGameControl::State into avatar action_flags. +// + +class LLGameControlTranslator +{ +public: + + using ActionToMaskMap = std::map< std::string, U32 >; // < action : mask > + using MaskToChannelMap = std::map< U32, LLGameControl::InputChannel >; // < mask : channel > + using NamedChannel = std::pair < std::string , LLGameControl::InputChannel >; + using NamedChannels = std::vector< NamedChannel >; + + + LLGameControlTranslator(); + void setAvailableActionMasks(ActionToMaskMap& action_to_mask); + LLGameControl::InputChannel getChannelByAction(const std::string& action) const; + 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) + // compute the corresponding LLGameControl::State that would have produced those flags. + // Note: "action_flags" are similar to, but not quite the same as, "control_flags". + const LLGameControl::State& computeStateFromFlags(U32 action_flags); + + // Given LLGameControl::State (i.e. from a real controller) + // compute corresponding action flags (e.g. for moving the avatar around) + U32 computeFlagsFromState(const std::vector<S32>& axes, U32 buttons); + + U32 getMappedFlags() const { return mMappedFlags; } + +private: + void updateMapInternal(const std::string& name, const LLGameControl::InputChannel& channel); + +private: + // mActionToMask is an invarient map between the possible actions + // and the action bit masks. Only actions therein can have their + // bit masks mapped to channels. + ActionToMaskMap mActionToMask; // invariant map after init + + // mMaskToChannel is a dynamic map between action bit masks + // and GameControl channels. + MaskToChannelMap mMaskToChannel; // dynamic map, per preference changes + + // mCachedState is an optimization: + // it is only recomputed when external action_flags change + LLGameControl::State mCachedState; + + U32 mMappedFlags { 0 }; + U32 mPrevActiveFlags { 0 }; +}; diff --git a/indra/llwindow/llkeyboard.cpp b/indra/llwindow/llkeyboard.cpp index a16c0a318a..cb0c312a1d 100644 --- a/indra/llwindow/llkeyboard.cpp +++ b/indra/llwindow/llkeyboard.cpp @@ -161,6 +161,7 @@ void LLKeyboard::resetKeyDownAndHandle() mCallbacks->handleTranslatedKeyUp(i, mask); } } + mCurTranslatedKey = KEY_NONE; } // BUG this has to be called when an OS dialog is shown, otherwise modifier key state @@ -226,7 +227,7 @@ LLKeyboard::NATIVE_KEY_TYPE LLKeyboard::inverseTranslateKey(const KEY translated } -bool LLKeyboard::handleTranslatedKeyDown(KEY translated_key, U32 translated_mask) +bool LLKeyboard::handleTranslatedKeyDown(KEY translated_key, MASK translated_mask) { bool handled = false; bool repeated = false; @@ -254,7 +255,7 @@ bool LLKeyboard::handleTranslatedKeyDown(KEY translated_key, U32 translated_mask } -bool LLKeyboard::handleTranslatedKeyUp(KEY translated_key, U32 translated_mask) +bool LLKeyboard::handleTranslatedKeyUp(KEY translated_key, MASK translated_mask) { bool handled = false; if( mKeyLevel[translated_key] ) @@ -276,6 +277,32 @@ bool LLKeyboard::handleTranslatedKeyUp(KEY translated_key, U32 translated_mask) } +bool LLKeyboard::handleKeyDown(const NATIVE_KEY_TYPE key, const MASK mask) +{ + MASK translated_mask = updateModifiers(mask); + KEY translated_key = 0; + bool handled = false; + if(translateKey(key, &translated_key)) + { + handled = handleTranslatedKeyDown(translated_key, translated_mask); + } + return handled; +} + + +bool LLKeyboard::handleKeyUp(const NATIVE_KEY_TYPE key, const MASK mask) +{ + MASK translated_mask = updateModifiers(mask); + KEY translated_key = 0; + bool handled = false; + if(translateKey(key, &translated_key)) + { + handled = handleTranslatedKeyUp(translated_key, translated_mask); + } + return handled; +} + + void LLKeyboard::toggleInsertMode() { if (LL_KIM_INSERT == mInsertMode) diff --git a/indra/llwindow/llkeyboard.h b/indra/llwindow/llkeyboard.h index d3c35b1ed4..c092f55685 100644 --- a/indra/llwindow/llkeyboard.h +++ b/indra/llwindow/llkeyboard.h @@ -55,10 +55,12 @@ class LLWindowCallbacks; class LLKeyboard { public: -#ifndef LL_SDL - typedef U16 NATIVE_KEY_TYPE; -#else +#ifdef LL_USE_SDL_KEYBOARD + // linux relies on SDL2 which uses U32 for its native key type typedef U32 NATIVE_KEY_TYPE; +#else + // on non-linux platforms we can get by with a smaller native key type + typedef U16 NATIVE_KEY_TYPE; #endif LLKeyboard(); virtual ~LLKeyboard(); @@ -73,17 +75,14 @@ public: bool getKeyRepeated(const KEY key) { return mKeyRepeated[key]; } bool translateKey(const NATIVE_KEY_TYPE os_key, KEY *translated_key); - NATIVE_KEY_TYPE inverseTranslateKey(const KEY translated_key); - bool handleTranslatedKeyUp(KEY translated_key, U32 translated_mask); // Translated into "Linden" keycodes - bool handleTranslatedKeyDown(KEY translated_key, U32 translated_mask); // Translated into "Linden" keycodes + NATIVE_KEY_TYPE inverseTranslateKey(const KEY translated_key); + bool handleTranslatedKeyUp(KEY translated_key, MASK translated_mask); // Translated into "Linden" keycodes + bool handleTranslatedKeyDown(KEY translated_key, MASK translated_mask); // Translated into "Linden" keycodes 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 +112,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/llkeyboardheadless.cpp b/indra/llwindow/llkeyboardheadless.cpp index ad8e42a412..0ca8c09f42 100644 --- a/indra/llwindow/llkeyboardheadless.cpp +++ b/indra/llwindow/llkeyboardheadless.cpp @@ -31,6 +31,16 @@ LLKeyboardHeadless::LLKeyboardHeadless() { } +bool LLKeyboardHeadless::handleKeyUp(const LLKeyboard::NATIVE_KEY_TYPE key, MASK mask) +{ + return false; +} + +bool LLKeyboardHeadless::handleKeyDown(const LLKeyboard::NATIVE_KEY_TYPE key, MASK mask) +{ + return false; +} + void LLKeyboardHeadless::resetMaskKeys() { } @@ -57,6 +67,7 @@ void LLKeyboardHeadless::scanKeyboard() mCallbacks->handleScanKey(key, mKeyDown[key], mKeyUp[key], mKeyLevel[key]); } } + mCurScanKey = KEY_NONE; // Reset edges for next frame for (S32 key = 0; key < KEY_COUNT; key++) diff --git a/indra/llwindow/llkeyboardheadless.h b/indra/llwindow/llkeyboardheadless.h index 439abaf25b..60c4d61e1c 100644 --- a/indra/llwindow/llkeyboardheadless.h +++ b/indra/llwindow/llkeyboardheadless.h @@ -33,20 +33,15 @@ class LLKeyboardHeadless : public LLKeyboard { public: LLKeyboardHeadless(); - /*virtual*/ ~LLKeyboardHeadless() {}; + ~LLKeyboardHeadless() {}; -#ifndef LL_SDL - /*virtual*/ bool handleKeyUp(const U16 key, MASK mask) { return false; } - /*virtual*/ bool handleKeyDown(const U16 key, MASK mask) { return false; } -#else - /*virtual*/ bool handleKeyUp(const U32 key, MASK mask) { return false; } - /*virtual*/ bool handleKeyDown(const U32 key, MASK mask) { return false; } -#endif - /*virtual*/ void resetMaskKeys(); - /*virtual*/ MASK currentMask(bool for_mouse_event); - /*virtual*/ void scanKeyboard(); + bool handleKeyUp(const LLKeyboard::NATIVE_KEY_TYPE key, MASK mask) override; + bool handleKeyDown(const LLKeyboard::NATIVE_KEY_TYPE key, MASK mask) override; + void resetMaskKeys() override; + MASK currentMask(bool for_mouse_event) override; + void scanKeyboard() override; #ifdef LL_DARWIN - /*virtual*/ void handleModifier(MASK mask); + void handleModifier(MASK mask) override; #endif }; diff --git a/indra/llwindow/llkeyboardmacosx.cpp b/indra/llwindow/llkeyboardmacosx.cpp index 89ff7c6d3f..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(const U32 mask) +MASK LLKeyboardMacOSX::updateModifiers(const MASK mask) { // translate the mask MASK out_mask = 0; @@ -226,7 +226,7 @@ MASK LLKeyboardMacOSX::updateModifiers(const 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; @@ -291,6 +291,7 @@ void LLKeyboardMacOSX::scanKeyboard() mCallbacks->handleScanKey(key, mKeyDown[key], mKeyUp[key], mKeyLevel[key]); } } + mCurScanKey = KEY_NONE; // Reset edges for next frame for (key = 0; key < KEY_COUNT; key++) diff --git a/indra/llwindow/llkeyboardmacosx.h b/indra/llwindow/llkeyboardmacosx.h index 92ab5c9a85..a5f59f3fba 100644 --- a/indra/llwindow/llkeyboardmacosx.h +++ b/indra/llwindow/llkeyboardmacosx.h @@ -42,17 +42,17 @@ class LLKeyboardMacOSX : public LLKeyboard { public: LLKeyboardMacOSX(); - /*virtual*/ ~LLKeyboardMacOSX() {}; + ~LLKeyboardMacOSX() {}; - /*virtual*/ bool handleKeyUp(const U16 key, MASK mask); - /*virtual*/ bool handleKeyDown(const U16 key, MASK mask); - /*virtual*/ void resetMaskKeys(); - /*virtual*/ MASK currentMask(bool for_mouse_event); - /*virtual*/ void scanKeyboard(); - /*virtual*/ void handleModifier(MASK mask); + bool handleKeyUp(const U16 key, MASK mask) override; + bool handleKeyDown(const U16 key, MASK mask) override; + void resetMaskKeys() override; + MASK currentMask(bool for_mouse_event) override; + void scanKeyboard() override; + 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 543882fc8f..b6666195a6 100644 --- a/indra/llwindow/llkeyboardsdl.cpp +++ b/indra/llwindow/llkeyboardsdl.cpp @@ -26,7 +26,7 @@ #include "linden_common.h" #include "llkeyboardsdl.h" #include "llwindowcallbacks.h" -#include "SDL2/SDL.h" + #include "SDL2/SDL_keycode.h" LLKeyboardSDL::LLKeyboardSDL() @@ -42,7 +42,7 @@ LLKeyboardSDL::LLKeyboardSDL() // <FS:ND> Looks like we need to map those despite of SDL_TEXTINPUT handling most of this, but without // the translation lower->upper here accelerators will not work. - U16 cur_char; + LLKeyboard::NATIVE_KEY_TYPE cur_char; for (cur_char = 'A'; cur_char <= 'Z'; cur_char++) { mTranslateKeyMap[cur_char] = cur_char; @@ -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) +U32 adjustNativekeyFromUnhandledMask(const LLKeyboard::NATIVE_KEY_TYPE key, const MASK mask) { // SDL doesn't automatically adjust the keysym according to // whether NUMLOCK is engaged, so we massage the keysym manually. @@ -226,11 +226,11 @@ static U32 adjustNativekeyFromUnhandledMask(const U32 key, const U32 mask) } -bool LLKeyboardSDL::handleKeyDown(const U32 key, const U32 mask) +bool LLKeyboardSDL::handleKeyDown(const LLKeyboard::NATIVE_KEY_TYPE key, const MASK mask) { U32 adjusted_nativekey; KEY translated_key = 0; - U32 translated_mask = MASK_NONE; + MASK translated_mask = MASK_NONE; bool handled = false; adjusted_nativekey = adjustNativekeyFromUnhandledMask(key, mask); @@ -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 LLKeyboard::NATIVE_KEY_TYPE key, const MASK mask) { U32 adjusted_nativekey; KEY translated_key = 0; @@ -300,6 +300,7 @@ void LLKeyboardSDL::scanKeyboard() mCallbacks->handleScanKey(key, mKeyDown[key], mKeyUp[key], mKeyLevel[key]); } } + mCurScanKey = KEY_NONE; // Reset edges for next frame for (S32 key = 0; key < KEY_COUNT; key++) @@ -314,12 +315,12 @@ void LLKeyboardSDL::scanKeyboard() } -bool LLKeyboardSDL::translateNumpadKey( const U32 os_key, KEY *translated_key) +bool LLKeyboardSDL::translateNumpadKey( const LLKeyboard::NATIVE_KEY_TYPE os_key, KEY *translated_key) { return translateKey(os_key, translated_key); } -U16 LLKeyboardSDL::inverseTranslateNumpadKey(const KEY translated_key) +LLKeyboard::NATIVE_KEY_TYPE LLKeyboardSDL::inverseTranslateNumpadKey(const KEY translated_key) { return inverseTranslateKey(translated_key); } @@ -497,7 +498,6 @@ enum class WindowsVK : U32 }; std::map< U32, U32 > mSDL2_to_Win; -std::set< U32 > mIgnoreSDL2Keys; U32 LLKeyboardSDL::mapSDL2toWin( U32 aSymbol ) { diff --git a/indra/llwindow/llkeyboardsdl.h b/indra/llwindow/llkeyboardsdl.h index 9ebff66865..7671e4c859 100644 --- a/indra/llwindow/llkeyboardsdl.h +++ b/indra/llwindow/llkeyboardsdl.h @@ -33,22 +33,22 @@ class LLKeyboardSDL : public LLKeyboard { public: LLKeyboardSDL(); - /*virtual*/ ~LLKeyboardSDL() {}; + ~LLKeyboardSDL() {}; - /*virtual*/ bool handleKeyUp(const U32 key, MASK mask); - /*virtual*/ bool handleKeyDown(const U32 key, MASK mask); - /*virtual*/ void resetMaskKeys(); - /*virtual*/ MASK currentMask(bool for_mouse_event); - /*virtual*/ void scanKeyboard(); + bool handleKeyUp(const LLKeyboard::NATIVE_KEY_TYPE key, MASK mask) override; + bool handleKeyDown(const LLKeyboard::NATIVE_KEY_TYPE key, MASK mask) override; + void resetMaskKeys() override; + MASK currentMask(bool for_mouse_event) override; + void scanKeyboard() override; 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); + bool translateNumpadKey( const LLKeyboard::NATIVE_KEY_TYPE os_key, KEY *translated_key ); + LLKeyboard::NATIVE_KEY_TYPE inverseTranslateNumpadKey(const KEY translated_key); private: - std::map<U32, KEY> mTranslateNumpadMap; // special map for translating OS keys to numpad keys - std::map<KEY, U32> mInvTranslateNumpadMap; // inverse of the above + std::map<LLKeyboard::NATIVE_KEY_TYPE, KEY> mTranslateNumpadMap; // special map for translating OS keys to numpad keys + std::map<KEY, LLKeyboard::NATIVE_KEY_TYPE> mInvTranslateNumpadMap; // inverse of the above public: static U32 mapSDL2toWin( U32 ); diff --git a/indra/llwindow/llkeyboardwin32.cpp b/indra/llwindow/llkeyboardwin32.cpp index 8d6b8d9b93..c31ef5c9a3 100644 --- a/indra/llwindow/llkeyboardwin32.cpp +++ b/indra/llwindow/llkeyboardwin32.cpp @@ -182,7 +182,7 @@ void LLKeyboardWin32::resetMaskKeys() //} -MASK LLKeyboardWin32::updateModifiers() +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() // (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)) { @@ -259,6 +258,7 @@ void LLKeyboardWin32::scanKeyboard() mCallbacks->handleScanKey(key, mKeyDown[key], mKeyUp[key], mKeyLevel[key]); } } + mCurScanKey = KEY_NONE; // Reset edges for next frame for (key = 0; key < KEY_COUNT; key++) @@ -321,4 +321,4 @@ U16 LLKeyboardWin32::inverseTranslateExtendedKey(const KEY translated_key) return inverseTranslateKey(converted_key); } -#endif +#endif // LL_WINDOWS diff --git a/indra/llwindow/llkeyboardwin32.h b/indra/llwindow/llkeyboardwin32.h index d0dfc5cfdd..ea7bb4d866 100644 --- a/indra/llwindow/llkeyboardwin32.h +++ b/indra/llwindow/llkeyboardwin32.h @@ -37,18 +37,19 @@ class LLKeyboardWin32 : public LLKeyboard { public: LLKeyboardWin32(); - /*virtual*/ ~LLKeyboardWin32() {}; + ~LLKeyboardWin32() {}; - /*virtual*/ bool handleKeyUp(const U16 key, MASK mask); - /*virtual*/ bool handleKeyDown(const U16 key, MASK mask); - /*virtual*/ void resetMaskKeys(); - /*virtual*/ MASK currentMask(bool for_mouse_event); - /*virtual*/ void scanKeyboard(); - bool translateExtendedKey(const U16 os_key, const MASK mask, KEY *translated_key); - U16 inverseTranslateExtendedKey(const KEY translated_key); + bool handleKeyUp(const U16 key, MASK mask) override; + bool handleKeyDown(const U16 key, MASK mask) override; + void resetMaskKeys() override; + MASK currentMask(bool for_mouse_event) override; + void scanKeyboard() override; + + bool translateExtendedKey(const U16 os_key, const MASK mask, KEY *translated_key); + 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/llsdl.cpp b/indra/llwindow/llsdl.cpp new file mode 100644 index 0000000000..6161bd2972 --- /dev/null +++ b/indra/llwindow/llsdl.cpp @@ -0,0 +1,102 @@ +/** + * @file llsdl.cpp + * @brief SDL2 initialization + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include <initializer_list> +#include <list> + +#include "SDL2/SDL.h" + +#include "llerror.h" +#include "llwindow.h" + +void sdl_logger(void *userdata, int category, SDL_LogPriority priority, const char *message) +{ + LL_DEBUGS("SDL2") << "log='" << message << "'" << LL_ENDL; +} + +void init_sdl() +{ + SDL_version c_sdl_version; + SDL_VERSION(&c_sdl_version); + LL_INFOS() << "Compiled against SDL " + << int(c_sdl_version.major) << "." + << int(c_sdl_version.minor) << "." + << int(c_sdl_version.patch) << LL_ENDL; + SDL_version r_sdl_version; + SDL_GetVersion(&r_sdl_version); + LL_INFOS() << "Running with SDL " + << int(r_sdl_version.major) << "." + << int(r_sdl_version.minor) << "." + << int(r_sdl_version.patch) << LL_ENDL; +#ifdef LL_LINUX + // For linux we SDL_INIT_VIDEO and _AUDIO + std::initializer_list<std::tuple< char const*, char const * > > hintList = + { + {SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR,"0"}, + {SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH,"1"}, + {SDL_HINT_IME_INTERNAL_EDITING,"1"} + }; + + for (auto hint: hintList) + { + SDL_SetHint(std::get<0>(hint), std::get<1>(hint)); + } + + std::initializer_list<std::tuple<uint32_t, char const*, bool>> initList= + { {SDL_INIT_VIDEO,"SDL_INIT_VIDEO", true}, + {SDL_INIT_AUDIO,"SDL_INIT_AUDIO", false}, + }; +#else + // For non-linux platforms we still SDL_INIT_VIDEO because it is a pre-requisite + // for SDL_INIT_GAMECONTROLLER. + std::initializer_list<std::tuple<uint32_t, char const*, bool>> initList= + { {SDL_INIT_VIDEO,"SDL_INIT_VIDEO", false}, + }; +#endif // LL_LINUX + // We SDL_INIT_GAMECONTROLLER later in the startup process to make it + // more likely we'll catch initial SDL_CONTROLLERDEVICEADDED events. + + for (auto subSystem : initList) + { + if (SDL_InitSubSystem(std::get<0>(subSystem)) < 0) + { + LL_WARNS() << "SDL_InitSubSystem for " << std::get<1>(subSystem) << " failed " << SDL_GetError() << LL_ENDL; + + if (std::get<2>(subSystem)) + { + OSMessageBox("SDL_Init() failure", "error", OSMB_OK); + return; + } + } + } + + SDL_LogSetOutputFunction(&sdl_logger, nullptr); +} + +void quit_sdl() +{ + SDL_Quit(); +} diff --git a/indra/llwindow/llsdl.h b/indra/llwindow/llsdl.h new file mode 100644 index 0000000000..9fc8de129c --- /dev/null +++ b/indra/llwindow/llsdl.h @@ -0,0 +1,30 @@ +/** + * @file llsdl.h + * @brief SDL2 initialization + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#pragma once + +void init_sdl(); +void quit_sdl(); diff --git a/indra/llwindow/llwindow.cpp b/indra/llwindow/llwindow.cpp index 378e633cd2..93ac58ca6f 100644 --- a/indra/llwindow/llwindow.cpp +++ b/indra/llwindow/llwindow.cpp @@ -27,18 +27,19 @@ #include "linden_common.h" #include "llwindowheadless.h" -#if LL_MESA_HEADLESS -#include "llwindowmesaheadless.h" -#elif LL_SDL -#include "llwindowsdl.h" -#elif LL_WINDOWS +#if LL_WINDOWS #include "llwindowwin32.h" #elif LL_DARWIN #include "llwindowmacosx.h" +#elif LL_MESA_HEADLESS +#include "llwindowmesaheadless.h" +#elif LL_LINUX +#include "llwindowsdl.h" #endif #include "llerror.h" #include "llkeyboard.h" +#include "llsdl.h" #include "llwindowcallbacks.h" @@ -72,13 +73,13 @@ S32 OSMessageBox(const std::string& text, const std::string& caption, U32 type) S32 result = 0; LL_WARNS() << "OSMessageBox: " << text << LL_ENDL; -#if LL_MESA_HEADLESS // !!! *FIX: (?) - return OSBTN_OK; -#elif LL_WINDOWS +#if LL_WINDOWS result = OSMessageBoxWin32(text, caption, type); #elif LL_DARWIN result = OSMessageBoxMacOSX(text, caption, type); -#elif LL_SDL +#elif LL_MESA_HEADLESS // !!! *FIX: (?) + return OSBTN_OK; +#elif LL_LINUX result = OSMessageBoxSDL(text, caption, type); #else #error("OSMessageBox not implemented for this platform!") @@ -263,7 +264,7 @@ std::vector<std::string> LLWindow::getDynamicFallbackFontList() return LLWindowWin32::getDynamicFallbackFontList(); #elif LL_DARWIN return LLWindowMacOSX::getDynamicFallbackFontList(); -#elif LL_SDL +#elif LL_LINUX return LLWindowSDL::getDynamicFallbackFontList(); #else return std::vector<std::string>(); @@ -342,12 +343,12 @@ bool LLSplashScreen::isVisible() // static LLSplashScreen *LLSplashScreen::create() { -#if LL_MESA_HEADLESS || LL_SDL // !!! *FIX: (?) - return 0; -#elif LL_WINDOWS +#if LL_WINDOWS return new LLSplashScreenWin32; #elif LL_DARWIN return new LLSplashScreenMacOSX; +#elif LL_MESA_HEADLESS || LL_LINUX // !!! *FIX: (?) + return 0; #else #error("LLSplashScreen not implemented on this platform!") #endif @@ -415,15 +416,8 @@ LLWindow* LLWindowManager::createWindow( if (use_gl) { -#if LL_MESA_HEADLESS - new_window = new LLWindowMesaHeadless(callbacks, - title, name, x, y, width, height, flags, - fullscreen, clearBg, enable_vsync, use_gl, ignore_pixel_depth); -#elif LL_SDL - new_window = new LLWindowSDL(callbacks, - title, x, y, width, height, flags, - fullscreen, clearBg, enable_vsync, use_gl, ignore_pixel_depth, fsaa_samples); -#elif LL_WINDOWS + init_sdl(); +#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_gl_version); @@ -431,6 +425,14 @@ LLWindow* LLWindowManager::createWindow( new_window = new LLWindowMacOSX(callbacks, title, name, x, y, width, height, flags, fullscreen, clearBg, enable_vsync, use_gl, ignore_pixel_depth, fsaa_samples); +#elif LL_MESA_HEADLESS + new_window = new LLWindowMesaHeadless(callbacks, + title, name, x, y, width, height, flags, + fullscreen, clearBg, enable_vsync, use_gl, ignore_pixel_depth); +#elif LL_LINUX + new_window = new LLWindowSDL(callbacks, + title, name, x, y, width, height, flags, + fullscreen, clearBg, enable_vsync, use_gl, ignore_pixel_depth, fsaa_samples); #endif } else @@ -462,6 +464,7 @@ bool LLWindowManager::destroyWindow(LLWindow* window) window->close(); sWindowList.erase(window); + quit_sdl(); delete window; diff --git a/indra/llwindow/llwindow.h b/indra/llwindow/llwindow.h index fcc4fd863a..e74142c7df 100644 --- a/indra/llwindow/llwindow.h +++ b/indra/llwindow/llwindow.h @@ -24,8 +24,7 @@ * $/LicenseInfo$ */ -#ifndef LL_LLWINDOW_H -#define LL_LLWINDOW_H +#pragma once #include "llrect.h" #include "llcoord.h" @@ -33,6 +32,7 @@ #include "llcursortypes.h" #include "llinstancetracker.h" #include "llsd.h" +#include "llsdl.h" class LLSplashScreen; class LLPreeditor; @@ -63,16 +63,16 @@ public: virtual void show() = 0; virtual void hide() = 0; virtual void close() = 0; - virtual bool getVisible() = 0; - virtual bool getMinimized() = 0; - virtual bool getMaximized() = 0; + virtual bool getVisible() const = 0; + virtual bool getMinimized() const = 0; + virtual bool getMaximized() const = 0; virtual bool maximize() = 0; virtual void minimize() = 0; virtual void restore() = 0; - bool getFullscreen() { return mFullscreen; }; - virtual bool getPosition(LLCoordScreen *position) = 0; - virtual bool getSize(LLCoordScreen *size) = 0; - virtual bool getSize(LLCoordWindow *size) = 0; + virtual bool getFullscreen() const { return mFullscreen; }; + virtual bool getPosition(LLCoordScreen *position) const = 0; + virtual bool getSize(LLCoordScreen *size) const = 0; + virtual bool getSize(LLCoordWindow *size) const = 0; virtual bool setPosition(LLCoordScreen position) = 0; bool setSize(LLCoordScreen size); bool setSize(LLCoordWindow size); @@ -93,7 +93,7 @@ public: virtual bool setCursorPosition(LLCoordWindow position) = 0; virtual bool getCursorPosition(LLCoordWindow *position) = 0; #if LL_WINDOWS - virtual bool getCursorDelta(LLCoordCommon* delta) = 0; + virtual bool getCursorDelta(LLCoordCommon* delta) const = 0; #endif virtual void showCursor() = 0; virtual void hideCursor() = 0; @@ -135,14 +135,14 @@ public: virtual bool copyTextToPrimary(const LLWString &src); virtual void flashIcon(F32 seconds) = 0; - virtual F32 getGamma() = 0; + virtual F32 getGamma() const = 0; virtual bool setGamma(const F32 gamma) = 0; // Set the gamma virtual void setFSAASamples(const U32 fsaa_samples) = 0; //set number of FSAA samples - virtual U32 getFSAASamples() = 0; + virtual U32 getFSAASamples() const = 0; virtual bool restoreGamma() = 0; // Restore original gamma table (before updating gamma) - virtual ESwapMethod getSwapMethod() { return mSwapMethod; } + ESwapMethod getSwapMethod() { return mSwapMethod; } virtual void processMiscNativeEvents(); - virtual void gatherInput() = 0; + virtual void gatherInput(bool app_has_focus) = 0; virtual void delayInputProcessing() = 0; virtual void swapBuffers() = 0; virtual void bringToFront() = 0; @@ -151,12 +151,12 @@ public: // handy coordinate space conversion routines // NB: screen to window and vice verse won't work on width/height coordinate pairs, // as the conversion must take into account left AND right border widths, etc. - virtual bool convertCoords( LLCoordScreen from, LLCoordWindow *to) = 0; - virtual bool convertCoords( LLCoordWindow from, LLCoordScreen *to) = 0; - virtual bool convertCoords( LLCoordWindow from, LLCoordGL *to) = 0; - virtual bool convertCoords( LLCoordGL from, LLCoordWindow *to) = 0; - virtual bool convertCoords( LLCoordScreen from, LLCoordGL *to) = 0; - virtual bool convertCoords( LLCoordGL from, LLCoordScreen *to) = 0; + virtual bool convertCoords( LLCoordScreen from, LLCoordWindow *to) const = 0; + virtual bool convertCoords( LLCoordWindow from, LLCoordScreen *to) const = 0; + virtual bool convertCoords( LLCoordWindow from, LLCoordGL *to) const = 0; + virtual bool convertCoords( LLCoordGL from, LLCoordWindow *to) const = 0; + virtual bool convertCoords( LLCoordScreen from, LLCoordGL *to) const = 0; + virtual bool convertCoords( LLCoordGL from, LLCoordScreen *to) const = 0; // query supported resolutions virtual LLWindowResolution* getSupportedResolutions(S32 &num_resolutions) = 0; @@ -189,7 +189,7 @@ public: static std::vector<std::string> getDynamicFallbackFontList(); // Provide native key event data - virtual LLSD getNativeKeyData() { return LLSD::emptyMap(); } + virtual LLSD getNativeKeyData() const { return LLSD::emptyMap(); } // Get system UI size based on DPI (for 96 DPI UI size should be 1.0) virtual F32 getSystemUISize() { return 1.0; } @@ -206,7 +206,7 @@ public: return false; }; - virtual S32 getRefreshRate() { return mRefreshRate; } + virtual S32 getRefreshRate() const { return mRefreshRate; } protected: LLWindow(LLWindowCallbacks* callbacks, bool fullscreen, U32 flags); virtual ~LLWindow(); @@ -328,4 +328,3 @@ extern const S32 gURLProtocolWhitelistCount; extern const std::string gURLProtocolWhitelist[]; //extern const std::string gURLProtocolWhitelistHandler[]; -#endif // _LL_window_h_ diff --git a/indra/llwindow/llwindowheadless.h b/indra/llwindow/llwindowheadless.h index 5696b69a59..96654b8838 100644 --- a/indra/llwindow/llwindowheadless.h +++ b/indra/llwindow/llwindowheadless.h @@ -32,77 +32,70 @@ class LLWindowHeadless : public LLWindow { public: - /*virtual*/ void show() override {} - /*virtual*/ void hide() override {} - /*virtual*/ void close() override {} - /*virtual*/ bool getVisible() override {return false;} - /*virtual*/ bool getMinimized() override {return false;} - /*virtual*/ bool getMaximized() override {return false;} - /*virtual*/ bool maximize() override {return false;} - /*virtual*/ void minimize() override {} - /*virtual*/ void restore() override {} - // TODO: LLWindow::getFullscreen() is (intentionally?) NOT virtual. - // Apparently the coder of LLWindowHeadless didn't realize that. Is it a - // mistake to shadow the base-class method with an LLWindowHeadless - // override when called on the subclass, yet call the base-class method - // when indirecting through a polymorphic pointer or reference? - bool getFullscreen() {return false;} - /*virtual*/ bool getPosition(LLCoordScreen *position) override {return false;} - /*virtual*/ bool getSize(LLCoordScreen *size) override {return false;} - /*virtual*/ bool getSize(LLCoordWindow *size) override {return false;} - /*virtual*/ bool setPosition(LLCoordScreen position) override {return false;} - /*virtual*/ bool setSizeImpl(LLCoordScreen size) override {return false;} - /*virtual*/ bool setSizeImpl(LLCoordWindow size) override {return false;} - /*virtual*/ bool switchContext(bool fullscreen, const LLCoordScreen &size, bool enable_vsync, const LLCoordScreen * const posp = NULL) override {return false;} + void show() override {} + void hide() override {} + void close() override {} + bool getVisible() const override {return false;} + bool getMinimized() const override {return false;} + bool getMaximized() const override {return false;} + bool maximize() override {return false;} + void minimize() override {} + void restore() override {} + bool getFullscreen() const override {return false;}; + bool getPosition(LLCoordScreen *position) const override {return false;} + bool getSize(LLCoordScreen *size) const override {return false;} + bool getSize(LLCoordWindow *size) const override {return false;} + bool setPosition(LLCoordScreen position) override {return false;} + bool setSizeImpl(LLCoordScreen size) override {return false;} + bool setSizeImpl(LLCoordWindow size) override {return false;} + bool switchContext(bool fullscreen, const LLCoordScreen &size, bool enable_vsync, const LLCoordScreen * const posp = NULL) override {return false;} void* createSharedContext() override { return nullptr; } void makeContextCurrent(void*) override {} void destroySharedContext(void*) override {} - /*virtual*/ void toggleVSync(bool enable_vsync) override { } - /*virtual*/ bool setCursorPosition(LLCoordWindow position) override {return false;} - /*virtual*/ bool getCursorPosition(LLCoordWindow *position) override {return false;} + void toggleVSync(bool enable_vsync) override { } + bool setCursorPosition(LLCoordWindow position) override {return false;} + bool getCursorPosition(LLCoordWindow *position) override {return false;} #if LL_WINDOWS - /*virtual*/ bool getCursorDelta(LLCoordCommon* delta) override { return false; } + bool getCursorDelta(LLCoordCommon* delta) const override { return false; } #endif - /*virtual*/ void showCursor() override {} - /*virtual*/ void hideCursor() override {} - /*virtual*/ void showCursorFromMouseMove() override {} - /*virtual*/ void hideCursorUntilMouseMove() override {} - /*virtual*/ bool isCursorHidden() override {return false;} - /*virtual*/ void updateCursor() override {} - //virtual ECursorType getCursor() override { return mCurrentCursor; } - /*virtual*/ void captureMouse() override {} - /*virtual*/ void releaseMouse() override {} - /*virtual*/ void setMouseClipping( bool b ) override {} - /*virtual*/ bool isClipboardTextAvailable() override {return false; } - /*virtual*/ bool pasteTextFromClipboard(LLWString &dst) override {return false; } - /*virtual*/ bool copyTextToClipboard(const LLWString &src) override {return false; } - /*virtual*/ void flashIcon(F32 seconds) override {} - /*virtual*/ F32 getGamma() override {return 1.0f; } - /*virtual*/ bool setGamma(const F32 gamma) override {return false; } // Set the gamma - /*virtual*/ void setFSAASamples(const U32 fsaa_samples) override { } - /*virtual*/ U32 getFSAASamples() override { return 0; } - /*virtual*/ bool restoreGamma() override {return false; } // Restore original gamma table (before updating gamma) - //virtual ESwapMethod getSwapMethod() override { return mSwapMethod; } - /*virtual*/ void gatherInput() override {} - /*virtual*/ void delayInputProcessing() override {} - /*virtual*/ void swapBuffers() override; + void showCursor() override {} + void hideCursor() override {} + void showCursorFromMouseMove() override {} + void hideCursorUntilMouseMove() override {} + bool isCursorHidden() override {return false;} + void updateCursor() override {} + void captureMouse() override {} + void releaseMouse() override {} + void setMouseClipping( bool b ) override {} + bool isClipboardTextAvailable() override {return false; } + bool pasteTextFromClipboard(LLWString &dst) override {return false; } + bool copyTextToClipboard(const LLWString &src) override {return false; } + void flashIcon(F32 seconds) override {} + F32 getGamma() const override {return 1.0f; } + bool setGamma(const F32 gamma) override {return false; } // Set the gamma + void setFSAASamples(const U32 fsaa_samples) override { } + U32 getFSAASamples() const override { return 0; } + bool restoreGamma() override {return false; } // Restore original gamma table (before updating gamma) + void gatherInput(bool app_has_focus) override {} + void delayInputProcessing() override {} + void swapBuffers() override; // handy coordinate space conversion routines - /*virtual*/ bool convertCoords(LLCoordScreen from, LLCoordWindow *to) override { return false; } - /*virtual*/ bool convertCoords(LLCoordWindow from, LLCoordScreen *to) override { return false; } - /*virtual*/ bool convertCoords(LLCoordWindow from, LLCoordGL *to) override { return false; } - /*virtual*/ bool convertCoords(LLCoordGL from, LLCoordWindow *to) override { return false; } - /*virtual*/ bool convertCoords(LLCoordScreen from, LLCoordGL *to) override { return false; } - /*virtual*/ bool convertCoords(LLCoordGL from, LLCoordScreen *to) override { return false; } + bool convertCoords(LLCoordScreen from, LLCoordWindow *to) const override { return false; } + bool convertCoords(LLCoordWindow from, LLCoordScreen *to) const override { return false; } + bool convertCoords(LLCoordWindow from, LLCoordGL *to) const override { return false; } + bool convertCoords(LLCoordGL from, LLCoordWindow *to) const override { return false; } + bool convertCoords(LLCoordScreen from, LLCoordGL *to) const override { return false; } + bool convertCoords(LLCoordGL from, LLCoordScreen *to) const override { return false; } - /*virtual*/ LLWindowResolution* getSupportedResolutions(S32 &num_resolutions) override { return NULL; } - /*virtual*/ F32 getNativeAspectRatio() override { return 1.0f; } - /*virtual*/ F32 getPixelAspectRatio() override { return 1.0f; } - /*virtual*/ void setNativeAspectRatio(F32 ratio) override {} + LLWindowResolution* getSupportedResolutions(S32 &num_resolutions) override { return NULL; } + F32 getNativeAspectRatio() override { return 1.0f; } + F32 getPixelAspectRatio() override { return 1.0f; } + void setNativeAspectRatio(F32 ratio) override {} - /*virtual*/ void *getPlatformWindow() override { return 0; } - /*virtual*/ void bringToFront() override {} + void *getPlatformWindow() override { return 0; } + void bringToFront() override {} LLWindowHeadless(LLWindowCallbacks* callbacks, const std::string& title, const std::string& name, diff --git a/indra/llwindow/llwindowmacosx.cpp b/indra/llwindow/llwindowmacosx.cpp index e95ad4d970..1883c6c9c1 100644 --- a/indra/llwindow/llwindowmacosx.cpp +++ b/indra/llwindow/llwindowmacosx.cpp @@ -28,6 +28,7 @@ #include "llwindowmacosx.h" +#include "llgamecontrol.h" #include "llkeyboardmacosx.h" #include "llwindowcallbacks.h" #include "llpreeditor.h" @@ -625,7 +626,7 @@ void LLWindowMacOSX::updateMouseDeltas(float* deltas) } } -void LLWindowMacOSX::getMouseDeltas(float* delta) +void LLWindowMacOSX::getMouseDeltas(float* delta) const { delta[0] = mCursorLastEventDeltaX; delta[1] = mCursorLastEventDeltaY; @@ -845,7 +846,7 @@ bool LLWindowMacOSX::isValid() return (mWindow != NULL); } -bool LLWindowMacOSX::getVisible() +bool LLWindowMacOSX::getVisible() const { bool result = false; @@ -860,12 +861,12 @@ bool LLWindowMacOSX::getVisible() return(result); } -bool LLWindowMacOSX::getMinimized() +bool LLWindowMacOSX::getMinimized() const { return mMinimized; } -bool LLWindowMacOSX::getMaximized() +bool LLWindowMacOSX::getMaximized() const { return mMaximized; } @@ -879,17 +880,13 @@ bool LLWindowMacOSX::maximize() return mMaximized; } -bool LLWindowMacOSX::getFullscreen() -{ - return mFullscreen; -} - -void LLWindowMacOSX::gatherInput() +void LLWindowMacOSX::gatherInput(bool app_has_focus) { updateCursor(); + LLGameControl::processEvents(app_has_focus); } -bool LLWindowMacOSX::getPosition(LLCoordScreen *position) +bool LLWindowMacOSX::getPosition(LLCoordScreen *position) const { S32 err = -1; @@ -916,7 +913,7 @@ bool LLWindowMacOSX::getPosition(LLCoordScreen *position) return (err == noErr); } -bool LLWindowMacOSX::getSize(LLCoordScreen *size) +bool LLWindowMacOSX::getSize(LLCoordScreen *size) const { S32 err = -1; @@ -942,7 +939,7 @@ bool LLWindowMacOSX::getSize(LLCoordScreen *size) return (err == noErr); } -bool LLWindowMacOSX::getSize(LLCoordWindow *size) +bool LLWindowMacOSX::getSize(LLCoordWindow *size) const { S32 err = -1; @@ -1016,7 +1013,7 @@ void LLWindowMacOSX::restoreGLContext() CGLSetCurrentContext(mContext); } -F32 LLWindowMacOSX::getGamma() +F32 LLWindowMacOSX::getGamma() const { F32 result = 2.2; // Default to something sane @@ -1050,7 +1047,7 @@ F32 LLWindowMacOSX::getGamma() return result; } -U32 LLWindowMacOSX::getFSAASamples() +U32 LLWindowMacOSX::getFSAASamples() const { return mFSAASamples; } @@ -1376,21 +1373,21 @@ LLWindow::LLWindowResolution* LLWindowMacOSX::getSupportedResolutions(S32 &num_r return mSupportedResolutions; } -bool LLWindowMacOSX::convertCoords(LLCoordGL from, LLCoordWindow *to) +bool LLWindowMacOSX::convertCoords(LLCoordGL from, LLCoordWindow *to) const { to->mX = from.mX; to->mY = from.mY; return true; } -bool LLWindowMacOSX::convertCoords(LLCoordWindow from, LLCoordGL* to) +bool LLWindowMacOSX::convertCoords(LLCoordWindow from, LLCoordGL* to) const { to->mX = from.mX; to->mY = from.mY; return true; } -bool LLWindowMacOSX::convertCoords(LLCoordScreen from, LLCoordWindow* to) +bool LLWindowMacOSX::convertCoords(LLCoordScreen from, LLCoordWindow* to) const { if(mWindow) { @@ -1409,7 +1406,7 @@ bool LLWindowMacOSX::convertCoords(LLCoordScreen from, LLCoordWindow* to) return false; } -bool LLWindowMacOSX::convertCoords(LLCoordWindow from, LLCoordScreen *to) +bool LLWindowMacOSX::convertCoords(LLCoordWindow from, LLCoordScreen *to) const { if(mWindow) { @@ -1428,14 +1425,14 @@ bool LLWindowMacOSX::convertCoords(LLCoordWindow from, LLCoordScreen *to) return false; } -bool LLWindowMacOSX::convertCoords(LLCoordScreen from, LLCoordGL *to) +bool LLWindowMacOSX::convertCoords(LLCoordScreen from, LLCoordGL *to) const { LLCoordWindow window_coord; return(convertCoords(from, &window_coord) && convertCoords(window_coord, to)); } -bool LLWindowMacOSX::convertCoords(LLCoordGL from, LLCoordScreen *to) +bool LLWindowMacOSX::convertCoords(LLCoordGL from, LLCoordScreen *to) const { LLCoordWindow window_coord; @@ -2321,7 +2318,7 @@ bool LLWindowMacOSX::getInputDevices(U32 device_type_filter, return return_value; } -LLSD LLWindowMacOSX::getNativeKeyData() +LLSD LLWindowMacOSX::getNativeKeyData() const { LLSD result = LLSD::emptyMap(); @@ -2505,6 +2502,7 @@ void LLWindowMacOSX::interruptLanguageTextInput() commitCurrentPreedit(mGLView); } +// static std::vector<std::string> LLWindowMacOSX::getDisplaysResolutionList() { std::vector<std::string> resolution_list; @@ -2534,7 +2532,7 @@ std::vector<std::string> LLWindowMacOSX::getDisplaysResolutionList() return resolution_list; } -//static +// static std::vector<std::string> LLWindowMacOSX::getDynamicFallbackFontList() { // Fonts previously in getFontListSans() have moved to fonts.xml. diff --git a/indra/llwindow/llwindowmacosx.h b/indra/llwindow/llwindowmacosx.h index 211ae872c6..14a56a038e 100644 --- a/indra/llwindow/llwindowmacosx.h +++ b/indra/llwindow/llwindowmacosx.h @@ -47,16 +47,15 @@ public: void show() override; void hide() override; void close() override; - bool getVisible() override; - bool getMinimized() override; - bool getMaximized() override; + bool getVisible() const override; + bool getMinimized() const override; + bool getMaximized() const override; bool maximize() override; void minimize() override; void restore() override; - bool getFullscreen(); - bool getPosition(LLCoordScreen *position) override; - bool getSize(LLCoordScreen *size) override; - bool getSize(LLCoordWindow *size) override; + bool getPosition(LLCoordScreen *position) const override; + bool getSize(LLCoordScreen *size) const override; + bool getSize(LLCoordWindow *size) const override; bool setPosition(LLCoordScreen position) override; bool setSizeImpl(LLCoordScreen size) override; bool setSizeImpl(LLCoordWindow size) override; @@ -77,23 +76,22 @@ public: bool pasteTextFromClipboard(LLWString &dst) override; bool copyTextToClipboard(const LLWString & src) override; void flashIcon(F32 seconds) override; - F32 getGamma() override; + F32 getGamma() const override; bool setGamma(const F32 gamma) override; // Set the gamma - U32 getFSAASamples() override; + U32 getFSAASamples() const override; void setFSAASamples(const U32 fsaa_samples) override; bool restoreGamma() override; // Restore original gamma table (before updating gamma) - ESwapMethod getSwapMethod() override { return mSwapMethod; } - void gatherInput() override; + void gatherInput(bool app_has_focus) override; void delayInputProcessing() override {}; void swapBuffers() override; // handy coordinate space conversion routines - bool convertCoords(LLCoordScreen from, LLCoordWindow *to) override; - bool convertCoords(LLCoordWindow from, LLCoordScreen *to) override; - bool convertCoords(LLCoordWindow from, LLCoordGL *to) override; - bool convertCoords(LLCoordGL from, LLCoordWindow *to) override; - bool convertCoords(LLCoordScreen from, LLCoordGL *to) override; - bool convertCoords(LLCoordGL from, LLCoordScreen *to) override; + bool convertCoords(LLCoordScreen from, LLCoordWindow *to) const override; + bool convertCoords(LLCoordWindow from, LLCoordScreen *to) const override; + bool convertCoords(LLCoordWindow from, LLCoordGL *to) const override; + bool convertCoords(LLCoordGL from, LLCoordWindow *to) const override; + bool convertCoords(LLCoordScreen from, LLCoordGL *to) const override; + bool convertCoords(LLCoordGL from, LLCoordScreen *to) const override; LLWindowResolution* getSupportedResolutions(S32 &num_resolutions) override; F32 getNativeAspectRatio() override; @@ -125,14 +123,14 @@ public: static std::vector<std::string> getDynamicFallbackFontList(); // Provide native key event data - LLSD getNativeKeyData() override; + LLSD getNativeKeyData() const override; void* getWindow() { return mWindow; } LLWindowCallbacks* getCallbacks() { return mCallbacks; } LLPreeditor* getPreeditor() { return mPreeditor; } void updateMouseDeltas(float* deltas); - void getMouseDeltas(float* delta); + void getMouseDeltas(float* delta) const; void handleDragNDrop(std::string url, LLWindowCallbacks::DragNDropAction action); diff --git a/indra/llwindow/llwindowmesaheadless.h b/indra/llwindow/llwindowmesaheadless.h index 0bf8c46a30..0a29ddfa5b 100644 --- a/indra/llwindow/llwindowmesaheadless.h +++ b/indra/llwindow/llwindowmesaheadless.h @@ -36,64 +36,64 @@ class LLWindowMesaHeadless : public LLWindow { public: - /*virtual*/ void show() {}; - /*virtual*/ void hide() {}; - /*virtual*/ void close() {}; - /*virtual*/ bool getVisible() {return false;}; - /*virtual*/ bool getMinimized() {return false;}; - /*virtual*/ bool getMaximized() {return false;}; - /*virtual*/ bool maximize() {return false;}; - /*virtual*/ void minimize() {}; - /*virtual*/ void restore() {}; - /*virtual*/ bool getFullscreen() {return false;}; - /*virtual*/ bool getPosition(LLCoordScreen *position) {return false;}; - /*virtual*/ bool getSize(LLCoordScreen *size) {return false;}; - /*virtual*/ bool getSize(LLCoordWindow *size) {return false;}; - /*virtual*/ bool setPosition(LLCoordScreen position) {return false;}; - /*virtual*/ bool setSizeImpl(LLCoordScreen size) {return false;}; - /*virtual*/ bool switchContext(bool fullscreen, const LLCoordScreen &size, bool disable_vsync, const LLCoordScreen * const posp = NULL) {return false;}; - /*virtual*/ bool setCursorPosition(LLCoordWindow position) {return false;}; - /*virtual*/ bool getCursorPosition(LLCoordWindow *position) {return false;}; - /*virtual*/ void showCursor() {}; - /*virtual*/ void hideCursor() {}; - /*virtual*/ void showCursorFromMouseMove() {}; - /*virtual*/ void hideCursorUntilMouseMove() {}; - /*virtual*/ bool isCursorHidden() {return false;}; - /*virtual*/ void updateCursor() {}; - //virtual ECursorType getCursor() { return mCurrentCursor; }; - /*virtual*/ void captureMouse() {}; - /*virtual*/ void releaseMouse() {}; - /*virtual*/ void setMouseClipping( bool b ) {}; - /*virtual*/ bool isClipboardTextAvailable() {return false; }; - /*virtual*/ bool pasteTextFromClipboard(LLWString &dst) {return false; }; - /*virtual*/ bool copyTextToClipboard(const LLWString &src) {return false; }; - /*virtual*/ void flashIcon(F32 seconds) {}; - /*virtual*/ F32 getGamma() {return 1.0f; }; - /*virtual*/ bool setGamma(const F32 gamma) {return false; }; // Set the gamma - /*virtual*/ bool restoreGamma() {return false; }; // Restore original gamma table (before updating gamma) - /*virtual*/ void setFSAASamples(const U32 fsaa_samples) { /* FSAA not supported yet on Mesa headless.*/ } - /*virtual*/ U32 getFSAASamples() { return 0; } - //virtual ESwapMethod getSwapMethod() { return mSwapMethod; } - /*virtual*/ void gatherInput() {}; - /*virtual*/ void delayInputProcessing() {}; - /*virtual*/ void swapBuffers(); - /*virtual*/ void restoreGLContext() {}; + void show() override {}; + void hide() override {}; + void close() override {}; + bool getVisible() override {return false;}; + bool getMinimized() override {return false;}; + bool getMaximized() override {return false;}; + bool maximize() override {return false;}; + void minimize() override {}; + void restore() override {}; + bool getFullscreen() override {return false;}; + bool getPosition(LLCoordScreen *position) override {return false;}; + bool getSize(LLCoordScreen *size) override {return false;}; + bool getSize(LLCoordWindow *size) override {return false;}; + bool setPosition(LLCoordScreen position) override {return false;}; + bool setSizeImpl(LLCoordScreen size) override {return false;}; + bool switchContext(bool fullscreen, const LLCoordScreen &size, bool disable_vsync, const LLCoordScreen * const posp = NULL) override {return false;}; + bool setCursorPosition(LLCoordWindow position) override {return false;}; + bool getCursorPosition(LLCoordWindow *position) override {return false;}; + void showCursor() override {}; + void hideCursor() override {}; + void showCursorFromMouseMove() override {}; + void hideCursorUntilMouseMove() override {}; + bool isCursorHidden() override {return false;}; + void updateCursor() override {}; + //ECursorType getCursor() override { return mCurrentCursor; }; + void captureMouse() override {}; + void releaseMouse() override {}; + void setMouseClipping( bool b ) override {}; + bool isClipboardTextAvailable() override {return false; }; + bool pasteTextFromClipboard(LLWString &dst) override {return false; }; + bool copyTextToClipboard(const LLWString &src) override {return false; }; + void flashIcon(F32 seconds) override {}; + F32 getGamma() override {return 1.0f; }; + bool setGamma(const F32 gamma) override {return false; }; // Set the gamma + bool restoreGamma() override {return false; }; // Restore original gamma table (before updating gamma) + void setFSAASamples(const U32 fsaa_samples) override { /* FSAA not supported yet on Mesa headless.*/ } + U32 getFSAASamples() override { return 0; } + //ESwapMethod getSwapMethod() override { return mSwapMethod; } + void gatherInput(bool app_has_focus) override {}; + void delayInputProcessing() override {}; + void swapBuffers() override; + void restoreGLContext() override {}; // handy coordinate space conversion routines - /*virtual*/ bool convertCoords(LLCoordScreen from, LLCoordWindow *to) { return false; }; - /*virtual*/ bool convertCoords(LLCoordWindow from, LLCoordScreen *to) { return false; }; - /*virtual*/ bool convertCoords(LLCoordWindow from, LLCoordGL *to) { return false; }; - /*virtual*/ bool convertCoords(LLCoordGL from, LLCoordWindow *to) { return false; }; - /*virtual*/ bool convertCoords(LLCoordScreen from, LLCoordGL *to) { return false; }; - /*virtual*/ bool convertCoords(LLCoordGL from, LLCoordScreen *to) { return false; }; + bool convertCoords(LLCoordScreen from, LLCoordWindow *to) override { return false; }; + bool convertCoords(LLCoordWindow from, LLCoordScreen *to) override { return false; }; + bool convertCoords(LLCoordWindow from, LLCoordGL *to) override { return false; }; + bool convertCoords(LLCoordGL from, LLCoordWindow *to) override { return false; }; + bool convertCoords(LLCoordScreen from, LLCoordGL *to) override { return false; }; + bool convertCoords(LLCoordGL from, LLCoordScreen *to) override { return false; }; - /*virtual*/ LLWindowResolution* getSupportedResolutions(S32 &num_resolutions) { return NULL; }; - /*virtual*/ F32 getNativeAspectRatio() { return 1.0f; }; - /*virtual*/ F32 getPixelAspectRatio() { return 1.0f; }; - /*virtual*/ void setNativeAspectRatio(F32 ratio) {} + LLWindowResolution* getSupportedResolutions(S32 &num_resolutions) override { return NULL; }; + F32 getNativeAspectRatio() override { return 1.0f; }; + F32 getPixelAspectRatio() override { return 1.0f; }; + void setNativeAspectRatio(F32 ratio) override {} - /*virtual*/ void *getPlatformWindow() { return 0; }; - /*virtual*/ void bringToFront() {}; + void *getPlatformWindow() override { return 0; }; + void bringToFront() override {}; LLWindowMesaHeadless(LLWindowCallbacks* callbacks, const std::string& title, const std::string& name, S32 x, S32 y, S32 width, S32 height, @@ -112,9 +112,9 @@ public: LLSplashScreenMesaHeadless() {}; virtual ~LLSplashScreenMesaHeadless() {}; - /*virtual*/ void showImpl() {}; - /*virtual*/ void updateImpl(const std::string& mesg) {}; - /*virtual*/ void hideImpl() {}; + void showImpl() override {}; + void updateImpl(const std::string& mesg) override {}; + void hideImpl() override {}; }; diff --git a/indra/llwindow/llwindowsdl.cpp b/indra/llwindow/llwindowsdl.cpp index 89f0d152c6..ea95a5aa2e 100644 --- a/indra/llwindow/llwindowsdl.cpp +++ b/indra/llwindow/llwindowsdl.cpp @@ -37,6 +37,7 @@ #include "llstring.h" #include "lldir.h" #include "llfindlocale.h" +#include "llgamecontrol.h" #ifdef LL_GLIB #include <glib.h> @@ -59,9 +60,6 @@ extern bool gDebugWindowProc; const S32 MAX_NUM_RESOLUTIONS = 200; -// static variable for ATI mouse cursor crash work-around: -static bool ATIbug = false; - // // LLWindowSDL // @@ -113,250 +111,8 @@ Display* LLWindowSDL::get_SDL_Display(void) } #endif // LL_X11 -#if LL_X11 - -// Clipboard handing via native X11, base on the implementation in Cool VL by Henri Beauchamp - -namespace -{ - std::array<Atom, 3> gSupportedAtoms; - - Atom XA_CLIPBOARD; - Atom XA_TARGETS; - Atom PVT_PASTE_BUFFER; - long const MAX_PASTE_BUFFER_SIZE = 16383; - - void filterSelectionRequest( XEvent aEvent ) - { - auto *display = LLWindowSDL::getSDLDisplay(); - auto &request = aEvent.xselectionrequest; - - XSelectionEvent reply { SelectionNotify, aEvent.xany.serial, aEvent.xany.send_event, display, - request.requestor, request.selection, request.target, - request.property,request.time }; - - if (request.target == XA_TARGETS) - { - XChangeProperty(display, request.requestor, request.property, - XA_ATOM, 32, PropModeReplace, - (unsigned char *) &gSupportedAtoms.front(), gSupportedAtoms.size()); - } - else if (std::find(gSupportedAtoms.begin(), gSupportedAtoms.end(), request.target) != - gSupportedAtoms.end()) - { - std::string utf8; - if (request.selection == XA_PRIMARY) - utf8 = wstring_to_utf8str(gWindowImplementation->getPrimaryText()); - else - utf8 = wstring_to_utf8str(gWindowImplementation->getSecondaryText()); - - XChangeProperty(display, request.requestor, request.property, - request.target, 8, PropModeReplace, - (unsigned char *) utf8.c_str(), utf8.length()); - } - else if (request.selection == XA_CLIPBOARD) - { - // Did not have what they wanted, so no property set - reply.property = None; - } - else - return; - - XSendEvent(request.display, request.requestor, False, NoEventMask, (XEvent *) &reply); - XSync(display, False); - } - - void filterSelectionClearRequest( XEvent aEvent ) - { - auto &request = aEvent.xselectionrequest; - if (request.selection == XA_PRIMARY) - gWindowImplementation->clearPrimaryText(); - else if (request.selection == XA_CLIPBOARD) - gWindowImplementation->clearSecondaryText(); - } - - int x11_clipboard_filter(void*, SDL_Event *evt) - { - Display *display = LLWindowSDL::getSDLDisplay(); - if (!display) - return 1; - - if (evt->type != SDL_SYSWMEVENT) - return 1; - - auto xevent = evt->syswm.msg->msg.x11.event; - - if (xevent.type == SelectionRequest) - filterSelectionRequest( xevent ); - else if (xevent.type == SelectionClear) - filterSelectionClearRequest( xevent ); - return 1; - } - - bool grab_property(Display* display, Window window, Atom selection, Atom target) - { - if( !display ) - return false; - - maybe_lock_display(); - - XDeleteProperty(display, window, PVT_PASTE_BUFFER); - XFlush(display); - - XConvertSelection(display, selection, target, PVT_PASTE_BUFFER, window, CurrentTime); - - // Unlock the connection so that the SDL event loop may function - maybe_unlock_display(); - - const auto start{ SDL_GetTicks() }; - const auto end{ start + 1000 }; - - XEvent xevent {}; - bool response = false; - - do - { - SDL_Event event {}; - - // Wait for an event - SDL_WaitEvent(&event); - - // If the event is a window manager event - if (event.type == SDL_SYSWMEVENT) - { - xevent = event.syswm.msg->msg.x11.event; - - if (xevent.type == SelectionNotify && xevent.xselection.requestor == window) - response = true; - } - } while (!response && SDL_GetTicks() < end ); - - return response && xevent.xselection.property != None; - } -} - -void LLWindowSDL::initialiseX11Clipboard() -{ - if (!mSDL_Display) - return; - - SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE); - SDL_SetEventFilter(x11_clipboard_filter, nullptr); - - maybe_lock_display(); - - XA_CLIPBOARD = XInternAtom(mSDL_Display, "CLIPBOARD", False); - - gSupportedAtoms[0] = XInternAtom(mSDL_Display, "UTF8_STRING", False); - gSupportedAtoms[1] = XInternAtom(mSDL_Display, "COMPOUND_TEXT", False); - gSupportedAtoms[2] = XA_STRING; - - // TARGETS atom - XA_TARGETS = XInternAtom(mSDL_Display, "TARGETS", False); - - // SL_PASTE_BUFFER atom - PVT_PASTE_BUFFER = XInternAtom(mSDL_Display, "FS_PASTE_BUFFER", False); - - maybe_unlock_display(); -} - -bool LLWindowSDL::getSelectionText( Atom aSelection, Atom aType, LLWString &text ) -{ - if( !mSDL_Display ) - return false; - - if( !grab_property(mSDL_Display, mSDL_XWindowID, aSelection,aType ) ) - return false; - - maybe_lock_display(); - - Atom type; - int format{}; - unsigned long len{},remaining {}; - unsigned char* data = nullptr; - int res = XGetWindowProperty(mSDL_Display, mSDL_XWindowID, - PVT_PASTE_BUFFER, 0, MAX_PASTE_BUFFER_SIZE, False, - AnyPropertyType, &type, &format, &len, - &remaining, &data); - if (data && len) - { - text = LLWString( - utf8str_to_wstring(reinterpret_cast< char const *>( data ) ) - ); - XFree(data); - } - - maybe_unlock_display(); - return res == Success; -} - -bool LLWindowSDL::getSelectionText(Atom selection, LLWString& text) -{ - if (!mSDL_Display) - return false; - - maybe_lock_display(); - - Window owner = XGetSelectionOwner(mSDL_Display, selection); - if (owner == None) - { - if (selection == XA_PRIMARY) - { - owner = DefaultRootWindow(mSDL_Display); - selection = XA_CUT_BUFFER0; - } - else - { - maybe_unlock_display(); - return false; - } - } - - maybe_unlock_display(); - - for( Atom atom : gSupportedAtoms ) - { - if(getSelectionText(selection, atom, text ) ) - return true; - } - - return false; -} - -bool LLWindowSDL::setSelectionText(Atom selection, const LLWString& text) -{ - maybe_lock_display(); - - if (selection == XA_PRIMARY) - { - std::string utf8 = wstring_to_utf8str(text); - XStoreBytes(mSDL_Display, utf8.c_str(), utf8.length() + 1); - mPrimaryClipboard = text; - } - else - mSecondaryClipboard = text; - - XSetSelectionOwner(mSDL_Display, selection, mSDL_XWindowID, CurrentTime); - - auto owner = XGetSelectionOwner(mSDL_Display, selection); - - maybe_unlock_display(); - - return owner == mSDL_XWindowID; -} - -Display* LLWindowSDL::getSDLDisplay() -{ - if (gWindowImplementation) - return gWindowImplementation->mSDL_Display; - return nullptr; -} - -#endif - - LLWindowSDL::LLWindowSDL(LLWindowCallbacks* callbacks, - const std::string& title, S32 x, S32 y, S32 width, + const std::string& title, const std::string& name, S32 x, S32 y, S32 width, S32 height, U32 flags, bool fullscreen, bool clearBg, bool enable_vsync, bool use_gl, @@ -390,7 +146,7 @@ LLWindowSDL::LLWindowSDL(LLWindowCallbacks* callbacks, mOriginalAspectRatio = 1024.0 / 768.0; if (title.empty()) - mWindowTitle = "SDL Window"; // *FIX: (?) + mWindowTitle = "Second Life"; else mWindowTitle = title; @@ -409,10 +165,7 @@ LLWindowSDL::LLWindowSDL(LLWindowCallbacks* callbacks, // Stash an object pointer for OSMessageBox() gWindowImplementation = this; -#if LL_X11 mFlashing = false; - initialiseX11Clipboard(); -#endif // LL_X11 mKeyVirtualKey = 0; mKeyModifiers = KMOD_NONE; @@ -434,145 +187,6 @@ static SDL_Surface *Load_BMP_Resource(const char *basename) return SDL_LoadBMP(path_buffer); } -#if LL_X11 -// This is an XFree86/XOrg-specific hack for detecting the amount of Video RAM -// on this machine. It works by searching /var/log/var/log/Xorg.?.log or -// /var/log/XFree86.?.log for a ': (VideoRAM ?|Memory): (%d+) kB' regex, where -// '?' is the X11 display number derived from $DISPLAY -static int x11_detect_VRAM_kb_fp(FILE *fp, const char *prefix_str) -{ - const int line_buf_size = 1000; - char line_buf[line_buf_size]; - while (fgets(line_buf, line_buf_size, fp)) - { - //LL_DEBUGS() << "XLOG: " << line_buf << LL_ENDL; - - // Why the ad-hoc parser instead of using a regex? Our - // favourite regex implementation - libboost_regex - is - // quite a heavy and troublesome dependency for the client, so - // it seems a shame to introduce it for such a simple task. - // *FIXME: libboost_regex is a dependency now anyway, so we may - // as well use it instead of this hand-rolled nonsense. - const char *part1_template = prefix_str; - const char part2_template[] = " kB"; - char *part1 = strstr(line_buf, part1_template); - if (part1) // found start of matching line - { - part1 = &part1[strlen(part1_template)]; // -> after - char *part2 = strstr(part1, part2_template); - if (part2) // found end of matching line - { - // now everything between part1 and part2 is - // supposed to be numeric, describing the - // number of kB of Video RAM supported - int rtn = 0; - for (; part1 < part2; ++part1) - { - if (*part1 < '0' || *part1 > '9') - { - // unexpected char, abort parse - rtn = 0; - break; - } - rtn *= 10; - rtn += (*part1) - '0'; - } - if (rtn > 0) - { - // got the kB number. return it now. - return rtn; - } - } - } - } - return 0; // 'could not detect' -} - -static int x11_detect_VRAM_kb() -{ - std::string x_log_location("/var/log/"); - std::string fname; - int rtn = 0; // 'could not detect' - int display_num = 0; - FILE *fp; - char *display_env = getenv("DISPLAY"); // e.g. :0 or :0.0 or :1.0 etc - // parse DISPLAY number so we can go grab the right log file - if (display_env[0] == ':' && - display_env[1] >= '0' && display_env[1] <= '9') - { - display_num = display_env[1] - '0'; - } - - // *TODO: we could be smarter and see which of Xorg/XFree86 has the - // freshest time-stamp. - - // Try Xorg log first - fname = x_log_location; - fname += "Xorg."; - fname += ('0' + display_num); - fname += ".log"; - fp = fopen(fname.c_str(), "r"); - if (fp) - { - LL_INFOS() << "Looking in " << fname - << " for VRAM info..." << LL_ENDL; - rtn = x11_detect_VRAM_kb_fp(fp, ": VideoRAM: "); - fclose(fp); - if (0 == rtn) - { - fp = fopen(fname.c_str(), "r"); - if (fp) - { - rtn = x11_detect_VRAM_kb_fp(fp, ": Video RAM: "); - fclose(fp); - if (0 == rtn) - { - fp = fopen(fname.c_str(), "r"); - if (fp) - { - rtn = x11_detect_VRAM_kb_fp(fp, ": Memory: "); - fclose(fp); - } - } - } - } - } - else - { - LL_INFOS() << "Could not open " << fname - << " - skipped." << LL_ENDL; - // Try old XFree86 log otherwise - fname = x_log_location; - fname += "XFree86."; - fname += ('0' + display_num); - fname += ".log"; - fp = fopen(fname.c_str(), "r"); - if (fp) - { - LL_INFOS() << "Looking in " << fname - << " for VRAM info..." << LL_ENDL; - rtn = x11_detect_VRAM_kb_fp(fp, ": VideoRAM: "); - fclose(fp); - if (0 == rtn) - { - fp = fopen(fname.c_str(), "r"); - if (fp) - { - rtn = x11_detect_VRAM_kb_fp(fp, ": Memory: "); - fclose(fp); - } - } - } - else - { - LL_INFOS() << "Could not open " << fname - << " - skipped." << LL_ENDL; - } - } - return rtn; -} -#endif // LL_X11 - void LLWindowSDL::setTitle(const std::string title) { SDL_SetWindowTitle( mWindow, title.c_str() ); @@ -633,8 +247,10 @@ void LLWindowSDL::tryFindFullscreenSize( int &width, int &height ) bool LLWindowSDL::createContext(int x, int y, int width, int height, int bits, bool fullscreen, bool enable_vsync) { - //bool glneedsinit = false; - + if (width == 0) + width = 1024; + if (height == 0) + width = 768; LL_INFOS() << "createContext, fullscreen=" << fullscreen << " size=" << width << "x" << height << LL_ENDL; @@ -642,54 +258,14 @@ bool LLWindowSDL::createContext(int x, int y, int width, int height, int bits, b mGrabbyKeyFlags = 0; mReallyCapturedCount = 0; - std::initializer_list<std::tuple< char const*, char const * > > hintList = - { - {SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR,"0"}, - {SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH,"1"}, - {SDL_HINT_IME_INTERNAL_EDITING,"1"} - }; - - for( auto hint: hintList ) - { - SDL_SetHint( std::get<0>(hint), std::get<1>(hint)); - } - - std::initializer_list<std::tuple<uint32_t, char const*, bool>> initList= - { {SDL_INIT_VIDEO,"SDL_INIT_VIDEO", true}, - {SDL_INIT_AUDIO,"SDL_INIT_AUDIO", false}, - {SDL_INIT_GAMECONTROLLER,"SDL_INIT_GAMECONTROLLER", false}, - {SDL_INIT_SENSOR,"SDL_INIT_SENSOR", false} - }; - - for( auto subSystem : initList) - { - if( SDL_InitSubSystem( std::get<0>(subSystem) ) < 0 ) - { - LL_WARNS() << "SDL_InitSubSystem for " << std::get<1>(subSystem) << " failed " << SDL_GetError() << LL_ENDL; - - if( std::get<2>(subSystem)) - setupFailure("SDL_Init() failure", "error", OSMB_OK); - - } - } - - SDL_version c_sdl_version; - SDL_VERSION(&c_sdl_version); - LL_INFOS() << "Compiled against SDL " - << int(c_sdl_version.major) << "." - << int(c_sdl_version.minor) << "." - << int(c_sdl_version.patch) << LL_ENDL; - SDL_version r_sdl_version; - SDL_GetVersion(&r_sdl_version); - LL_INFOS() << " Running against SDL " - << int(r_sdl_version.major) << "." - << int(r_sdl_version.minor) << "." - << int(r_sdl_version.patch) << LL_ENDL; - if (width == 0) width = 1024; if (height == 0) width = 768; + if (x == 0) + x = SDL_WINDOWPOS_UNDEFINED; + if (y == 0) + y = SDL_WINDOWPOS_UNDEFINED; mFullscreen = fullscreen; @@ -703,51 +279,54 @@ bool LLWindowSDL::createContext(int x, int y, int width, int height, int bits, b mSDLFlags = sdlflags; + // Setup default backing colors GLint redBits{8}, greenBits{8}, blueBits{8}, alphaBits{8}; + GLint depthBits{24}, stencilBits{8}; - GLint depthBits{(bits <= 16) ? 16 : 24}, stencilBits{8}; - - if (getenv("LL_GL_NO_STENCIL")) - stencilBits = 0; - - SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, alphaBits); SDL_GL_SetAttribute(SDL_GL_RED_SIZE, redBits); SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, greenBits); SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, blueBits); - SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, depthBits ); - - // We need stencil support for a few (minor) things. - if (stencilBits) - SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, stencilBits); - // *FIX: try to toggle vsync here? + SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, alphaBits); + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, depthBits); + SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, stencilBits); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); - if (mFSAASamples > 0) + U32 context_flags = 0; + if (gDebugGL) { - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, mFSAASamples); + context_flags |= SDL_GL_CONTEXT_DEBUG_FLAG; } - + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, context_flags); SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1); - mWindow = SDL_CreateWindow( mWindowTitle.c_str(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, mSDLFlags ); - if( mWindow ) + // Create the window + mWindow = SDL_CreateWindow(mWindowTitle.c_str(), x, y, width, height, mSDLFlags); + if (mWindow == nullptr) { - mContext = SDL_GL_CreateContext( mWindow ); + LL_WARNS() << "Window creation failure. SDL: " << SDL_GetError() << LL_ENDL; + setupFailure("Window creation error", "Error", OSMB_OK); + return false; + } - if( mContext == 0 ) - { - LL_WARNS() << "Cannot create GL context " << SDL_GetError() << LL_ENDL; - setupFailure("GL Context creation error creation error", "Error", OSMB_OK); - return false; - } - // SDL_GL_SetSwapInterval(1); - mSurface = SDL_GetWindowSurface( mWindow ); + // Create the context + mContext = SDL_GL_CreateContext(mWindow); + if(!mContext) + { + LL_WARNS() << "Cannot create GL context " << SDL_GetError() << LL_ENDL; + setupFailure("GL Context creation error", "Error", OSMB_OK); + return false; } + if (SDL_GL_MakeCurrent(mWindow, mContext) != 0) + { + LL_WARNS() << "Failed to make context current. SDL: " << SDL_GetError() << LL_ENDL; + setupFailure("GL Context failed to set current failure", "Error", OSMB_OK); + return false; + } - if( mFullscreen ) + mSurface = SDL_GetWindowSurface(mWindow); + if(mFullscreen) { if (mSurface) { @@ -788,40 +367,6 @@ bool LLWindowSDL::createContext(int x, int y, int width, int height, int bits, b } } - // Set the application icon. - SDL_Surface *bmpsurface; - bmpsurface = Load_BMP_Resource("ll_icon.BMP"); - if (bmpsurface) - { - SDL_SetWindowIcon(mWindow, bmpsurface); - SDL_FreeSurface(bmpsurface); - bmpsurface = NULL; - } - - // Detect video memory size. -# if LL_X11 - gGLManager.mVRAM = x11_detect_VRAM_kb() / 1024; - if (gGLManager.mVRAM != 0) - { - LL_INFOS() << "X11 log-parser detected " << gGLManager.mVRAM << "MB VRAM." << LL_ENDL; - } else -# endif // LL_X11 - { - // fallback to letting SDL detect VRAM. - // note: I've not seen SDL's detection ever actually find - // VRAM != 0, but if SDL *does* detect it then that's a bonus. - gGLManager.mVRAM = 0; - if (gGLManager.mVRAM != 0) - { - LL_INFOS() << "SDL detected " << gGLManager.mVRAM << "MB VRAM." << LL_ENDL; - } - } - // If VRAM is not detected, that is handled later - - // *TODO: Now would be an appropriate time to check for some - // explicitly unsupported cards. - //const char* RENDERER = (const char*) glGetString(GL_RENDERER); - SDL_GL_GetAttribute(SDL_GL_RED_SIZE, &redBits); SDL_GL_GetAttribute(SDL_GL_GREEN_SIZE, &greenBits); SDL_GL_GetAttribute(SDL_GL_BLUE_SIZE, &blueBits); @@ -855,6 +400,20 @@ bool LLWindowSDL::createContext(int x, int y, int width, int height, int bits, b return false; } + LL_PROFILER_GPU_CONTEXT; + + // Enable vertical sync + toggleVSync(enable_vsync); + + // Set the application icon. + SDL_Surface* bmpsurface = Load_BMP_Resource("ll_icon.BMP"); + if (bmpsurface) + { + SDL_SetWindowIcon(mWindow, bmpsurface); + SDL_FreeSurface(bmpsurface); + bmpsurface = NULL; + } + #if LL_X11 /* Grab the window manager specific information */ SDL_SysWMinfo info; @@ -880,7 +439,6 @@ bool LLWindowSDL::createContext(int x, int y, int width, int height, int bits, b } #endif // LL_X11 - SDL_StartTextInput(); //make sure multisampling is disabled by default glDisable(GL_MULTISAMPLE_ARB); @@ -889,6 +447,43 @@ bool LLWindowSDL::createContext(int x, int y, int width, int height, int bits, b return true; } +void* LLWindowSDL::createSharedContext() +{ + SDL_GLContext pContext = SDL_GL_CreateContext(mWindow); + if (pContext) + { + LL_DEBUGS() << "Creating shared OpenGL context successful!" << LL_ENDL; + return (void*)pContext; + } + + LL_WARNS() << "Creating shared OpenGL context failed!" << LL_ENDL; + return nullptr; +} + +void LLWindowSDL::makeContextCurrent(void* contextPtr) +{ + SDL_GL_MakeCurrent(mWindow, contextPtr); + LL_PROFILER_GPU_CONTEXT; +} + +void LLWindowSDL::destroySharedContext(void* contextPtr) +{ + SDL_GL_DeleteContext(contextPtr); +} + +void LLWindowSDL::toggleVSync(bool enable_vsync) +{ + if (!enable_vsync) + { + LL_INFOS("Window") << "Disabling vertical sync" << LL_ENDL; + SDL_GL_SetSwapInterval(0); + } + else + { + LL_INFOS("Window") << "Enabling vertical sync" << LL_ENDL; + SDL_GL_SetSwapInterval(1); + } +} // changing fullscreen resolution, or switching between windowed and fullscreen mode. bool LLWindowSDL::switchContext(bool fullscreen, const LLCoordScreen &size, bool enable_vsync, const LLCoordScreen * const posp) @@ -901,7 +496,7 @@ bool LLWindowSDL::switchContext(bool fullscreen, const LLCoordScreen &size, bool if(needsRebuild) { destroyContext(); - result = createContext(0, 0, size.mX, size.mY, 0, fullscreen, enable_vsync); + result = createContext(0, 0, size.mX, size.mY, 32, fullscreen, enable_vsync); if (result) { gGLManager.initGL(); @@ -921,7 +516,13 @@ void LLWindowSDL::destroyContext() { LL_INFOS() << "destroyContext begins" << LL_ENDL; + // Stop unicode input SDL_StopTextInput(); + + // Clean up remaining GL state before blowing away window + LL_INFOS() << "shutdownGL begins" << LL_ENDL; + gGLManager.shutdownGL(); + #if LL_X11 mSDL_Display = NULL; mSDL_XWindowID = None; @@ -929,18 +530,38 @@ void LLWindowSDL::destroyContext() Unlock_Display = NULL; #endif // LL_X11 - // Clean up remaining GL state before blowing away window - LL_INFOS() << "shutdownGL begins" << LL_ENDL; - gGLManager.shutdownGL(); + LL_INFOS() << "Destroying SDL cursors" << LL_ENDL; + quitCursors(); + + if (mContext) + { + LL_INFOS() << "Destroying SDL GL Context" << LL_ENDL; + SDL_GL_DeleteContext(mContext); + mContext = nullptr; + } + else + { + LL_INFOS() << "SDL GL Context already destroyed" << LL_ENDL; + } + + if (mWindow) + { + LL_INFOS() << "Destroying SDL Window" << LL_ENDL; + SDL_DestroyWindow(mWindow); + mWindow = nullptr; + } + else + { + LL_INFOS() << "SDL Window already destroyed" << LL_ENDL; + } + LL_INFOS() << "destroyContext end" << LL_ENDL; + LL_INFOS() << "SDL_QuitSS/VID begins" << LL_ENDL; SDL_QuitSubSystem(SDL_INIT_VIDEO); // *FIX: this might be risky... - - mWindow = NULL; } LLWindowSDL::~LLWindowSDL() { - quitCursors(); destroyContext(); if(mSupportedResolutions != NULL) @@ -954,27 +575,38 @@ LLWindowSDL::~LLWindowSDL() void LLWindowSDL::show() { - // *FIX: What to do with SDL? + if (mWindow) + { + SDL_ShowWindow(mWindow); + } } void LLWindowSDL::hide() { - // *FIX: What to do with SDL? + if (mWindow) + { + SDL_HideWindow(mWindow); + } } //virtual void LLWindowSDL::minimize() { - // *FIX: What to do with SDL? + if (mWindow) + { + SDL_MinimizeWindow(mWindow); + } } //virtual void LLWindowSDL::restore() { - // *FIX: What to do with SDL? + if (mWindow) + { + SDL_RestoreWindow(mWindow); + } } - // close() destroys all OS-specific code associated with a window. // Usually called from LLWindowManager::destroyWindow() void LLWindowSDL::close() @@ -997,63 +629,70 @@ bool LLWindowSDL::isValid() return (mWindow != NULL); } -bool LLWindowSDL::getVisible() +bool LLWindowSDL::getVisible() const { bool result = false; - - // *FIX: This isn't really right... - // Then what is? if (mWindow) { - result = true; + Uint32 flags = SDL_GetWindowFlags(mWindow); + if (flags & SDL_WINDOW_SHOWN) + { + result = true; + } } - - return(result); + return result; } -bool LLWindowSDL::getMinimized() +bool LLWindowSDL::getMinimized() const { bool result = false; - - if (mWindow && (1 == mIsMinimized)) + if (mWindow) { - result = true; + Uint32 flags = SDL_GetWindowFlags(mWindow); + if (flags & SDL_WINDOW_MINIMIZED) + { + result = true; + } } - return(result); + return result; } -bool LLWindowSDL::getMaximized() +bool LLWindowSDL::getMaximized() const { bool result = false; - if (mWindow) { - // TODO + Uint32 flags = SDL_GetWindowFlags(mWindow); + if (flags & SDL_WINDOW_MAXIMIZED) + { + result = true; + } } - return(result); + return result; } bool LLWindowSDL::maximize() { - // TODO + if (mWindow) + { + SDL_MaximizeWindow(mWindow); + return true; + } return false; } -bool LLWindowSDL::getFullscreen() -{ - return mFullscreen; -} - -bool LLWindowSDL::getPosition(LLCoordScreen *position) +bool LLWindowSDL::getPosition(LLCoordScreen *position) const { - // *FIX: can anything be done with this? - position->mX = 0; - position->mY = 0; - return true; + if (mWindow) + { + SDL_GetWindowPosition(mWindow, &position->mX, &position->mY); + return true; + } + return false; } -bool LLWindowSDL::getSize(LLCoordScreen *size) +bool LLWindowSDL::getSize(LLCoordScreen *size) const { if (mSurface) { @@ -1065,7 +704,7 @@ bool LLWindowSDL::getSize(LLCoordScreen *size) return (false); } -bool LLWindowSDL::getSize(LLCoordWindow *size) +bool LLWindowSDL::getSize(LLCoordWindow *size) const { if (mSurface) { @@ -1079,13 +718,13 @@ bool LLWindowSDL::getSize(LLCoordWindow *size) bool LLWindowSDL::setPosition(const LLCoordScreen position) { - if(mWindow) + if (mWindow) { - // *FIX: (?) - //MacMoveWindow(mWindow, position.mX, position.mY, false); + SDL_SetWindowPosition(mWindow, position.mX, position.mY); + return true; } - return true; + return false; } template< typename T > bool setSizeImpl( const T& newSize, SDL_Window *pWin ) @@ -1126,11 +765,12 @@ void LLWindowSDL::swapBuffers() { if (mWindow) { - SDL_GL_SwapWindow( mWindow ); + SDL_GL_SwapWindow(mWindow); } + LL_PROFILER_GPU_COLLECT; } -U32 LLWindowSDL::getFSAASamples() +U32 LLWindowSDL::getFSAASamples() const { return mFSAASamples; } @@ -1140,24 +780,35 @@ void LLWindowSDL::setFSAASamples(const U32 samples) mFSAASamples = samples; } -F32 LLWindowSDL::getGamma() +F32 LLWindowSDL::getGamma() const { - return 1/mGamma; + return 1.f / mGamma; } bool LLWindowSDL::restoreGamma() { - //CGDisplayRestoreColorSyncSettings(); - // SDL_SetGamma(1.0f, 1.0f, 1.0f); + if (mWindow) + { + Uint16 ramp[256]; + SDL_CalculateGammaRamp(1.f, ramp); + SDL_SetWindowGammaRamp(mWindow, ramp, ramp, ramp); + } return true; } bool LLWindowSDL::setGamma(const F32 gamma) { - mGamma = gamma; - if (mGamma == 0) mGamma = 0.1f; - mGamma = 1/mGamma; - // SDL_SetGamma(mGamma, mGamma, mGamma); + if (mWindow) + { + Uint16 ramp[256]; + + mGamma = gamma; + if (mGamma == 0) mGamma = 0.1f; + mGamma = 1.f / mGamma; + + SDL_CalculateGammaRamp(mGamma, ramp); + SDL_SetWindowGammaRamp(mWindow, ramp, ramp, ramp); + } return true; } @@ -1166,10 +817,8 @@ bool LLWindowSDL::isCursorHidden() return mCursorHidden; } - - // Constrains the mouse to the window. -void LLWindowSDL::setMouseClipping( bool b ) +void LLWindowSDL::setMouseClipping(bool b) { //SDL_WM_GrabInput(b ? SDL_GRAB_ON : SDL_GRAB_OFF); } @@ -1179,18 +828,10 @@ void LLWindowSDL::setMinSize(U32 min_width, U32 min_height, bool enforce_immedia { LLWindow::setMinSize(min_width, min_height, enforce_immediately); -#if LL_X11 - // Set the minimum size limits for X11 window - // so the window manager doesn't allow resizing below those limits. - XSizeHints* hints = XAllocSizeHints(); - hints->flags |= PMinSize; - hints->min_width = mMinWindowWidth; - hints->min_height = mMinWindowHeight; - - XSetWMNormalHints(mSDL_Display, mSDL_XWindowID, hints); - - XFree(hints); -#endif + if (mWindow && min_width > 0 && min_height > 0) + { + SDL_SetWindowMinimumSize(mWindow, mMinWindowWidth, mMinWindowHeight); + } } bool LLWindowSDL::setCursorPosition(const LLCoordWindow position) @@ -1218,7 +859,6 @@ bool LLWindowSDL::getCursorPosition(LLCoordWindow *position) //Point cursor_point; LLCoordScreen screen_pos; - //GetMouse(&cursor_point); int x, y; SDL_GetMouseState(&x, &y); @@ -1333,85 +973,70 @@ void LLWindowSDL::afterDialog() } } - -#if LL_X11 -// set/reset the XWMHints flag for 'urgency' that usually makes the icon flash -void LLWindowSDL::x11_set_urgent(bool urgent) -{ - if (mSDL_Display && !mFullscreen) - { - XWMHints *wm_hints; - - LL_INFOS() << "X11 hint for urgency, " << urgent << LL_ENDL; - - maybe_lock_display(); - wm_hints = XGetWMHints(mSDL_Display, mSDL_XWindowID); - if (!wm_hints) - wm_hints = XAllocWMHints(); - - if (urgent) - wm_hints->flags |= XUrgencyHint; - else - wm_hints->flags &= ~XUrgencyHint; - - XSetWMHints(mSDL_Display, mSDL_XWindowID, wm_hints); - XFree(wm_hints); - XSync(mSDL_Display, False); - maybe_unlock_display(); - } -} -#endif // LL_X11 - void LLWindowSDL::flashIcon(F32 seconds) { - if (getMinimized()) - { -#if !LL_X11 - LL_INFOS() << "Stub LLWindowSDL::flashIcon(" << seconds << ")" << LL_ENDL; -#else - LL_INFOS() << "X11 LLWindowSDL::flashIcon(" << seconds << ")" << LL_ENDL; + LL_INFOS() << "LLWindowSDL::flashIcon(" << seconds << ")" << LL_ENDL; - F32 remaining_time = mFlashTimer.getRemainingTimeF32(); - if (remaining_time < seconds) - remaining_time = seconds; - mFlashTimer.reset(); - mFlashTimer.setTimerExpirySec(remaining_time); + F32 remaining_time = mFlashTimer.getRemainingTimeF32(); + if (remaining_time < seconds) + remaining_time = seconds; + mFlashTimer.reset(); + mFlashTimer.setTimerExpirySec(remaining_time); - x11_set_urgent(true); - mFlashing = true; -#endif // LL_X11 - } + SDL_FlashWindow(mWindow, SDL_FLASH_UNTIL_FOCUSED); + mFlashing = true; } bool LLWindowSDL::isClipboardTextAvailable() { - return mSDL_Display && XGetSelectionOwner(mSDL_Display, XA_CLIPBOARD) != None; + return SDL_HasClipboardText() == SDL_TRUE; } bool LLWindowSDL::pasteTextFromClipboard(LLWString &dst) { - return getSelectionText(XA_CLIPBOARD, dst); + if (isClipboardTextAvailable()) + { + char* data = SDL_GetClipboardText(); + if (data) + { + dst = LLWString(utf8str_to_wstring(data)); + SDL_free(data); + return true; + } + } + return false; } -bool LLWindowSDL::copyTextToClipboard(const LLWString &s) +bool LLWindowSDL::copyTextToClipboard(const LLWString& text) { - return setSelectionText(XA_CLIPBOARD, s); + const std::string utf8 = wstring_to_utf8str(text); + return SDL_SetClipboardText(utf8.c_str()) == 0; // success == 0 } bool LLWindowSDL::isPrimaryTextAvailable() { - LLWString text; - return getSelectionText(XA_PRIMARY, text) && !text.empty(); + return SDL_HasPrimarySelectionText() == SDL_TRUE; } bool LLWindowSDL::pasteTextFromPrimary(LLWString &dst) { - return getSelectionText(XA_PRIMARY, dst); + if (isPrimaryTextAvailable()) + { + char* data = SDL_GetPrimarySelectionText(); + if (data) + { + dst = LLWString(utf8str_to_wstring(data)); + SDL_free(data); + return true; + } + } + return false; } -bool LLWindowSDL::copyTextToPrimary(const LLWString &s) +bool LLWindowSDL::copyTextToPrimary(const LLWString& text) { - return setSelectionText(XA_PRIMARY, s); + const std::string utf8 = wstring_to_utf8str(text); + return SDL_SetPrimarySelectionText(utf8.c_str()) == 0; // success == 0 } LLWindow::LLWindowResolution* LLWindowSDL::getSupportedResolutions(S32 &num_resolutions) @@ -1454,7 +1079,7 @@ LLWindow::LLWindowResolution* LLWindowSDL::getSupportedResolutions(S32 &num_reso return mSupportedResolutions; } -bool LLWindowSDL::convertCoords(LLCoordGL from, LLCoordWindow *to) +bool LLWindowSDL::convertCoords(LLCoordGL from, LLCoordWindow *to) const { if (!to) return false; @@ -1465,7 +1090,7 @@ bool LLWindowSDL::convertCoords(LLCoordGL from, LLCoordWindow *to) return true; } -bool LLWindowSDL::convertCoords(LLCoordWindow from, LLCoordGL* to) +bool LLWindowSDL::convertCoords(LLCoordWindow from, LLCoordGL* to) const { if (!to) return false; @@ -1476,7 +1101,7 @@ bool LLWindowSDL::convertCoords(LLCoordWindow from, LLCoordGL* to) return true; } -bool LLWindowSDL::convertCoords(LLCoordScreen from, LLCoordWindow* to) +bool LLWindowSDL::convertCoords(LLCoordScreen from, LLCoordWindow* to) const { if (!to) return false; @@ -1487,7 +1112,7 @@ bool LLWindowSDL::convertCoords(LLCoordScreen from, LLCoordWindow* to) return (true); } -bool LLWindowSDL::convertCoords(LLCoordWindow from, LLCoordScreen *to) +bool LLWindowSDL::convertCoords(LLCoordWindow from, LLCoordScreen *to) const { if (!to) return false; @@ -1498,23 +1123,20 @@ bool LLWindowSDL::convertCoords(LLCoordWindow from, LLCoordScreen *to) return (true); } -bool LLWindowSDL::convertCoords(LLCoordScreen from, LLCoordGL *to) +bool LLWindowSDL::convertCoords(LLCoordScreen from, LLCoordGL *to) const { LLCoordWindow window_coord; return(convertCoords(from, &window_coord) && convertCoords(window_coord, to)); } -bool LLWindowSDL::convertCoords(LLCoordGL from, LLCoordScreen *to) +bool LLWindowSDL::convertCoords(LLCoordGL from, LLCoordScreen *to) const { LLCoordWindow window_coord; return(convertCoords(from, &window_coord) && convertCoords(window_coord, to)); } - - - void LLWindowSDL::setupFailure(const std::string& text, const std::string& caption, U32 type) { destroyContext(); @@ -1549,47 +1171,24 @@ bool LLWindowSDL::SDLReallyCaptureInput(bool capture) bool newGrab = wantGrab; -#if LL_X11 if (!mFullscreen) /* only bother if we're windowed anyway */ { - if (mSDL_Display) + int result; + if (wantGrab == true) { - /* we dirtily mix raw X11 with SDL so that our pointer - isn't (as often) constrained to the limits of the - window while grabbed, which feels nicer and - hopefully eliminates some reported 'sticky pointer' - problems. We use raw X11 instead of - SDL_WM_GrabInput() because the latter constrains - the pointer to the window and also steals all - *keyboard* input from the window manager, which was - frustrating users. */ - int result; - if (wantGrab == true) - { - maybe_lock_display(); - result = XGrabPointer(mSDL_Display, mSDL_XWindowID, - True, 0, GrabModeAsync, - GrabModeAsync, - None, None, CurrentTime); - maybe_unlock_display(); - if (GrabSuccess == result) - newGrab = true; - else - newGrab = false; - } + result = SDL_CaptureMouse(SDL_TRUE); + if (0 == result) + newGrab = true; else - { newGrab = false; - - maybe_lock_display(); - XUngrabPointer(mSDL_Display, CurrentTime); - // Make sure the ungrab happens RIGHT NOW. - XSync(mSDL_Display, False); - maybe_unlock_display(); - } + } + else + { + newGrab = false; + result = SDL_CaptureMouse(SDL_FALSE); } } -#endif // LL_X11 + // return boolean success for whether we ended up in the desired state return capture == newGrab; } @@ -1719,7 +1318,7 @@ void check_vm_bloat() last_rss_size = this_rss_size; last_vm_size = this_vm_size; - finally: +finally: if (NULL != ptr) { free(ptr); @@ -1753,13 +1352,8 @@ void LLWindowSDL::processMiscNativeEvents() } } -void LLWindowSDL::gatherInput() +void LLWindowSDL::gatherInput(bool app_has_focus) { - const Uint32 CLICK_THRESHOLD = 300; // milliseconds - static int leftClick = 0; - static int rightClick = 0; - static Uint32 lastLeftDown = 0; - static Uint32 lastRightDown = 0; SDL_Event event; // Handle all outstanding SDL events @@ -1768,13 +1362,21 @@ void LLWindowSDL::gatherInput() switch (event.type) { case SDL_MOUSEWHEEL: + { if( event.wheel.y != 0 ) + { mCallbacks->handleScrollWheel(this, -event.wheel.y); + } + if (event.wheel.x != 0) + { + mCallbacks->handleScrollHWheel(this, -event.wheel.x); + } break; + } case SDL_MOUSEMOTION: { - LLCoordWindow winCoord(event.button.x, event.button.y); + LLCoordWindow winCoord(event.motion.x, event.motion.y); LLCoordGL openGlCoord; convertCoords(winCoord, &openGlCoord); MASK mask = gKeyboard->currentMask(true); @@ -1847,64 +1449,30 @@ void LLWindowSDL::gatherInput() case SDL_MOUSEBUTTONDOWN: { - bool isDoubleClick = false; LLCoordWindow winCoord(event.button.x, event.button.y); LLCoordGL openGlCoord; convertCoords(winCoord, &openGlCoord); MASK mask = gKeyboard->currentMask(true); - if (event.button.button == SDL_BUTTON_LEFT) // SDL doesn't manage double clicking... - { - Uint32 now = SDL_GetTicks(); - if ((now - lastLeftDown) > CLICK_THRESHOLD) - leftClick = 1; - else - { - if (++leftClick >= 2) - { - leftClick = 0; - isDoubleClick = true; - } - } - lastLeftDown = now; - } - else if (event.button.button == SDL_BUTTON_RIGHT) - { - Uint32 now = SDL_GetTicks(); - if ((now - lastRightDown) > CLICK_THRESHOLD) - rightClick = 1; - else - { - if (++rightClick >= 2) - { - rightClick = 0; - isDoubleClick = true; - } - } - lastRightDown = now; - } - if (event.button.button == SDL_BUTTON_LEFT) // left { - if (isDoubleClick) + if (event.button.clicks >= 2) mCallbacks->handleDoubleClick(this, openGlCoord, mask); else mCallbacks->handleMouseDown(this, openGlCoord, mask); } - else if (event.button.button == SDL_BUTTON_RIGHT) // right { mCallbacks->handleRightMouseDown(this, openGlCoord, mask); } - else if (event.button.button == SDL_BUTTON_MIDDLE) // middle { mCallbacks->handleMiddleMouseDown(this, openGlCoord, mask); } - else if (event.button.button == 4) // mousewheel up...thanks to X11 for making SDL consider these "buttons". - mCallbacks->handleScrollWheel(this, -1); - else if (event.button.button == 5) // mousewheel down...thanks to X11 for making SDL consider these "buttons". - mCallbacks->handleScrollWheel(this, 1); + else + { + mCallbacks->handleOtherMouseDown(this, openGlCoord, mask, event.button.button); + } break; } @@ -1917,64 +1485,65 @@ void LLWindowSDL::gatherInput() MASK mask = gKeyboard->currentMask(true); if (event.button.button == SDL_BUTTON_LEFT) // left + { mCallbacks->handleMouseUp(this, openGlCoord, mask); + } else if (event.button.button == SDL_BUTTON_RIGHT) // right + { mCallbacks->handleRightMouseUp(this, openGlCoord, mask); + } else if (event.button.button == SDL_BUTTON_MIDDLE) // middle + { mCallbacks->handleMiddleMouseUp(this, openGlCoord, mask); - // don't handle mousewheel here... + } + else + { + mCallbacks->handleOtherMouseUp(this, openGlCoord, mask, event.button.button); + } break; } - case SDL_WINDOWEVENT: // *FIX: handle this? + case SDL_WINDOWEVENT: { - if( event.window.event == SDL_WINDOWEVENT_RESIZED - /* || event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED*/ ) // <FS:ND> SDL_WINDOWEVENT_SIZE_CHANGED is followed by SDL_WINDOWEVENT_RESIZED, so handling one shall be enough - { - LL_INFOS() << "Handling a resize event: " << event.window.data1 << "x" << event.window.data2 << LL_ENDL; - - S32 width = llmax(event.window.data1, (S32)mMinWindowWidth); - S32 height = llmax(event.window.data2, (S32)mMinWindowHeight); - mSurface = SDL_GetWindowSurface( mWindow ); - - // *FIX: I'm not sure this is necessary! - // <FS:ND> I think is is not - // SDL_SetWindowSize(mWindow, width, height); - // - - mCallbacks->handleResize(this, width, height); - } - else if( event.window.event == SDL_WINDOWEVENT_FOCUS_GAINED ) // <FS:ND> What about SDL_WINDOWEVENT_ENTER (mouse focus) - { - // We have to do our own state massaging because SDL - // can send us two unfocus events in a row for example, - // which confuses the focus code [SL-24071]. - mHaveInputFocus = true; - - mCallbacks->handleFocus(this); - } - else if( event.window.event == SDL_WINDOWEVENT_FOCUS_LOST ) // <FS:ND> What about SDL_WINDOWEVENT_LEAVE (mouse focus) + switch(event.window.event) { - // We have to do our own state massaging because SDL - // can send us two unfocus events in a row for example, - // which confuses the focus code [SL-24071]. - mHaveInputFocus = false; + //case SDL_WINDOWEVENT_SIZE_CHANGED: <FS:ND> SDL_WINDOWEVENT_SIZE_CHANGED is followed by SDL_WINDOWEVENT_RESIZED, so handling one shall be enough + case SDL_WINDOWEVENT_RESIZED: + { + LL_INFOS() << "Handling a resize event: " << event.window.data1 << "x" << event.window.data2 << LL_ENDL; + S32 width = llmax(event.window.data1, (S32)mMinWindowWidth); + S32 height = llmax(event.window.data2, (S32)mMinWindowHeight); - mCallbacks->handleFocusLost(this); - } - else if( event.window.event == SDL_WINDOWEVENT_MINIMIZED || - event.window.event == SDL_WINDOWEVENT_MAXIMIZED || - event.window.event == SDL_WINDOWEVENT_RESTORED || - event.window.event == SDL_WINDOWEVENT_EXPOSED || - event.window.event == SDL_WINDOWEVENT_SHOWN ) - { - mIsMinimized = (event.window.event == SDL_WINDOWEVENT_MINIMIZED); + mSurface = SDL_GetWindowSurface(mWindow); + mCallbacks->handleResize(this, width, height); + break; + } + case SDL_WINDOWEVENT_LEAVE: + mCallbacks->handleMouseLeave(this); + break; + case SDL_WINDOWEVENT_FOCUS_GAINED: + mCallbacks->handleFocus(this); + break; + case SDL_WINDOWEVENT_FOCUS_LOST: + mCallbacks->handleFocusLost(this); + break; + case SDL_WINDOWEVENT_EXPOSED: + case SDL_WINDOWEVENT_SHOWN: + case SDL_WINDOWEVENT_HIDDEN: + case SDL_WINDOWEVENT_MINIMIZED: + case SDL_WINDOWEVENT_MAXIMIZED: + case SDL_WINDOWEVENT_RESTORED: + { + Uint32 flags = SDL_GetWindowFlags(mWindow); + bool minimized = (flags & SDL_WINDOW_MINIMIZED); + bool hidden = (flags & SDL_WINDOW_HIDDEN); - mCallbacks->handleActivate(this, !mIsMinimized); - LL_INFOS() << "SDL deiconification state switched to " << mIsMinimized << LL_ENDL; + mCallbacks->handleActivate(this, !minimized || !hidden); + LL_INFOS() << "SDL deiconification state switched to " << minimized << LL_ENDL; + break; + } } - break; } case SDL_QUIT: @@ -1986,27 +1555,25 @@ void LLWindowSDL::gatherInput() } break; default: - //LL_INFOS() << "Unhandled SDL event type " << event.type << LL_ENDL; + LLGameControl::handleEvent(event, app_has_focus); break; } } updateCursor(); -#if LL_X11 // This is a good time to stop flashing the icon if our mFlashTimer has // expired. if (mFlashing && mFlashTimer.hasExpired()) { - x11_set_urgent(false); + SDL_FlashWindow(mWindow, SDL_FLASH_CANCEL); mFlashing = false; } -#endif // LL_X11 } static SDL_Cursor *makeSDLCursorFromBMP(const char *filename, int hotx, int hoty) { - SDL_Cursor *sdlcursor = NULL; + SDL_Cursor *sdlcursor = nullptr; SDL_Surface *bmpsurface; // Load cursor pixel data from BMP file @@ -2080,12 +1647,6 @@ static SDL_Cursor *makeSDLCursorFromBMP(const char *filename, int hotx, int hoty void LLWindowSDL::updateCursor() { - if (ATIbug) { - // cursor-updating is very flaky when this bug is - // present; do nothing. - return; - } - if (mCurrentCursor != mNextCursor) { if (mNextCursor < UI_CURSOR_COUNT) @@ -2097,10 +1658,13 @@ void LLWindowSDL::updateCursor() sdlcursor = mSDLCursors[UI_CURSOR_ARROW]; if (sdlcursor) SDL_SetCursor(sdlcursor); - } else { + + mCurrentCursor = mNextCursor; + } + else + { LL_WARNS() << "Tried to set invalid cursor number " << mNextCursor << LL_ENDL; } - mCurrentCursor = mNextCursor; } } @@ -2110,24 +1674,24 @@ void LLWindowSDL::initCursors() // Blank the cursor pointer array for those we may miss. for (i=0; i<UI_CURSOR_COUNT; ++i) { - mSDLCursors[i] = NULL; + mSDLCursors[i] = nullptr; } // Pre-make an SDL cursor for each of the known cursor types. // We hardcode the hotspots - to avoid that we'd have to write // a .cur file loader. // NOTE: SDL doesn't load RLE-compressed BMP files. - mSDLCursors[UI_CURSOR_ARROW] = makeSDLCursorFromBMP("llarrow.BMP",0,0); - mSDLCursors[UI_CURSOR_WAIT] = makeSDLCursorFromBMP("wait.BMP",12,15); - mSDLCursors[UI_CURSOR_HAND] = makeSDLCursorFromBMP("hand.BMP",7,10); - mSDLCursors[UI_CURSOR_IBEAM] = makeSDLCursorFromBMP("ibeam.BMP",15,16); - mSDLCursors[UI_CURSOR_CROSS] = makeSDLCursorFromBMP("cross.BMP",16,14); - mSDLCursors[UI_CURSOR_SIZENWSE] = makeSDLCursorFromBMP("sizenwse.BMP",14,17); - mSDLCursors[UI_CURSOR_SIZENESW] = makeSDLCursorFromBMP("sizenesw.BMP",17,17); - mSDLCursors[UI_CURSOR_SIZEWE] = makeSDLCursorFromBMP("sizewe.BMP",16,14); - mSDLCursors[UI_CURSOR_SIZENS] = makeSDLCursorFromBMP("sizens.BMP",17,16); - mSDLCursors[UI_CURSOR_SIZEALL] = makeSDLCursorFromBMP("sizeall.BMP", 17, 17); - mSDLCursors[UI_CURSOR_NO] = makeSDLCursorFromBMP("llno.BMP",8,8); - mSDLCursors[UI_CURSOR_WORKING] = makeSDLCursorFromBMP("working.BMP",12,15); + mSDLCursors[UI_CURSOR_ARROW] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); + mSDLCursors[UI_CURSOR_WAIT] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_WAIT); + mSDLCursors[UI_CURSOR_HAND] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND); + mSDLCursors[UI_CURSOR_IBEAM] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM); + mSDLCursors[UI_CURSOR_CROSS] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_CROSSHAIR); + mSDLCursors[UI_CURSOR_SIZENWSE] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENWSE); + mSDLCursors[UI_CURSOR_SIZENESW] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENESW); + mSDLCursors[UI_CURSOR_SIZEWE] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE); + mSDLCursors[UI_CURSOR_SIZENS] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENS); + mSDLCursors[UI_CURSOR_SIZEALL] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEALL); + mSDLCursors[UI_CURSOR_NO] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NO); + mSDLCursors[UI_CURSOR_WORKING] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_WAITARROW); mSDLCursors[UI_CURSOR_TOOLGRAB] = makeSDLCursorFromBMP("lltoolgrab.BMP",2,13); mSDLCursors[UI_CURSOR_TOOLLAND] = makeSDLCursorFromBMP("lltoolland.BMP",1,6); mSDLCursors[UI_CURSOR_TOOLFOCUS] = makeSDLCursorFromBMP("lltoolfocus.BMP",8,5); @@ -2160,11 +1724,6 @@ void LLWindowSDL::initCursors() mSDLCursors[UI_CURSOR_TOOLPATHFINDING_PATH_END] = makeSDLCursorFromBMP("lltoolpathfindingpathend.BMP", 16, 16); mSDLCursors[UI_CURSOR_TOOLPATHFINDING_PATH_END_ADD] = makeSDLCursorFromBMP("lltoolpathfindingpathendadd.BMP", 16, 16); mSDLCursors[UI_CURSOR_TOOLNO] = makeSDLCursorFromBMP("llno.BMP",8,8); - - if (getenv("LL_ATI_MOUSE_CURSOR_BUG") != NULL) { - LL_INFOS() << "Disabling cursor updating due to LL_ATI_MOUSE_CURSOR_BUG" << LL_ENDL; - ATIbug = true; - } } void LLWindowSDL::quitCursors() @@ -2214,7 +1773,7 @@ void LLWindowSDL::hideCursor() // LL_INFOS() << "hideCursor: hiding" << LL_ENDL; mCursorHidden = true; mHideCursorPermanent = true; - SDL_ShowCursor(0); + SDL_ShowCursor(SDL_DISABLE); } else { @@ -2229,7 +1788,7 @@ void LLWindowSDL::showCursor() // LL_INFOS() << "showCursor: showing" << LL_ENDL; mCursorHidden = false; mHideCursorPermanent = false; - SDL_ShowCursor(1); + SDL_ShowCursor(SDL_ENABLE); } else { @@ -2320,7 +1879,7 @@ bool LLWindowSDL::dialogColorPicker( F32 *r, F32 *g, F32 *b) Make the raw keyboard data available - used to poke through to LLQtWebKit so that Qt/Webkit has access to the virtual keycodes etc. that it needs */ -LLSD LLWindowSDL::getNativeKeyData() +LLSD LLWindowSDL::getNativeKeyData() const { LLSD result = LLSD::emptyMap(); @@ -2347,62 +1906,6 @@ LLSD LLWindowSDL::getNativeKeyData() return result; } -#if LL_LINUX || LL_SOLARIS -// extracted from spawnWebBrowser for clarity and to eliminate -// compiler confusion regarding close(int fd) vs. LLWindow::close() -void exec_cmd(const std::string& cmd, const std::string& arg) -{ - char* const argv[] = {(char*)cmd.c_str(), (char*)arg.c_str(), NULL}; - fflush(NULL); - pid_t pid = fork(); - if (pid == 0) - { // child - // disconnect from stdin/stdout/stderr, or child will - // keep our output pipe undesirably alive if it outlives us. - // close(0); - // close(1); - // close(2); - // <FS:TS> Reopen stdin, stdout, and stderr to /dev/null. - // It's good practice to always have those file - // descriptors open to something, lest the exec'd - // program actually try to use them. - FILE *result; - result = freopen("/dev/null","r",stdin); - if (result == NULL) - { - LL_WARNS() << "Error reopening stdin for web browser: " - << strerror(errno) << LL_ENDL; - } - result = freopen("/dev/null","w",stdout); - if (result == NULL) - { - LL_WARNS() << "Error reopening stdout for web browser: " - << strerror(errno) << LL_ENDL; - } - result = freopen("/dev/null","w",stderr); - if (result == NULL) - { - LL_WARNS() << "Error reopening stderr for web browser: " - << strerror(errno) << LL_ENDL; - } - // end ourself by running the command - execv(cmd.c_str(), argv); /* Flawfinder: ignore */ - // if execv returns at all, there was a problem. - LL_WARNS() << "execv failure when trying to start " << cmd << LL_ENDL; - _exit(1); // _exit because we don't want atexit() clean-up! - } else { - if (pid > 0) - { - // parent - wait for child to die - int childExitStatus; - waitpid(pid, &childExitStatus, 0); - } else { - LL_WARNS() << "fork failure." << LL_ENDL; - } - } -} -#endif - // Open a URL with the user's default web browser. // Must begin with protocol identifier. void LLWindowSDL::spawnWebBrowser(const std::string& escaped_url, bool async) @@ -2426,35 +1929,14 @@ void LLWindowSDL::spawnWebBrowser(const std::string& escaped_url, bool async) LL_INFOS() << "spawn_web_browser: " << escaped_url << LL_ENDL; -#if LL_LINUX -# if LL_X11 - if (mSDL_Display) + if (SDL_OpenURL(escaped_url.c_str()) != 0) { - maybe_lock_display(); - // Just in case - before forking. - XSync(mSDL_Display, False); - maybe_unlock_display(); + LL_WARNS() << "spawn_web_browser failed with error: " << SDL_GetError() << LL_ENDL; } -# endif // LL_X11 - - std::string cmd, arg; - cmd = gDirUtilp->getAppRODataDir(); - cmd += gDirUtilp->getDirDelimiter(); - cmd += "etc"; - cmd += gDirUtilp->getDirDelimiter(); - cmd += "launch_url.sh"; - arg = escaped_url; - exec_cmd(cmd, arg); -#endif // LL_LINUX LL_INFOS() << "spawn_web_browser returning." << LL_ENDL; } -void LLWindowSDL::openFile(const std::string& file_name) -{ - spawnWebBrowser("file://"+file_name,true); -} - void *LLWindowSDL::getPlatformWindow() { return NULL; @@ -2465,15 +1947,10 @@ void LLWindowSDL::bringToFront() // This is currently used when we are 'launched' to a specific // map position externally. LL_INFOS() << "bringToFront" << LL_ENDL; -#if LL_X11 - if (mSDL_Display && !mFullscreen) + if (mWindow && !mFullscreen) { - maybe_lock_display(); - XRaiseWindow(mSDL_Display, mSDL_XWindowID); - XSync(mSDL_Display, False); - maybe_unlock_display(); + SDL_RaiseWindow(mWindow); } -#endif // LL_X11 } //static @@ -2577,44 +2054,6 @@ std::vector<std::string> LLWindowSDL::getDynamicFallbackFontList() return rtns; } - -void* LLWindowSDL::createSharedContext() -{ - auto *pContext = SDL_GL_CreateContext(mWindow); - if ( pContext) - { - SDL_GL_SetSwapInterval(0); - SDL_GL_MakeCurrent(mWindow, mContext); - - LLCoordScreen size; - if (getSize(&size)) - setSize(size); - - LL_DEBUGS() << "Creating shared OpenGL context successful!" << LL_ENDL; - - return (void*)pContext; - } - - LL_WARNS() << "Creating shared OpenGL context failed!" << LL_ENDL; - - return nullptr; -} - -void LLWindowSDL::makeContextCurrent(void* contextPtr) -{ - LL_PROFILER_GPU_CONTEXT; - SDL_GL_MakeCurrent( mWindow, contextPtr ); -} - -void LLWindowSDL::destroySharedContext(void* contextPtr) -{ - SDL_GL_DeleteContext( contextPtr ); -} - -void LLWindowSDL::toggleVSync(bool enable_vsync) -{ -} - void LLWindowSDL::setLanguageTextInput(const LLCoordGL& position) { LLCoordWindow win_pos; diff --git a/indra/llwindow/llwindowsdl.h b/indra/llwindow/llwindowsdl.h index 10769bb3ba..a85b7c11e7 100644 --- a/indra/llwindow/llwindowsdl.h +++ b/indra/llwindow/llwindowsdl.h @@ -29,6 +29,7 @@ // Simple Directmedia Layer (http://libsdl.org/) implementation of LLWindow class +#if LL_LINUX #include "llwindow.h" #include "lltimer.h" @@ -54,11 +55,11 @@ public: void close() override; - bool getVisible() override; + bool getVisible() const override; - bool getMinimized() override; + bool getMinimized() const override; - bool getMaximized() override; + bool getMaximized() const override; bool maximize() override; @@ -66,13 +67,11 @@ public: void restore() override; - bool getFullscreen(); + bool getPosition(LLCoordScreen *position) const override; - bool getPosition(LLCoordScreen *position) override; + bool getSize(LLCoordScreen *size) const override; - bool getSize(LLCoordScreen *size) override; - - bool getSize(LLCoordWindow *size) override; + bool getSize(LLCoordWindow *size) const override; bool setPosition(LLCoordScreen position) override; @@ -121,19 +120,19 @@ public: void flashIcon(F32 seconds) override; - F32 getGamma() override; + F32 getGamma() const override; bool setGamma(const F32 gamma) override; // Set the gamma - U32 getFSAASamples() override; + + U32 getFSAASamples() const override; void setFSAASamples(const U32 samples) override; bool restoreGamma() override; // Restore original gamma table (before updating gamma) - ESwapMethod getSwapMethod() override { return mSwapMethod; } void processMiscNativeEvents() override; - void gatherInput() override; + void gatherInput(bool app_has_focus) override; void swapBuffers() override; @@ -142,17 +141,17 @@ public: void delayInputProcessing() override {}; // handy coordinate space conversion routines - bool convertCoords(LLCoordScreen from, LLCoordWindow *to) override; + bool convertCoords(LLCoordScreen from, LLCoordWindow *to) const override; - bool convertCoords(LLCoordWindow from, LLCoordScreen *to) override; + bool convertCoords(LLCoordWindow from, LLCoordScreen *to) const override; - bool convertCoords(LLCoordWindow from, LLCoordGL *to) override; + bool convertCoords(LLCoordWindow from, LLCoordGL *to) const override; - bool convertCoords(LLCoordGL from, LLCoordWindow *to) override; + bool convertCoords(LLCoordGL from, LLCoordWindow *to) const override; - bool convertCoords(LLCoordScreen from, LLCoordGL *to) override; + bool convertCoords(LLCoordScreen from, LLCoordGL *to) const override; - bool convertCoords(LLCoordGL from, LLCoordScreen *to) override; + bool convertCoords(LLCoordGL from, LLCoordScreen *to) const override; LLWindowResolution *getSupportedResolutions(S32 &num_resolutions) override; @@ -176,8 +175,6 @@ public: void spawnWebBrowser(const std::string &escaped_url, bool async) override; - void openFile(const std::string &file_name); - void setTitle(const std::string title) override; static std::vector<std::string> getDynamicFallbackFontList(); @@ -211,7 +208,7 @@ public: protected: LLWindowSDL(LLWindowCallbacks *callbacks, - const std::string &title, int x, int y, int width, int height, U32 flags, + const std::string &title, const std::string& name, int x, int y, int width, int height, U32 flags, bool fullscreen, bool clearBg, bool enable_vsync, bool use_gl, bool ignore_pixel_depth, U32 fsaa_samples); @@ -219,7 +216,7 @@ protected: bool isValid() override; - LLSD getNativeKeyData() override; + LLSD getNativeKeyData() const override; void initCursors(); @@ -247,8 +244,6 @@ protected: void setupFailure(const std::string &text, const std::string &caption, U32 type); - void fixWindowSize(void); - U32 SDLCheckGrabbyKeys(U32 keysym, bool gain); bool SDLReallyCaptureInput(bool capture); @@ -280,45 +275,15 @@ protected: friend class LLWindowManager; private: -#if LL_X11 - - void x11_set_urgent(bool urgent); - bool mFlashing; LLTimer mFlashTimer; -#endif //LL_X11 - U32 mKeyVirtualKey; U32 mKeyModifiers; std::string mInputType; -public: -#if LL_X11 - - static Display *getSDLDisplay(); - - LLWString const &getPrimaryText() const { return mPrimaryClipboard; } - - LLWString const &getSecondaryText() const { return mSecondaryClipboard; } - - void clearPrimaryText() { mPrimaryClipboard.clear(); } - - void clearSecondaryText() { mSecondaryClipboard.clear(); } private: void tryFindFullscreenSize(int &aWidth, int &aHeight); - - void initialiseX11Clipboard(); - - bool getSelectionText(Atom selection, LLWString &text); - - bool getSelectionText(Atom selection, Atom type, LLWString &text); - - bool setSelectionText(Atom selection, const LLWString &text); - -#endif - LLWString mPrimaryClipboard; - LLWString mSecondaryClipboard; }; class LLSplashScreenSDL : public LLSplashScreen @@ -334,4 +299,5 @@ public: S32 OSMessageBoxSDL(const std::string& text, const std::string& caption, U32 type); +#endif //LL_LINUX #endif //LL_LLWINDOWSDL_H diff --git a/indra/llwindow/llwindowwin32.cpp b/indra/llwindow/llwindowwin32.cpp index b19fa13b41..f8294f063f 100644 --- a/indra/llwindow/llwindowwin32.cpp +++ b/indra/llwindow/llwindowwin32.cpp @@ -31,6 +31,7 @@ #include "llwindowwin32.h" // LLWindow library includes +#include "llgamecontrol.h" #include "llkeyboardwin32.h" #include "lldragdropwin32.h" #include "llpreeditor.h" @@ -982,17 +983,17 @@ bool LLWindowWin32::isValid() return (mWindowHandle != NULL); } -bool LLWindowWin32::getVisible() +bool LLWindowWin32::getVisible() const { return (mWindowHandle && IsWindowVisible(mWindowHandle)); } -bool LLWindowWin32::getMinimized() +bool LLWindowWin32::getMinimized() const { return (mWindowHandle && IsIconic(mWindowHandle)); } -bool LLWindowWin32::getMaximized() +bool LLWindowWin32::getMaximized() const { return (mWindowHandle && IsZoomed(mWindowHandle)); } @@ -1017,26 +1018,21 @@ bool LLWindowWin32::maximize() return true; } -bool LLWindowWin32::getFullscreen() -{ - return mFullscreen; -} - -bool LLWindowWin32::getPosition(LLCoordScreen *position) +bool LLWindowWin32::getPosition(LLCoordScreen *position) const { position->mX = mRect.left; position->mY = mRect.top; return true; } -bool LLWindowWin32::getSize(LLCoordScreen *size) +bool LLWindowWin32::getSize(LLCoordScreen *size) const { size->mX = mRect.right - mRect.left; size->mY = mRect.bottom - mRect.top; return true; } -bool LLWindowWin32::getSize(LLCoordWindow *size) +bool LLWindowWin32::getSize(LLCoordWindow *size) const { size->mX = mClientRect.right - mClientRect.left; size->mY = mClientRect.bottom - mClientRect.top; @@ -1974,7 +1970,7 @@ bool LLWindowWin32::getCursorPosition(LLCoordWindow *position) return true; } -bool LLWindowWin32::getCursorDelta(LLCoordCommon* delta) +bool LLWindowWin32::getCursorDelta(LLCoordCommon* delta) const { if (delta == nullptr) { @@ -2162,7 +2158,7 @@ void LLWindowWin32::delayInputProcessing() } -void LLWindowWin32::gatherInput() +void LLWindowWin32::gatherInput(bool app_has_focus) { ASSERT_MAIN_THREAD(); LL_PROFILE_ZONE_SCOPED_CATEGORY_WIN32; @@ -2242,6 +2238,8 @@ void LLWindowWin32::gatherInput() mInputProcessingPaused = false; updateCursor(); + + LLGameControl::processEvents(app_has_focus); } static LLTrace::BlockTimerStatHandle FTM_KEYHANDLER("Handle Keyboard"); @@ -3112,7 +3110,7 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_ return ret; } -bool LLWindowWin32::convertCoords(LLCoordGL from, LLCoordWindow *to) +bool LLWindowWin32::convertCoords(LLCoordGL from, LLCoordWindow *to) const { S32 client_height; RECT client_rect; @@ -3132,7 +3130,7 @@ bool LLWindowWin32::convertCoords(LLCoordGL from, LLCoordWindow *to) return true; } -bool LLWindowWin32::convertCoords(LLCoordWindow from, LLCoordGL* to) +bool LLWindowWin32::convertCoords(LLCoordWindow from, LLCoordGL* to) const { S32 client_height; RECT client_rect; @@ -3151,7 +3149,7 @@ bool LLWindowWin32::convertCoords(LLCoordWindow from, LLCoordGL* to) return true; } -bool LLWindowWin32::convertCoords(LLCoordScreen from, LLCoordWindow* to) +bool LLWindowWin32::convertCoords(LLCoordScreen from, LLCoordWindow* to) const { POINT mouse_point; @@ -3168,7 +3166,7 @@ bool LLWindowWin32::convertCoords(LLCoordScreen from, LLCoordWindow* to) return result; } -bool LLWindowWin32::convertCoords(LLCoordWindow from, LLCoordScreen *to) +bool LLWindowWin32::convertCoords(LLCoordWindow from, LLCoordScreen *to) const { POINT mouse_point; @@ -3185,7 +3183,7 @@ bool LLWindowWin32::convertCoords(LLCoordWindow from, LLCoordScreen *to) return result; } -bool LLWindowWin32::convertCoords(LLCoordScreen from, LLCoordGL *to) +bool LLWindowWin32::convertCoords(LLCoordScreen from, LLCoordGL *to) const { LLCoordWindow window_coord; @@ -3199,7 +3197,7 @@ bool LLWindowWin32::convertCoords(LLCoordScreen from, LLCoordGL *to) return true; } -bool LLWindowWin32::convertCoords(LLCoordGL from, LLCoordScreen *to) +bool LLWindowWin32::convertCoords(LLCoordGL from, LLCoordScreen *to) const { LLCoordWindow window_coord; @@ -3318,7 +3316,7 @@ void LLWindowWin32::setMouseClipping( bool b ) } } -bool LLWindowWin32::getClientRectInScreenSpace( RECT* rectp ) +bool LLWindowWin32::getClientRectInScreenSpace( RECT* rectp ) const { bool success = false; @@ -3362,7 +3360,7 @@ void LLWindowWin32::flashIcon(F32 seconds) }); } -F32 LLWindowWin32::getGamma() +F32 LLWindowWin32::getGamma() const { return mCurrentGamma; } @@ -3424,7 +3422,7 @@ void LLWindowWin32::setFSAASamples(const U32 fsaa_samples) mFSAASamples = fsaa_samples; } -U32 LLWindowWin32::getFSAASamples() +U32 LLWindowWin32::getFSAASamples() const { return mFSAASamples; } @@ -3787,7 +3785,7 @@ void LLWindowWin32::openFolder(const std::string &path) Make the raw keyboard data available - used to poke through to LLQtWebKit so that Qt/Webkit has access to the virtual keycodes etc. that it needs */ -LLSD LLWindowWin32::getNativeKeyData() +LLSD LLWindowWin32::getNativeKeyData() const { LLSD result = LLSD::emptyMap(); diff --git a/indra/llwindow/llwindowwin32.h b/indra/llwindow/llwindowwin32.h index e38cfe7ebc..f4964d064e 100644 --- a/indra/llwindow/llwindowwin32.h +++ b/indra/llwindow/llwindowwin32.h @@ -45,84 +45,82 @@ typedef void (*LLW32MsgCallback)(const MSG &msg); class LLWindowWin32 : public LLWindow { public: - /*virtual*/ void show(); - /*virtual*/ void hide(); - /*virtual*/ void close(); - /*virtual*/ bool getVisible(); - /*virtual*/ bool getMinimized(); - /*virtual*/ bool getMaximized(); - /*virtual*/ bool maximize(); - /*virtual*/ void minimize(); - /*virtual*/ void restore(); - /*virtual*/ bool getFullscreen(); - /*virtual*/ bool getPosition(LLCoordScreen *position); - /*virtual*/ bool getSize(LLCoordScreen *size); - /*virtual*/ bool getSize(LLCoordWindow *size); - /*virtual*/ bool setPosition(LLCoordScreen position); - /*virtual*/ bool setSizeImpl(LLCoordScreen size); - /*virtual*/ bool setSizeImpl(LLCoordWindow size); - /*virtual*/ bool switchContext(bool fullscreen, const LLCoordScreen &size, bool enable_vsync, const LLCoordScreen * const posp = NULL); - /*virtual*/ void setTitle(const std::string title); + void show() override; + void hide() override; + void close() override; + bool getVisible() const override; + bool getMinimized() const override; + bool getMaximized() const override; + bool maximize() override; + void minimize() override; + void restore() override; + bool getPosition(LLCoordScreen *position) const override; + bool getSize(LLCoordScreen *size) const override; + bool getSize(LLCoordWindow *size) const override; + bool setPosition(LLCoordScreen position) override; + bool setSizeImpl(LLCoordScreen size) override; + bool setSizeImpl(LLCoordWindow size) override; + bool switchContext(bool fullscreen, const LLCoordScreen &size, bool enable_vsync, const LLCoordScreen * const posp = NULL) override; + void setTitle(const std::string title) override; void* createSharedContext() override; void makeContextCurrent(void* context) override; void destroySharedContext(void* context) override; - /*virtual*/ void toggleVSync(bool enable_vsync); - /*virtual*/ bool setCursorPosition(LLCoordWindow position); - /*virtual*/ bool getCursorPosition(LLCoordWindow *position); - /*virtual*/ bool getCursorDelta(LLCoordCommon* delta); - /*virtual*/ void showCursor(); - /*virtual*/ void hideCursor(); - /*virtual*/ void showCursorFromMouseMove(); - /*virtual*/ void hideCursorUntilMouseMove(); - /*virtual*/ bool isCursorHidden(); - /*virtual*/ void updateCursor(); - /*virtual*/ ECursorType getCursor() const; - /*virtual*/ void captureMouse(); - /*virtual*/ void releaseMouse(); - /*virtual*/ void setMouseClipping( bool b ); - /*virtual*/ bool isClipboardTextAvailable(); - /*virtual*/ bool pasteTextFromClipboard(LLWString &dst); - /*virtual*/ bool copyTextToClipboard(const LLWString &src); - /*virtual*/ void flashIcon(F32 seconds); - /*virtual*/ F32 getGamma(); - /*virtual*/ bool setGamma(const F32 gamma); // Set the gamma - /*virtual*/ void setFSAASamples(const U32 fsaa_samples); - /*virtual*/ U32 getFSAASamples(); - /*virtual*/ bool restoreGamma(); // Restore original gamma table (before updating gamma) - /*virtual*/ ESwapMethod getSwapMethod() { return mSwapMethod; } - /*virtual*/ void gatherInput(); - /*virtual*/ void delayInputProcessing(); - /*virtual*/ void swapBuffers(); - /*virtual*/ void restoreGLContext() {}; + void toggleVSync(bool enable_vsync) override; + bool setCursorPosition(LLCoordWindow position) override; + bool getCursorPosition(LLCoordWindow *position) override; + bool getCursorDelta(LLCoordCommon* delta) const override; + void showCursor() override; + void hideCursor() override; + void showCursorFromMouseMove() override; + void hideCursorUntilMouseMove() override; + bool isCursorHidden() override; + void updateCursor() override; + ECursorType getCursor() const override; + void captureMouse() override; + void releaseMouse() override; + void setMouseClipping( bool b ) override; + bool isClipboardTextAvailable() override; + bool pasteTextFromClipboard(LLWString &dst) override; + bool copyTextToClipboard(const LLWString &src) override; + void flashIcon(F32 seconds) override; + F32 getGamma() const override; + bool setGamma(const F32 gamma) override; // Set the gamma + void setFSAASamples(const U32 fsaa_samples) override; + U32 getFSAASamples() const override; + bool restoreGamma() override; // Restore original gamma table (before updating gamma) + void gatherInput(bool app_has_focus) override; + void delayInputProcessing() override; + void swapBuffers() override; + void restoreGLContext() {}; // handy coordinate space conversion routines - /*virtual*/ bool convertCoords(LLCoordScreen from, LLCoordWindow *to); - /*virtual*/ bool convertCoords(LLCoordWindow from, LLCoordScreen *to); - /*virtual*/ bool convertCoords(LLCoordWindow from, LLCoordGL *to); - /*virtual*/ bool convertCoords(LLCoordGL from, LLCoordWindow *to); - /*virtual*/ bool convertCoords(LLCoordScreen from, LLCoordGL *to); - /*virtual*/ bool convertCoords(LLCoordGL from, LLCoordScreen *to); - - /*virtual*/ LLWindowResolution* getSupportedResolutions(S32 &num_resolutions); - /*virtual*/ F32 getNativeAspectRatio(); - /*virtual*/ F32 getPixelAspectRatio(); - /*virtual*/ void setNativeAspectRatio(F32 ratio) { mOverrideAspectRatio = ratio; } - - /*virtual*/ bool dialogColorPicker(F32 *r, F32 *g, F32 *b ); - - /*virtual*/ void *getPlatformWindow(); - /*virtual*/ void bringToFront(); - /*virtual*/ void focusClient(); - - /*virtual*/ void allowLanguageTextInput(LLPreeditor *preeditor, bool b); - /*virtual*/ void setLanguageTextInput( const LLCoordGL & pos ); - /*virtual*/ void updateLanguageTextInputArea(); - /*virtual*/ void interruptLanguageTextInput(); - /*virtual*/ void spawnWebBrowser(const std::string& escaped_url, bool async); + bool convertCoords(LLCoordScreen from, LLCoordWindow *to) const override; + bool convertCoords(LLCoordWindow from, LLCoordScreen *to) const override; + bool convertCoords(LLCoordWindow from, LLCoordGL *to) const override; + bool convertCoords(LLCoordGL from, LLCoordWindow *to) const override; + bool convertCoords(LLCoordScreen from, LLCoordGL *to) const override; + bool convertCoords(LLCoordGL from, LLCoordScreen *to) const override; + + LLWindowResolution* getSupportedResolutions(S32 &num_resolutions) override; + F32 getNativeAspectRatio() override; + F32 getPixelAspectRatio() override; + void setNativeAspectRatio(F32 ratio) override { mOverrideAspectRatio = ratio; } + + bool dialogColorPicker(F32 *r, F32 *g, F32 *b ) override; + + void *getPlatformWindow() override; + void bringToFront() override; + void focusClient() override; + + void allowLanguageTextInput(LLPreeditor *preeditor, bool b) override; + void setLanguageTextInput( const LLCoordGL & pos ) override; + void updateLanguageTextInputArea() override; + void interruptLanguageTextInput() override; + void spawnWebBrowser(const std::string& escaped_url, bool async) override; void openFolder(const std::string &path) override; - /*virtual*/ F32 getSystemUISize(); + F32 getSystemUISize() override; LLWindowCallbacks::DragNDropResult completeDragNDropRequest( const LLCoordGL gl_coord, const MASK mask, LLWindowCallbacks::DragNDropAction action, const std::string url ); @@ -130,13 +128,13 @@ public: static std::vector<std::string> getDynamicFallbackFontList(); static void setDPIAwareness(); - /*virtual*/ void* getDirectInput8(); - /*virtual*/ bool getInputDevices(U32 device_type_filter, + void* getDirectInput8() override; + bool getInputDevices(U32 device_type_filter, std::function<bool(std::string&, LLSD&, void*)> osx_callback, void* win_callback, - void* userdata); + void* userdata) override; - U32 getRawWParam() { return mRawWParam; } + U32 getRawWParam() const { return mRawWParam; } protected: LLWindowWin32(LLWindowCallbacks* callbacks, @@ -149,7 +147,7 @@ protected: HCURSOR loadColorCursor(LPCTSTR name); bool isValid(); void moveWindow(const LLCoordScreen& position,const LLCoordScreen& size); - virtual LLSD getNativeKeyData(); + LLSD getNativeKeyData() const override; // Changes display resolution. Returns true if successful bool setDisplayResolution(S32 width, S32 height, S32 bits, S32 refresh); @@ -175,7 +173,7 @@ protected: // Platform specific methods // - bool getClientRectInScreenSpace(RECT* rectp); + bool getClientRectInScreenSpace(RECT* rectp) const; static LRESULT CALLBACK mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_param, LPARAM l_param); @@ -266,9 +264,9 @@ public: LLSplashScreenWin32(); virtual ~LLSplashScreenWin32(); - /*virtual*/ void showImpl(); - /*virtual*/ void updateImpl(const std::string& mesg); - /*virtual*/ void hideImpl(); + void showImpl() override; + void updateImpl(const std::string& mesg) override; + void hideImpl() override; #if LL_WINDOWS static LRESULT CALLBACK windowProc(HWND h_wnd, UINT u_msg, |