diff options
author | Leviathan Linden <leviathan@lindenlab.com> | 2023-09-19 09:40:08 -0700 |
---|---|---|
committer | Andrew Meadows <andrew.l.meadows@gmail.com> | 2024-10-03 09:01:06 -0700 |
commit | 13221f67c465017f44ca46aeca23b0d820935825 (patch) | |
tree | a38de052ac3adb3160cfca76abbadf77b321cc71 | |
parent | 49c661f6cf9ae0a75b93c870a00edba59df54189 (diff) |
add GameControl feature and SDL2 dependency
33 files changed, 1159 insertions, 114 deletions
diff --git a/autobuild.xml b/autobuild.xml index 4f70212fa8..4ec43be8de 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -9,8 +9,32 @@ <map> <key>SDL2</key> <map> + <key>copyright</key> + <string>Copyright (C) 1997-2022 Sam Lantinga (slouken@libsdl.org)</string> + <key>description</key> + <string>Simple DirectMedia Layer is a cross-platform multimedia library designed to provide low level access to audio, keyboard, mouse, joystick, 3D hardware via OpenGL, and 2D video framebuffer.</string> + <key>license</key> + <string>lgpl</string> + <key>license_file</key> + <string>LICENSES/SDL2.txt</string> + <key>name</key> + <string>SDL2</string> <key>platforms</key> <map> + <key>darwin64</key> + <map> + <key>archive</key> + <map> + <key>hash</key> + <string>fd6368b53609b078b4ed8816bad1d1de2756f4f2</string> + <key>hash_algorithm</key> + <string>sha1</string> + <key>url</key> + <string>https://github.com/secondlife/3p-sdl2/releases/download/v2.28.0/SDL2-2.28.0-darwin64-5991c8f.tar.zst</string> + </map> + <key>name</key> + <string>darwin64</string> + </map> <key>linux64</key> <map> <key>archive</key> @@ -25,19 +49,21 @@ <key>name</key> <string>linux64</string> </map> + <key>windows64</key> + <map> + <key>archive</key> + <map> + <key>hash</key> + <string>48e8d971dfa8025353293ead7d41a2a77b004faa</string> + <key>hash_algorithm</key> + <string>sha1</string> + <key>url</key> + <string>https://github.com/secondlife/3p-sdl2/releases/download/v2.28.0/SDL2-2.28.0-windows64-5991c8f.tar.zst</string> + </map> + <key>name</key> + <string>windows64</string> + </map> </map> - <key>license</key> - <string>lgpl</string> - <key>license_file</key> - <string>LICENSES/SDL2.txt</string> - <key>copyright</key> - <string>Copyright (C) 1997-2022 Sam Lantinga (slouken@libsdl.org)</string> - <key>version</key> - <string>2.28.0</string> - <key>name</key> - <string>SDL2</string> - <key>description</key> - <string>Simple DirectMedia Layer is a cross-platform multimedia library designed to provide low level access to audio, keyboard, mouse, joystick, 3D hardware via OpenGL, and 2D video framebuffer.</string> </map> <key>fltk</key> <map> @@ -1181,6 +1207,18 @@ <key>name</key> <string>darwin64</string> </map> + <key>linux64</key> + <map> + <key>archive</key> + <map> + <key>hash</key> + <string>ffbdd109356d66ddfefd8a5d57f63f1f</string> + <key>url</key> + <string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/533/1144/libhunspell-1.3.2.500526-linux64-500526.tar.bz2</string> + </map> + <key>name</key> + <string>linux64</string> + </map> <key>windows64</key> <map> <key>archive</key> @@ -1574,7 +1612,7 @@ <string>https://s3-proxy.lindenlab.com/private-builds-secondlife-com/ct2/84731/788139/llphysicsextensions_tpv-1.0.561752-windows64-561752.tar.bz2</string> </map> <key>name</key> - <string>windows</string> + <string>windows64</string> </map> </map> <key>license</key> @@ -3254,7 +3292,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <key>name</key> <string>linux64</string> </map> - <key>windows</key> + <key>windows64</key> <map> <key>configurations</key> <map> @@ -3411,7 +3449,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <key>build_directory</key> <string>build-vc${AUTOBUILD_VSVER|170}-$AUTOBUILD_ADDRSIZE</string> <key>name</key> - <string>windows</string> + <string>windows64</string> </map> </map> <key>license</key> diff --git a/indra/cmake/CMakeLists.txt b/indra/cmake/CMakeLists.txt index 16da388e61..9017fc2fb4 100644 --- a/indra/cmake/CMakeLists.txt +++ b/indra/cmake/CMakeLists.txt @@ -52,6 +52,7 @@ set(cmake_SOURCE_FILES Prebuilt.cmake PulseAudio.cmake Python.cmake + SDL2.cmake TemplateCheck.cmake TinyEXR.cmake TinyGLTF.cmake diff --git a/indra/cmake/Copy3rdPartyLibs.cmake b/indra/cmake/Copy3rdPartyLibs.cmake index 1e6dabd6c0..e98c77497b 100644 --- a/indra/cmake/Copy3rdPartyLibs.cmake +++ b/indra/cmake/Copy3rdPartyLibs.cmake @@ -54,6 +54,7 @@ if(WINDOWS) set(release_src_dir "${ARCH_PREBUILT_DIRS_RELEASE}") set(release_files openjp2.dll + SDL2.dll ) if(LLCOMMON_LINK_SHARED) @@ -169,6 +170,8 @@ elseif(DARWIN) set(release_src_dir "${ARCH_PREBUILT_DIRS_RELEASE}") set(release_files libndofdev.dylib + libSDL2.dylib + libSDL2-2.0.dylib ) if(LLCOMMON_LINK_SHARED) @@ -197,6 +200,7 @@ elseif(LINUX) libortp.so libvivoxoal.so.1 libvivoxsdk.so + libSDL2.so ) set(slvoice_files SLVoice) diff --git a/indra/cmake/LLWindow.cmake b/indra/cmake/LLWindow.cmake index 23f4115aeb..b2c1792df1 100644 --- a/indra/cmake/LLWindow.cmake +++ b/indra/cmake/LLWindow.cmake @@ -3,18 +3,6 @@ include(Variables) include(GLEXT) include(Prebuilt) +include(SDL2) include_guard() -add_library( ll::SDL INTERFACE IMPORTED ) - - -if (LINUX) - #Must come first as use_system_binary can exit this file early - target_compile_definitions( ll::SDL INTERFACE LL_SDL_VERSION=2 LL_SDL) - - #find_package(SDL2 REQUIRED) - #target_link_libraries( ll::SDL INTERFACE SDL2::SDL2 SDL2::SDL2main X11) - - use_prebuilt_binary(SDL2) - target_link_libraries( ll::SDL INTERFACE SDL2 X11) -endif (LINUX) diff --git a/indra/cmake/SDL2.cmake b/indra/cmake/SDL2.cmake new file mode 100644 index 0000000000..87195ed108 --- /dev/null +++ b/indra/cmake/SDL2.cmake @@ -0,0 +1,22 @@ +# -*- cmake -*- +cmake_minimum_required( VERSION 3.13 FATAL_ERROR ) + +include(Linking) +include( Prebuilt ) +include_guard() + +add_library( ll::SDL2 INTERFACE IMPORTED ) + +use_system_binary( SDL2 ) +use_prebuilt_binary( SDL2 ) + +find_library( SDL2_LIBRARY + NAMES SDL2 + PATHS "${LIBS_PREBUILT_DIR}/lib/release") +if ( "${SDL2_LIBRARY}" STREQUAL "SDL2_LIBRARY-NOTFOUND" ) + message( FATAL_ERROR "unable to find SDL2_LIBRARY" ) +endif() + +target_link_libraries( ll::SDL2 INTERFACE "${SDL2_LIBRARY}" ) +target_include_directories( ll::SDL2 SYSTEM INTERFACE "${LIBS_PREBUILT_DIR}/include" ) + diff --git a/indra/llcommon/llkeybind.cpp b/indra/llcommon/llkeybind.cpp index e36c1d0a4c..83c53d220d 100644 --- a/indra/llcommon/llkeybind.cpp +++ b/indra/llcommon/llkeybind.cpp @@ -123,7 +123,7 @@ LLKeyData& LLKeyData::operator=(const LLKeyData& rhs) return *this; } -bool LLKeyData::operator==(const LLKeyData& rhs) +bool LLKeyData::operator==(const LLKeyData& rhs) const { if (mMouse != rhs.mMouse) return false; if (mKey != rhs.mKey) return false; @@ -132,7 +132,7 @@ bool LLKeyData::operator==(const LLKeyData& rhs) return true; } -bool LLKeyData::operator!=(const LLKeyData& rhs) +bool LLKeyData::operator!=(const LLKeyData& rhs) const { if (mMouse != rhs.mMouse) return true; if (mKey != rhs.mKey) return true; @@ -179,7 +179,7 @@ LLKeyBind::LLKeyBind(const LLSD &key_bind) } } -bool LLKeyBind::operator==(const LLKeyBind& rhs) +bool LLKeyBind::operator==(const LLKeyBind& rhs) const { auto size = mData.size(); if (size != rhs.mData.size()) return false; @@ -192,7 +192,7 @@ bool LLKeyBind::operator==(const LLKeyBind& rhs) return true; } -bool LLKeyBind::operator!=(const LLKeyBind& rhs) +bool LLKeyBind::operator!=(const LLKeyBind& rhs) const { auto size = mData.size(); if (size != rhs.mData.size()) return true; diff --git a/indra/llcommon/llkeybind.h b/indra/llcommon/llkeybind.h index 1bbb2fadb5..eb9b68f9d1 100644 --- a/indra/llcommon/llkeybind.h +++ b/indra/llcommon/llkeybind.h @@ -44,8 +44,8 @@ public: bool empty() const { return isEmpty(); }; void reset(); LLKeyData& operator=(const LLKeyData& rhs); - bool operator==(const LLKeyData& rhs); - bool operator!=(const LLKeyData& rhs); + bool operator==(const LLKeyData& rhs) const; + bool operator!=(const LLKeyData& rhs) const; bool canHandle(const LLKeyData& data) const; bool canHandle(EMouseClickType mouse, KEY key, MASK mask) const; @@ -64,8 +64,8 @@ public: LLKeyBind() {} LLKeyBind(const LLSD &key_bind); - bool operator==(const LLKeyBind& rhs); - bool operator!=(const LLKeyBind& rhs); + bool operator==(const LLKeyBind& rhs) const; + bool operator!=(const LLKeyBind& rhs) const; bool isEmpty() const; bool empty() const { return isEmpty(); }; diff --git a/indra/llmessage/message_prehash.cpp b/indra/llmessage/message_prehash.cpp index c264a9f086..d3b80d684f 100644 --- a/indra/llmessage/message_prehash.cpp +++ b/indra/llmessage/message_prehash.cpp @@ -1402,3 +1402,5 @@ char const* const _PREHASH_HoverHeight = LLMessageStringTable::getInstance()->ge char const* const _PREHASH_Experience = LLMessageStringTable::getInstance()->getString("Experience"); char const* const _PREHASH_ExperienceID = LLMessageStringTable::getInstance()->getString("ExperienceID"); char const* const _PREHASH_LargeGenericMessage = LLMessageStringTable::getInstance()->getString("LargeGenericMessage"); +char const* const _PREHASH_GameControlInput = LLMessageStringTable::getInstance()->getString("GameControlInput"); +char const* const _PREHASH_AxisData = LLMessageStringTable::getInstance()->getString("AxisData"); diff --git a/indra/llmessage/message_prehash.h b/indra/llmessage/message_prehash.h index 1d30b69b67..5449eaf2a5 100644 --- a/indra/llmessage/message_prehash.h +++ b/indra/llmessage/message_prehash.h @@ -1403,5 +1403,7 @@ extern char const* const _PREHASH_HoverHeight; extern char const* const _PREHASH_Experience; extern char const* const _PREHASH_ExperienceID; extern char const* const _PREHASH_LargeGenericMessage; +extern char const* const _PREHASH_GameControlInput; +extern char const* const _PREHASH_AxisData; #endif diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt index 69e1b57245..13a0250fe5 100644 --- a/indra/llui/CMakeLists.txt +++ b/indra/llui/CMakeLists.txt @@ -270,12 +270,13 @@ target_link_libraries(llui llmath ll::hunspell llcommon + ll::SDL2 ) # Add tests if(LL_TESTS) include(LLAddBuildTest) - set(test_libs llmessage llcorehttp llxml llrender llcommon ll::hunspell) + set(test_libs llmessage llcorehttp llxml llrender llcommon ll::hunspell ll::SDL2) SET(llui_TEST_SOURCE_FILES llurlmatch.cpp @@ -285,7 +286,7 @@ if(LL_TESTS) # INTEGRATION TESTS if(NOT LINUX) - set(test_libs llui llmessage llcorehttp llxml llrender llcommon ll::hunspell ) + set(test_libs llui llmessage llcorehttp llxml llrender llcommon ll::hunspell ll::SDL2) LL_ADD_INTEGRATION_TEST(llurlentry llurlentry.cpp "${test_libs}") endif(NOT LINUX) endif(LL_TESTS) diff --git a/indra/llwindow/CMakeLists.txt b/indra/llwindow/CMakeLists.txt index 075e17235a..e251af3e6c 100644 --- a/indra/llwindow/CMakeLists.txt +++ b/indra/llwindow/CMakeLists.txt @@ -18,9 +18,11 @@ include(LLWindow) include(UI) include(ViewerMiscLibs) include(GLM) +include(SDL2) set(llwindow_SOURCE_FILES llcursortypes.cpp + llgamecontrol.cpp llkeyboard.cpp llkeyboardheadless.cpp llwindowheadless.cpp @@ -32,6 +34,7 @@ set(llwindow_HEADER_FILES CMakeLists.txt llcursortypes.h + llgamecontrol.h llkeyboard.h llkeyboardheadless.h llwindowheadless.h diff --git a/indra/llwindow/llgamecontrol.cpp b/indra/llwindow/llgamecontrol.cpp new file mode 100644 index 0000000000..5dc01c5e54 --- /dev/null +++ b/indra/llwindow/llgamecontrol.cpp @@ -0,0 +1,605 @@ +/** + * @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 <map> + +#include "SDL2/SDL.h" +#include "SDL2/SDL_gamecontroller.h" +#include "SDL2/SDL_joystick.h" + +constexpr size_t NUM_AXES = 6; + +// internal class for managing list of controllers and per-controller state +class LLGameControllerManager +{ +public: + void addController(SDL_JoystickID id, SDL_GameController* controller); + void removeController(SDL_JoystickID id); + + void onAxis(SDL_JoystickID id, U8 axis, S16 value); + void onButton(SDL_JoystickID id, U8 button, bool pressed); + + void onKeyButton(U8 button, bool pressed); + void onKeyAxis(U8 axis, U16 value); + + void clearAllInput(); + void clearAllKeys(); + size_t getControllerIndex(SDL_JoystickID id) const; + + void computeFinalState(LLGameControl::State& state); + + void clear(); + +private: + std::vector<SDL_JoystickID> mControllerIDs; + std::vector<SDL_GameController*> mControllers; + std::vector<LLGameControl::State> mStates; + LLGameControl::State mKeyboardState; +}; + +// 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 th ambient resend bandwidth low we + // expand the resend period at a geometric rate. + // + constexpr U64 MSEC_PER_NSEC = 1e6; + constexpr U64 FIRST_RESEND_PERIOD = 100 * MSEC_PER_NSEC; + constexpr U64 RESEND_EXPANSION_RATE = 10; + LLGameControl::State g_gameControlState; + U64 g_lastSend = 0; + U64 g_nextResendPeriod = FIRST_RESEND_PERIOD; + + std::map<U16, U8> g_keyButtonMap; + std::map<U16, U8> g_keyAxisMapPositive; + std::map<U16, U8> g_keyAxisMapNegative; + + bool g_includeKeyboardButtons = false; + + constexpr U8 MAX_AXIS = 5; + constexpr U8 MAX_BUTTON = 31; +} + +LLGameControl::~LLGameControl() +{ + terminate(); +} + +LLGameControl::State::State() : mButtons(0) +{ + mAxes.resize(NUM_AXES, 0); + mPrevAxes.resize(NUM_AXES, 0); +} + +bool LLGameControl::State::onButton(U8 button, bool pressed) +{ + U32 old_buttons = mButtons; + if (button <= MAX_BUTTON) + { + if (pressed) + { + mButtons |= (0x01 << button); + } + else + { + mButtons &= ~(0x01 << button); + } + } + bool changed = (old_buttons != mButtons); + return changed; +} + +void LLGameControllerManager::addController(SDL_JoystickID id, SDL_GameController* controller) +{ + if (controller) + { + size_t i = 0; + for (; i < mControllerIDs.size(); ++i) + { + if (id == mControllerIDs[i]) + { + break; + } + } + if (i == mControllerIDs.size()) + { + mControllerIDs.push_back(id); + mControllers.push_back(controller); + mStates.push_back(LLGameControl::State()); + LL_DEBUGS("SDL2") << "joystick=0x" << std::hex << id << std::dec + << " controller=" << controller + << LL_ENDL; + } + } +} + +void LLGameControllerManager::removeController(SDL_JoystickID id) +{ + size_t i = 0; + size_t num_controllers = mControllerIDs.size(); + for (; i < num_controllers; ++i) + { + if (id == mControllerIDs[i]) + { + LL_DEBUGS("SDL2") << "joystick=0x" << std::hex << id << std::dec + << " controller=" << mControllers[i] + << LL_ENDL; + + mControllerIDs[i] = mControllerIDs[num_controllers - 1]; + mControllers[i] = mControllers[num_controllers - 1]; + mStates[i] = mStates[num_controllers - 1]; + + mControllerIDs.pop_back(); + mControllers.pop_back(); + mStates.pop_back(); + break; + } + } +} + +size_t LLGameControllerManager::getControllerIndex(SDL_JoystickID id) const +{ + constexpr size_t UNREASONABLY_HIGH_INDEX = 1e6; + size_t index = UNREASONABLY_HIGH_INDEX; + for (size_t i = 0; i < mControllers.size(); ++i) + { + if (id == mControllerIDs[i]) + { + index = i; + break; + } + } + return index; +} + +void LLGameControllerManager::onAxis(SDL_JoystickID id, U8 axis, S16 value) +{ + if (axis > MAX_AXIS) + { + return; + } + size_t index = getControllerIndex(id); + if (index < mControllers.size()) + { + LL_DEBUGS("SDL2") << "joystick=0x" << std::hex << id << std::dec + << " axis=" << (S32)(axis) + << " value=" << (S32)(value) << LL_ENDL; + mStates[index].mAxes[axis] = value; + } +} + +void LLGameControllerManager::onButton(SDL_JoystickID id, U8 button, bool pressed) +{ + size_t index = getControllerIndex(id); + if (index < mControllers.size()) + { + if (mStates[index].onButton(button, pressed)) + { + LL_DEBUGS("SDL2") << "joystick=0x" << std::hex << id << std::dec + << " button i=" << (S32)(button) + << " pressed=" << pressed << LL_ENDL; + } + } +} + +void LLGameControllerManager::onKeyButton(U8 button, bool pressed) +{ + if (mKeyboardState.onButton(button, pressed)) + { + LL_DEBUGS("SDL2") << " keyboard button i=" << (S32)(button) << " pressed=" << pressed << LL_ENDL; + } +} + +void LLGameControllerManager::onKeyAxis(U8 axis, U16 value) +{ + if (mKeyboardState.mAxes[axis] != value) + { + mKeyboardState.mAxes[axis] = value; + LL_DEBUGS("SDL2") << " keyboard axis i=" << (S32)(axis) << " value=" << (S32)(value) << LL_ENDL; + } +} + +void LLGameControllerManager::clearAllInput() +{ + for (auto& state : mStates) + { + state.mButtons = 0; + std::fill(state.mAxes.begin(), state.mAxes.end(), 0); + } + mKeyboardState.mButtons = 0; + std::fill(mKeyboardState.mAxes.begin(), mKeyboardState.mAxes.end(), 0); +} + +void LLGameControllerManager::clearAllKeys() +{ + mKeyboardState.mButtons = 0; + std::fill(mKeyboardState.mAxes.begin(), mKeyboardState.mAxes.end(), 0); +} + +void LLGameControllerManager::computeFinalState(LLGameControl::State& state) +{ + // clear the slate + std::vector<S32> axes_accumulator; + axes_accumulator.resize(NUM_AXES, 0); + U32 old_buttons = state.mButtons; + state.mButtons = 0; + + // accumulate the controllers + for (const auto& s : mStates) + { + state.mButtons |= s.mButtons; + for (size_t i = 0; i < NUM_AXES; ++i) + { + axes_accumulator[i] += (S32)(s.mAxes[i]); + } + } + + // accumulate the keyboard + state.mButtons |= mKeyboardState.mButtons; + for (size_t i = 0; i < NUM_AXES; ++i) + { + axes_accumulator[i] += (S32)(mKeyboardState.mAxes[i]); + } + if (old_buttons != state.mButtons) + { + g_nextResendPeriod = 0; // packet needs to go out ASAP + } + + // clamp the axes + for (size_t i = 0; i < NUM_AXES; ++i) + { + S32 new_axis = (S16)(std::min(std::max(axes_accumulator[i], -32768), 32767)); + // check for change + if (state.mAxes[i] != new_axis) + { + // When axis changes we explicitly update the corresponding prevAxis + // otherwise, we let prevAxis get updated in updateResendPeriod() + // which is explicitly called after a packet is sent. This allows + // unchanged axes to be included in first resend but not later ones. + state.mPrevAxes[i] = state.mAxes[i]; + state.mAxes[i] = new_axis; + g_nextResendPeriod = 0; // packet needs to go out ASAP + } + } +} + +void LLGameControllerManager::clear() +{ + mControllerIDs.clear(); + mControllers.clear(); + mStates.clear(); +} + + +U64 get_now_nsec() +{ + std::chrono::time_point<std::chrono::steady_clock> t0; + return (std::chrono::steady_clock::now() - t0).count(); +} + +// util for dumping SDL_GameController info +std::ostream& operator<<(std::ostream& out, SDL_GameController* c) +{ + if (! c) + { + return out << "nullptr"; + } + out << "{"; + out << " name='" << SDL_GameControllerName(c) << "'"; + out << " type='" << SDL_GameControllerGetType(c) << "'"; + out << " vendor='" << SDL_GameControllerGetVendor(c) << "'"; + out << " product='" << SDL_GameControllerGetProduct(c) << "'"; + out << " version='" << SDL_GameControllerGetProductVersion(c) << "'"; + //CRASH! out << " serial='" << SDL_GameControllerGetSerial(c) << "'"; + out << " }"; + return out; +} + +// util for dumping SDL_Joystick info +std::ostream& operator<<(std::ostream& out, SDL_Joystick* j) +{ + if (! j) + { + return out << "nullptr"; + } + out << "{"; + out << " p=0x" << (void*)(j); + out << " name='" << SDL_JoystickName(j) << "'"; + out << " type='" << SDL_JoystickGetType(j) << "'"; + out << " instance='" << SDL_JoystickInstanceID(j) << "'"; + out << " product='" << SDL_JoystickGetProduct(j) << "'"; + out << " version='" << SDL_JoystickGetProductVersion(j) << "'"; + out << " num_axes=" << SDL_JoystickNumAxes(j); + out << " num_balls=" << SDL_JoystickNumBalls(j); + out << " num_hats=" << SDL_JoystickNumHats(j); + out << " num_buttons=" << SDL_JoystickNumHats(j); + out << " }"; + return out; +} + +void onControllerDeviceAdded(const SDL_Event& event) +{ + int device_index = event.cdevice.which; + SDL_JoystickID id = SDL_JoystickGetDeviceInstanceID(device_index); + SDL_GameController* controller = SDL_GameControllerOpen(device_index); + + g_manager.addController(id, controller); +} + +void onControllerDeviceRemoved(const SDL_Event& event) +{ + SDL_JoystickID id = event.cdevice.which; + g_manager.removeController(id); +} + +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::isInitialized() +{ + return g_gameControl != nullptr; +} + +void sdl_logger(void *userdata, int category, SDL_LogPriority priority, const char *message) +{ + LL_DEBUGS("SDL2") << "log='" << message << "'" << LL_ENDL; +} + +// static +void LLGameControl::init() +{ + if (!g_gameControl) + { + g_gameControl = LLGameControl::getInstance(); + SDL_InitSubSystem(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER); + SDL_LogSetOutputFunction(&sdl_logger, nullptr); + } +} + +// static +void LLGameControl::terminate() +{ + g_manager.clear(); + SDL_Quit(); +} + +// static +void LLGameControl::addKeyButtonMap(U16 key, U8 button) +{ + g_keyButtonMap[key] = button; +} + +// static +void LLGameControl::removeKeyButtonMap(U16 key) +{ + g_keyButtonMap.erase(key); +} + +// static +void LLGameControl::addKeyAxisMap(U16 key, U8 axis, bool positive) +{ + if (axis > MAX_AXIS) + { + return; + } + if (positive) + { + g_keyAxisMapPositive[key] = axis; + g_keyAxisMapNegative.erase(key); + } + else + { + g_keyAxisMapNegative[key] = axis; + g_keyAxisMapPositive.erase(key); + } +} + +// static +void LLGameControl::removeKeyAxisMap(U16 key) +{ + g_keyAxisMapPositive.erase(key); + g_keyAxisMapNegative.erase(key); +} + +// static +void LLGameControl::onKeyDown(U16 key, U32 mask) +{ + auto itr = g_keyButtonMap.find(key); + if (itr != g_keyButtonMap.end()) + { + g_manager.onKeyButton(itr->second, true); + } + else + { + itr = g_keyAxisMapPositive.find(key); + if (itr != g_keyAxisMapPositive.end()) + { + g_manager.onKeyAxis(itr->second, 32767); + } + else + { + itr = g_keyAxisMapNegative.find(key); + if (itr != g_keyAxisMapNegative.end()) + { + g_manager.onKeyAxis(itr->second, -32768); + } + } + } +} + +// static +void LLGameControl::onKeyUp(U16 key, U32 mask) +{ + auto itr = g_keyButtonMap.find(key); + if (itr != g_keyButtonMap.end()) + { + g_manager.onKeyButton(itr->second, true); + } + else + { + itr = g_keyAxisMapPositive.find(key); + if (itr != g_keyAxisMapPositive.end()) + { + g_manager.onKeyAxis(itr->second, 0); + } + else + { + itr = g_keyAxisMapNegative.find(key); + if (itr != g_keyAxisMapNegative.end()) + { + g_manager.onKeyAxis(itr->second, 0); + } + } + } +} + +//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::computeFinalInputAndCheckForChanges() +{ + g_manager.computeFinalState(g_gameControlState); + return g_lastSend + g_nextResendPeriod < get_now_nsec(); +} + +// static +void LLGameControl::clearAllInput() +{ + g_manager.clearAllInput(); +} + +// static +void LLGameControl::clearAllKeys() +{ + g_manager.clearAllKeys(); +} + +// static +void LLGameControl::processEvents(bool app_has_focus) +{ + 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 + } + clearAllInput(); + return; + } + + while (g_gameControl && SDL_PollEvent(&event)) + { + switch (event.type) + { + case SDL_CONTROLLERDEVICEADDED: + onControllerDeviceAdded(event); + break; + case SDL_CONTROLLERDEVICEREMOVED: + onControllerDeviceRemoved(event); + break; + case SDL_CONTROLLERBUTTONDOWN: + /* FALLTHROUGH */ + case SDL_CONTROLLERBUTTONUP: + onControllerButton(event); + break; + case SDL_CONTROLLERAXISMOTION: + onControllerAxis(event); + break; + default: + break; + } + } +} + +// static +const LLGameControl::State& LLGameControl::getState() +{ + return g_gameControlState; +} + +// static +void LLGameControl::setIncludeKeyboardButtons(bool include) +{ + g_includeKeyboardButtons = include; +} + +// static +bool LLGameControl::getIncludeKeyboardButtons() +{ + return g_includeKeyboardButtons; +} + +//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. + // + // In other words: we want to include changed axes in the first resend + // so we only overrite g_gameControlState.mPrevAxes on higher resends. + g_gameControlState.mPrevAxes = g_gameControlState.mAxes; + g_nextResendPeriod *= RESEND_EXPANSION_RATE; + } +} + diff --git a/indra/llwindow/llgamecontrol.h b/indra/llwindow/llgamecontrol.h new file mode 100644 index 0000000000..fe6d6f0138 --- /dev/null +++ b/indra/llwindow/llgamecontrol.h @@ -0,0 +1,86 @@ +/** + * @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" + + +// LLGameControl is a singleton with pure static public interface +class LLGameControl : public LLSingleton<LLGameControl> +{ + LLSINGLETON_EMPTY_CTOR(LLGameControl); + virtual ~LLGameControl(); + LOG_CLASS(LLGameControl); + +public: + // State is a minimal class for storing axes and buttons values + class State + { + public: + State(); + bool onButton(U8 button, bool pressed); + std::vector<S16> mAxes; // [ -32768, 32767 ] + std::vector<S16> mPrevAxes; // value in last outgoing packet + U32 mButtons; + }; + + static bool isInitialized(); + static void init(); + static void terminate(); + + static void addKeyButtonMap(U16 key, U8 button); + static void removeKeyButtonMap(U16 key); + static void addKeyAxisMap(U16 key, U8 axis, bool positive); + static void removeKeyAxisMap(U16 key); + + static void onKeyDown(U16 key, U32 mask); + static void onKeyUp(U16 key, U32 mask); + + // 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 computeFinalInputAndCheckForChanges(); + + static void clearAllInput(); + static void clearAllKeys(); + + static void processEvents(bool app_has_focus = true); + static const State& getState(); + + static void setIncludeKeyboardButtons(bool include); + static bool getIncludeKeyboardButtons(); + + // call this after putting a GameControlInput packet on the wire + static void updateResendPeriod(); +}; + diff --git a/indra/llwindow/llkeyboard.cpp b/indra/llwindow/llkeyboard.cpp index a16c0a318a..a858d3b16f 100644 --- a/indra/llwindow/llkeyboard.cpp +++ b/indra/llwindow/llkeyboard.cpp @@ -29,6 +29,7 @@ #include "llkeyboard.h" #include "llwindowcallbacks.h" +#include "llgamecontrol.h" // // Globals @@ -161,6 +162,7 @@ void LLKeyboard::resetKeyDownAndHandle() mCallbacks->handleTranslatedKeyUp(i, mask); } } + LLGameControl::clearAllKeys(); } // BUG this has to be called when an OS dialog is shown, otherwise modifier key state @@ -275,6 +277,43 @@ bool LLKeyboard::handleTranslatedKeyUp(KEY translated_key, U32 translated_mask) return handled; } +bool LLKeyboard::handleKeyDown(const U16 key, const U32 mask) +{ + U32 translated_mask = updateModifiers(mask); + + KEY translated_key = 0; + bool handled = false; + if(translateKey(key, &translated_key)) + { + handled = handleTranslatedKeyDown(translated_key, translated_mask); + } + if (!handled) + { + LLGameControl::onKeyDown(translated_key, translated_mask); + } + + return handled; +} + + +bool LLKeyboard::handleKeyUp(const U16 key, const U32 mask) +{ + U32 translated_mask = updateModifiers(mask); + + KEY translated_key = 0; + bool handled = false; + if(translateKey(key, &translated_key)) + { + handled = handleTranslatedKeyUp(translated_key, translated_mask); + } + if (!handled) + { + LLGameControl::onKeyUp(translated_key, translated_mask); + } + + return handled; +} + void LLKeyboard::toggleInsertMode() { diff --git a/indra/llwindow/llkeyboardheadless.h b/indra/llwindow/llkeyboardheadless.h index 439abaf25b..cc31b99d3f 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 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; #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..f590b8db8b 100644 --- a/indra/llwindow/llkeyboardmacosx.cpp +++ b/indra/llwindow/llkeyboardmacosx.cpp @@ -203,7 +203,7 @@ void LLKeyboardMacOSX::handleModifier(MASK mask) updateModifiers(mask); } -MASK LLKeyboardMacOSX::updateModifiers(const U32 mask) +MASK LLKeyboardMacOSX::updateModifiers(U32 mask) { // translate the mask MASK out_mask = 0; diff --git a/indra/llwindow/llkeyboardmacosx.h b/indra/llwindow/llkeyboardmacosx.h index 92ab5c9a85..af8a626db8 100644 --- a/indra/llwindow/llkeyboardmacosx.h +++ b/indra/llwindow/llkeyboardmacosx.h @@ -42,14 +42,14 @@ 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); diff --git a/indra/llwindow/llkeyboardwin32.cpp b/indra/llwindow/llkeyboardwin32.cpp index 8d6b8d9b93..7ef616a8b7 100644 --- a/indra/llwindow/llkeyboardwin32.cpp +++ b/indra/llwindow/llkeyboardwin32.cpp @@ -182,7 +182,7 @@ void LLKeyboardWin32::resetMaskKeys() //} -MASK LLKeyboardWin32::updateModifiers() +MASK LLKeyboardWin32::updateModifiers(U32 mask) { //RN: this seems redundant, as we should have already received the appropriate // messages for the modifier keys @@ -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..d3dc65d9aa 100644 --- a/indra/llwindow/llkeyboardwin32.h +++ b/indra/llwindow/llkeyboardwin32.h @@ -37,15 +37,16 @@ class LLKeyboardWin32 : public LLKeyboard { public: LLKeyboardWin32(); - /*virtual*/ ~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); + ~LLKeyboardWin32() {}; + + 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(); diff --git a/indra/llwindow/llwindow.cpp b/indra/llwindow/llwindow.cpp index 4016f420e8..a4b7a65cb2 100644 --- a/indra/llwindow/llwindow.cpp +++ b/indra/llwindow/llwindow.cpp @@ -27,14 +27,14 @@ #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" @@ -72,13 +72,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 +263,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 +342,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,22 +415,22 @@ 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, name, x, y, width, height, flags, - fullscreen, clearBg, enable_vsync, use_gl, ignore_pixel_depth, fsaa_samples); -#elif LL_WINDOWS +#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); + fullscreen, clearBg, enable_vsync, use_gl, ignore_pixel_depth, fsaa_samples, max_cores, max_vram, max_gl_version); #elif LL_DARWIN 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 diff --git a/indra/llwindow/llwindowsdl.h b/indra/llwindow/llwindowsdl.h index 7ad30d41ce..609d8a6f49 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" @@ -300,4 +301,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/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 29dbceedac..7fbf214dcf 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -28,6 +28,7 @@ include(LLKDU) include(LLPhysicsExtensions) include(LLPrimitive) include(LLWindow) +include(SDL2) include(NDOF) include(NVAPI) include(OPENAL) @@ -1745,7 +1746,7 @@ if (WINDOWS) # And of course it's straightforward to read a text file in Python. set(COPY_INPUT_DEPENDENCIES - # The following commented dependencies are determined at variably at build time. Can't do this here. + # The following commented dependencies are determined variably at build time. Can't do this here. ${CMAKE_SOURCE_DIR}/../etc/message.xml ${CMAKE_SOURCE_DIR}/../scripts/messages/message_template.msg ${SHARED_LIB_STAGING_DIR}/openjp2.dll @@ -2245,6 +2246,7 @@ if (LL_TESTS) lllogin llplugin llappearance + ll::SDL2 ) set_source_files_properties( @@ -2296,6 +2298,7 @@ if (LL_TESTS) lllogin llprimitive lllogin + ll::SDL2 ) LL_ADD_INTEGRATION_TEST(cppfeatures diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 60d8c6db76..55f8f77383 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -2612,6 +2612,28 @@ <key>Value</key> <integer>1</integer> </map> + <key>EnableGameControlInput</key> + <map> + <key>Comment</key> + <string>Transmit game controller input to server</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>0</integer> + </map> + <key>EnableGameControlKeyboardInput</key> + <map> + <key>Comment</key> + <string>Send 'unhandled' keystrokes as GameInput to server</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>0</integer> + </map> <key>EnableGestureSounds</key> <map> <key>Comment</key> diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 6fd58ef1be..ffa742d154 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -64,6 +64,7 @@ #include "llluamanager.h" #include "llurlfloaterdispatchhandler.h" #include "llviewerjoystick.h" +#include "llgamecontrol.h" #include "llcalc.h" #include "llconversationlog.h" #if LL_WINDOWS @@ -1132,6 +1133,7 @@ bool LLAppViewer::init() { LLViewerJoystick::getInstance()->init(false); } + LLGameControl::init(); try { @@ -1416,6 +1418,59 @@ bool LLAppViewer::frame() return ret; } + +// static +bool packGameControlInput(LLMessageSystem* msg) +{ + if (! LLGameControl::computeFinalInputAndCheckForChanges()) + { + return false; + } + if (!gSavedSettings.getBOOL("EnableGameControlInput")) + { + LLGameControl::clearAllInput(); + return false; + } + + msg->newMessageFast(_PREHASH_GameControlInput); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID", gAgentID); + msg->addUUID("SessionID", gAgentSessionID); + + const LLGameControl::State& state = LLGameControl::getState(); + + size_t num_indices = state.mAxes.size(); + for (U8 i = 0; i < num_indices; ++i) + { + if (state.mAxes[i] != state.mPrevAxes[i]) + { + // only pack an axis if it differs from previously packed value + msg->nextBlockFast(_PREHASH_AxisData); + msg->addU8Fast(_PREHASH_Index, i); + msg->addS16Fast(_PREHASH_Value, state.mAxes[i]); + } + } + + U32 button_flags = state.mButtons; + if (button_flags > 0) + { + std::vector<U8> buttons; + for (U8 i = 0; i < 32; i++) + { + if (button_flags & (0x1 << i)) + { + buttons.push_back(i); + } + } + msg->nextBlockFast(_PREHASH_ButtonData); + msg->addBinaryDataFast(_PREHASH_Data, (void*)(buttons.data()), (S32)(buttons.size())); + } + + LLGameControl::updateResendPeriod(); + return true; +} + + bool LLAppViewer::doFrame() { LL_RECORD_BLOCK_TIME(FTM_FRAME); @@ -1526,6 +1581,15 @@ bool LLAppViewer::doFrame() joystick->scanJoystick(); gKeyboard->scanKeyboard(); gViewerInput.scanMouse(); + + LLGameControl::setIncludeKeyboardButtons(gSavedSettings.getBOOL("EnableGameControlKeyboardInput")); + LLGameControl::processEvents(gFocusMgr.getAppHasFocus()); + // to help minimize lag we send GameInput packets immediately + // after getting the latest GameController input + if (packGameControlInput(gMessageSystem)) + { + gAgent.sendMessage(); + } } // Update state based on messages, user input, object idle. @@ -1961,6 +2025,7 @@ bool LLAppViewer::cleanup() // Turn off Space Navigator and similar devices LLViewerJoystick::getInstance()->terminate(); } + LLGameControl::terminate(); LL_INFOS() << "Cleaning up Objects" << LL_ENDL; diff --git a/indra/newview/llfilepicker.cpp b/indra/newview/llfilepicker.cpp index d5e3627d8e..3639064cc4 100644 --- a/indra/newview/llfilepicker.cpp +++ b/indra/newview/llfilepicker.cpp @@ -36,11 +36,8 @@ #include "llviewercontrol.h" #include "llwindow.h" // beforeDialog() -#if LL_SDL -#include "llwindowsdl.h" // for some X/GTK utils to help with filepickers -#endif // LL_SDL - #if LL_LINUX +#include "llwindowsdl.h" // for some X/GTK utils to help with filepickers #include "llhttpconstants.h" // file picker uses some of thes constants on Linux #endif diff --git a/indra/newview/llkeyconflict.cpp b/indra/newview/llkeyconflict.cpp index 666ab4f5d0..b9456349cd 100644 --- a/indra/newview/llkeyconflict.cpp +++ b/indra/newview/llkeyconflict.cpp @@ -99,29 +99,34 @@ LLKeyConflictHandler::~LLKeyConflictHandler() // Note: does not reset bindings if temporary file was used } -bool LLKeyConflictHandler::canHandleControl(const std::string &control_name, EMouseClickType mouse_ind, KEY key, MASK mask) +bool LLKeyConflictHandler::canHandleControl(const std::string &control_name, EMouseClickType mouse_ind, KEY key, MASK mask) const { - return mControlsMap[control_name].canHandle(mouse_ind, key, mask); + control_map_t::const_iterator iter = mControlsMap.find(control_name); + if (iter != mControlsMap.end()) + { + return iter->second.canHandle(mouse_ind, key, mask); + } + return false; } -bool LLKeyConflictHandler::canHandleKey(const std::string &control_name, KEY key, MASK mask) +bool LLKeyConflictHandler::canHandleKey(const std::string &control_name, KEY key, MASK mask) const { return canHandleControl(control_name, CLICK_NONE, key, mask); } -bool LLKeyConflictHandler::canHandleMouse(const std::string &control_name, EMouseClickType mouse_ind, MASK mask) +bool LLKeyConflictHandler::canHandleMouse(const std::string &control_name, EMouseClickType mouse_ind, MASK mask) const { return canHandleControl(control_name, mouse_ind, KEY_NONE, mask); } -bool LLKeyConflictHandler::canHandleMouse(const std::string &control_name, S32 mouse_ind, MASK mask) +bool LLKeyConflictHandler::canHandleMouse(const std::string &control_name, S32 mouse_ind, MASK mask) const { return canHandleControl(control_name, (EMouseClickType)mouse_ind, KEY_NONE, mask); } -bool LLKeyConflictHandler::canAssignControl(const std::string &control_name) +bool LLKeyConflictHandler::canAssignControl(const std::string &control_name) const { - control_map_t::iterator iter = mControlsMap.find(control_name); + control_map_t::const_iterator iter = mControlsMap.find(control_name); if (iter != mControlsMap.end()) { return iter->second.mAssignable; @@ -284,7 +289,7 @@ void LLKeyConflictHandler::loadFromSettings(const LLViewerInput::KeyMode& keymod LLKeyboard::keyFromString(it->key, &key); } LLKeyboard::maskFromString(it->mask, &mask); - // Note: it->command is also the name of UI element, howhever xml we are loading from + // Note: it->command is also the name of UI element, however xml we are loading from // might not know all the commands, so UI will have to know what to fill by its own // Assumes U32_MAX conflict mask, and is assignable by default, // but assignability might have been overriden by generatePlaceholders. diff --git a/indra/newview/llkeyconflict.h b/indra/newview/llkeyconflict.h index 6c01ddb7a7..21990688a2 100644 --- a/indra/newview/llkeyconflict.h +++ b/indra/newview/llkeyconflict.h @@ -44,7 +44,7 @@ public: LLKeyData getKeyData(U32 index) { return mKeyBind.getKeyData(index); } void setPrimaryKeyData(const LLKeyData& data) { mKeyBind.replaceKeyData(data, 0); } void setKeyData(const LLKeyData& data, U32 index) { mKeyBind.replaceKeyData(data, index); } - bool canHandle(EMouseClickType mouse, KEY key, MASK mask) { return mKeyBind.canHandle(mouse, key, mask); } + bool canHandle(EMouseClickType mouse, KEY key, MASK mask) const { return mKeyBind.canHandle(mouse, key, mask); } LLKeyBind mKeyBind; bool mAssignable; // whether user can change key or key simply acts as placeholder @@ -76,11 +76,11 @@ public: LLKeyConflictHandler(ESourceMode mode); ~LLKeyConflictHandler(); - bool canHandleControl(const std::string &control_name, EMouseClickType mouse_ind, KEY key, MASK mask); - bool canHandleKey(const std::string &control_name, KEY key, MASK mask); - bool canHandleMouse(const std::string &control_name, EMouseClickType mouse_ind, MASK mask); - bool canHandleMouse(const std::string &control_name, S32 mouse_ind, MASK mask); //Just for convinience - bool canAssignControl(const std::string &control_name); + bool canHandleControl(const std::string &control_name, EMouseClickType mouse_ind, KEY key, MASK mask) const; + bool canHandleKey(const std::string &control_name, KEY key, MASK mask) const; + bool canHandleMouse(const std::string &control_name, EMouseClickType mouse_ind, MASK mask) const; + bool canHandleMouse(const std::string &control_name, S32 mouse_ind, MASK mask) const; //Just for convenience + bool canAssignControl(const std::string &control_name) const; static bool isReservedByMenu(const KEY &key, const MASK &mask); static bool isReservedByMenu(const LLKeyData &data); diff --git a/indra/newview/skins/default/xui/en/floater_preferences.xml b/indra/newview/skins/default/xui/en/floater_preferences.xml index 4b0e0bb221..4614f2f06c 100644 --- a/indra/newview/skins/default/xui/en/floater_preferences.xml +++ b/indra/newview/skins/default/xui/en/floater_preferences.xml @@ -170,6 +170,13 @@ layout="topleft" help_topic="preferences_controls_tab" name="controls" /> + <panel + class="panel_preference_game_controls" + filename="panel_preferences_game_controls.xml" + label="Game Controls" + layout="topleft" + help_topic="preferences_game_controls_tab" + name="gamecontrols" /> </tab_container> </floater> diff --git a/indra/newview/skins/default/xui/en/panel_preferences_game_controls.xml b/indra/newview/skins/default/xui/en/panel_preferences_game_controls.xml new file mode 100644 index 0000000000..4b693e8955 --- /dev/null +++ b/indra/newview/skins/default/xui/en/panel_preferences_game_controls.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<panel + border="true" + follows="all" + height="408" + label="Game Controls" + layout="topleft" + left="102" + name="gamecontrols" + top="1" + width="517"> + <check_box + control_name="EnableGameControlInput" + follows="top|left" + height="15" + label="Send GameControl Input to server" + layout="topleft" + left="10" + name="enable_game_control_input" + top="6" + width="232"/> + <check_box + control_name="EnableGameControlKeyboardInput" + follows="top|left" + height="15" + label="Include otherwise 'unhandled' Keyboard events in GameControl Input" + layout="topleft" + left="10" + name="game_control_keyboard_input" + top="27" + width="232"/> +</panel> diff --git a/indra/newview/tests/llgamecontrol_stub.cpp b/indra/newview/tests/llgamecontrol_stub.cpp new file mode 100644 index 0000000000..0872f647d7 --- /dev/null +++ b/indra/newview/tests/llgamecontrol_stub.cpp @@ -0,0 +1,76 @@ +/** + * @file llgamecontrol_stub.h + * @brief Stubbery for LLGameControl + * + * $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 "SDL2/SDL_events.h" + + +void LLGameControl::addKeyButtonMap(U16 key, U8 button) +{ +} + +void LLGameControl::removeKeyButtonMap(U16 key) +{ +} + +void LLGameControl::addKeyAxisMap(U16 key, U8 axis, bool positive) +{ +} + +void LLGameControl::removeKeyAxisMap(U16 key) +{ +} + +void LLGameControl::onKeyDown(U16 key, U32 mask) +{ +} + +void LLGameControl::onKeyUp(U16 key, U32 mask) +{ +} + +// static +bool LLGameControl::isInitialized() +{ + return false; +} + +// static +void LLGameControl::init() +{ +} + +// static +void LLGameControl::terminate() +{ +} + +// static +void LLGameControl::processEvents(bool app_has_focus) +{ +} + diff --git a/indra/newview/tests/llversioninfo_test.cpp b/indra/newview/tests/llversioninfo_test.cpp index 8049e67fc5..9eb5146f2b 100644 --- a/indra/newview/tests/llversioninfo_test.cpp +++ b/indra/newview/tests/llversioninfo_test.cpp @@ -29,7 +29,10 @@ #include "../llversioninfo.h" - #include <iostream> +#include <iostream> + +#include "llgamecontrol_stub.cpp" + // LL_VIEWER_CHANNEL is a macro defined on the compiler command line. The // macro expands to the string name of the channel, but without quotes. We diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index daa7e58211..16904cc43c 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -584,6 +584,9 @@ class Windows_x86_64_Manifest(ViewerManifest): self.path("vivoxsdk_x64.dll") self.path("ortp_x64.dll") + # SDL2 + self.path("SDL2.dll") + # BugSplat if self.args.get('bugsplat'): self.path("BsSndRpt64.exe") @@ -928,6 +931,7 @@ class Darwin_x86_64_Manifest(ViewerManifest): with self.prefix(src=relpkgdir, dst=""): self.path("libndofdev.dylib") + self.path("libSDL2-*.dylib") with self.prefix(src_dst="cursors_mac"): self.path("*.tif") @@ -1406,7 +1410,7 @@ class Linux_x86_64_Manifest(LinuxManifest): pkgdir = self.args['package_dir'] relpkgdir = os.path.join(pkgdir, "lib", "release") - debpkgdir = os.path.join(pkgdir, "lib", "debug") + #debpkgdir = os.path.join(pkgdir, "lib", "debug") with self.prefix(src=relpkgdir, dst="lib"): self.path("libapr-1.so*") @@ -1415,6 +1419,15 @@ class Linux_x86_64_Manifest(LinuxManifest): self.path_optional("libjemalloc*.so") + self.path("libdb*.so") + self.path("libuuid.so*") + self.path("libdirectfb-1.*.so.*") + self.path("libfusion-1.*.so.*") + self.path("libdirect-1.*.so.*") + self.path("libopenjp2.so*") + self.path("libdirectfb-1.4.so.5") + self.path("libfusion-1.4.so.5") + self.path("libdirect-1.4.so.5*") self.path("libalut.so*") self.path("libopenal.so*") self.path("libopenal.so", "libvivoxoal.so.1") # vivox's sdk expects this soname diff --git a/scripts/messages/message_template.msg b/scripts/messages/message_template.msg index 1450c111c2..4bbcbd369d 100755 --- a/scripts/messages/message_template.msg +++ b/scripts/messages/message_template.msg @@ -6,7 +6,7 @@ version 2.0 // numbers. Each message must be numbered relative to the // other messages of that type. The current highest number // for each type is listed below: -// Low: 430 +// Low: 431 // Medium: 18 // High: 30 // PLEASE UPDATE THIS WHEN YOU ADD A NEW MESSAGE! @@ -9133,3 +9133,36 @@ version 2.0 } } +// viewer->sim +// GameControlInput - input from game controller +// This message is split into two Variable chunks: +// +// AxisData = list of {Index:Value} pairs. Value is an S16 that maps to range [-1, 1]. +// ButtonData = list of indices of pressed buttons +// +// Any Axis ommitted from the message is assumed by the receiving Simulator to be unchanged +// from its last message. +// +// Any Button ommitted from the message is assumed by the receiving Simulator to be unpressed. +// +// Since GameControlInput messages are sent unreliably: whenenver the changes stop the last +// message will be resent a few times in the hopes the server finally receives it. +// +{ + GameControlInput Medium 431 NotTrusted Zerocoded + { + AgentData Single + { AgentID LLUUID } + { SessionID LLUUID } + } + { + AxisData Variable + { Index U8 } + { Value S16 } + } + { + ButtonData Variable + { Data Variable 1 } + } +} + |