summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLeviathan Linden <leviathan@lindenlab.com>2023-09-19 09:40:08 -0700
committerAndrew Meadows <andrew.l.meadows@gmail.com>2024-10-03 09:01:06 -0700
commit13221f67c465017f44ca46aeca23b0d820935825 (patch)
treea38de052ac3adb3160cfca76abbadf77b321cc71
parent49c661f6cf9ae0a75b93c870a00edba59df54189 (diff)
add GameControl feature and SDL2 dependency
-rw-r--r--autobuild.xml68
-rw-r--r--indra/cmake/CMakeLists.txt1
-rw-r--r--indra/cmake/Copy3rdPartyLibs.cmake4
-rw-r--r--indra/cmake/LLWindow.cmake14
-rw-r--r--indra/cmake/SDL2.cmake22
-rw-r--r--indra/llcommon/llkeybind.cpp8
-rw-r--r--indra/llcommon/llkeybind.h8
-rw-r--r--indra/llmessage/message_prehash.cpp2
-rw-r--r--indra/llmessage/message_prehash.h2
-rw-r--r--indra/llui/CMakeLists.txt5
-rw-r--r--indra/llwindow/CMakeLists.txt3
-rw-r--r--indra/llwindow/llgamecontrol.cpp605
-rw-r--r--indra/llwindow/llgamecontrol.h86
-rw-r--r--indra/llwindow/llkeyboard.cpp39
-rw-r--r--indra/llwindow/llkeyboardheadless.h19
-rw-r--r--indra/llwindow/llkeyboardmacosx.cpp2
-rw-r--r--indra/llwindow/llkeyboardmacosx.h14
-rw-r--r--indra/llwindow/llkeyboardwin32.cpp4
-rw-r--r--indra/llwindow/llkeyboardwin32.h19
-rw-r--r--indra/llwindow/llwindow.cpp46
-rw-r--r--indra/llwindow/llwindowsdl.h2
-rw-r--r--indra/newview/CMakeLists.txt5
-rw-r--r--indra/newview/app_settings/settings.xml22
-rw-r--r--indra/newview/llappviewer.cpp65
-rw-r--r--indra/newview/llfilepicker.cpp5
-rw-r--r--indra/newview/llkeyconflict.cpp21
-rw-r--r--indra/newview/llkeyconflict.h12
-rw-r--r--indra/newview/skins/default/xui/en/floater_preferences.xml7
-rw-r--r--indra/newview/skins/default/xui/en/panel_preferences_game_controls.xml32
-rw-r--r--indra/newview/tests/llgamecontrol_stub.cpp76
-rw-r--r--indra/newview/tests/llversioninfo_test.cpp5
-rwxr-xr-xindra/newview/viewer_manifest.py15
-rwxr-xr-xscripts/messages/message_template.msg35
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 }
+ }
+}
+